├── CONTRIBUTING.md ├── .gitignore ├── composer.json ├── LICENSE ├── CodeIgniter4 ├── Sniffs │ ├── PHP │ │ ├── DiscouragedFunctionsSniff.php │ │ └── ForbiddenFunctionsSniff.php │ ├── Operators │ │ ├── BooleanOrSniff.php │ │ ├── BooleanAndSniff.php │ │ ├── IsIdenticalSniff.php │ │ └── IsNotIdenticalSniff.php │ ├── Files │ │ ├── FilenameMatchesClassSniff.php │ │ └── OneClassPerFileSniff.php │ ├── WhiteSpace │ │ ├── BooleanNotSpaceAfterSniff.php │ │ └── FunctionClosingBraceSpaceSniff.php │ ├── NamingConventions │ │ ├── ValidFunctionNameSniff.php │ │ ├── ValidMethodNameSniff.php │ │ └── ValidVariableNameSniff.php │ ├── ControlStructures │ │ ├── ControlStructureSpacingSniff.php │ │ └── AllmanControlSignatureSniff.php │ ├── Commenting │ │ ├── ClassCommentSniff.php │ │ └── FileCommentSniff.php │ └── Functions │ │ └── FunctionDeclarationSniff.php ├── Util │ └── Common.php └── ruleset.xml ├── composer.lock └── README.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CodeIgniter4-Standard 2 | 3 | ## Contributing 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | 4 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 5 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 6 | # composer.lock 7 | /Codeigniter4/Tests 8 | /tests 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "louisl/codeigniter4-standard", 3 | "description": "CodeIgniter 4 Standard for PHP_CodeSniffer 3.", 4 | "version":"1.0.0-beta0007", 5 | "license":"MIT", 6 | "authors": [ 7 | { 8 | "name": "Louis Linehan", 9 | "email": "louis.linehan@gmail.com", 10 | "homepage": "https://github.com/louisl", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | }, 16 | "require-dev": { 17 | "squizlabs/php_codesniffer": "^3.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 louisl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/PHP/DiscouragedFunctionsSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\PHP; 12 | 13 | use CodeIgniter4\Sniffs\PHP\ForbiddenFunctionsSniff; 14 | use PHP_CodeSniffer\Sniffs\Sniff; 15 | use PHP_CodeSniffer\Files\File; 16 | 17 | /** 18 | * Discouraged Functions Sniff 19 | * 20 | * Discourages the use of debug functions. 21 | * 22 | * @author Louis Linehan 23 | */ 24 | class DiscouragedFunctionsSniff extends ForbiddenFunctionsSniff 25 | { 26 | 27 | /** 28 | * A list of discouraged functions with their alternatives. 29 | * 30 | * The value is NULL if no alternative exists. IE, the 31 | * function should just not be used. 32 | * 33 | * @var array(string => string|null) 34 | */ 35 | public $forbiddenFunctions = array( 36 | 'print_r' => null, 37 | 'var_dump' => null, 38 | ); 39 | 40 | /** 41 | * Set error to false to show warnings. 42 | * 43 | * @var boolean 44 | */ 45 | public $error = false; 46 | 47 | }//end class 48 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Operators/BooleanOrSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Operators; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Boolean Or Sniff 18 | * 19 | * Check that the 'or' operator is the boolean version '||'. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class BooleanOrSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return array(T_LOGICAL_OR); 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param File $phpcsFile The current file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | if ($tokens[$stackPtr]['code'] === T_LOGICAL_OR) { 53 | $error = '"%s" is not allowed, use "||" instead'; 54 | $data = array($tokens[$stackPtr]['content']); 55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'LogicalOrNotAllowed', $data); 56 | if ($fix === true) { 57 | $phpcsFile->fixer->beginChangeset(); 58 | $phpcsFile->fixer->replaceToken($stackPtr, '||'); 59 | $phpcsFile->fixer->endChangeset(); 60 | } 61 | } 62 | 63 | }//end process() 64 | 65 | 66 | }//end class 67 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Operators/BooleanAndSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Operators; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Boolean And Sniff 18 | * 19 | * Check that the 'and' operator is the boolean version '&&'. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class BooleanAndSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return array(T_LOGICAL_AND); 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param File $phpcsFile The current file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | if ($tokens[$stackPtr]['code'] === T_LOGICAL_AND) { 53 | $error = '"%s" is not allowed, use "&&" instead'; 54 | $data = array($tokens[$stackPtr]['content']); 55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'LogicalAndNotAllowed', $data); 56 | if ($fix === true) { 57 | $phpcsFile->fixer->beginChangeset(); 58 | $phpcsFile->fixer->replaceToken($stackPtr, '&&'); 59 | $phpcsFile->fixer->endChangeset(); 60 | } 61 | } 62 | 63 | }//end process() 64 | 65 | 66 | }//end class 67 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Operators/IsIdenticalSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Operators; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Is Identical Sniff 18 | * 19 | * Check for is equal '==' operator, should use is identical '==='. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class IsIdenticalSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return array(T_IS_EQUAL); 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param File $phpcsFile The current file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | if ($tokens[$stackPtr]['code'] === T_IS_EQUAL) { 53 | $error = '"%s" is not allowed, use "===" instead'; 54 | $data = array($tokens[$stackPtr]['content']); 55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'IsEqualNotAllowed', $data); 56 | if ($fix === true) { 57 | $phpcsFile->fixer->beginChangeset(); 58 | $phpcsFile->fixer->replaceToken($stackPtr, '==='); 59 | $phpcsFile->fixer->endChangeset(); 60 | } 61 | } 62 | 63 | }//end process() 64 | 65 | 66 | }//end class 67 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Operators/IsNotIdenticalSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Operators; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Is Not Identical Sniff 18 | * 19 | * Check for is not equal '!=' operator, should use is not identical '!=='. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class IsNotIdenticalSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return array(T_IS_NOT_EQUAL); 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param File $phpcsFile The current file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | if ($tokens[$stackPtr]['code'] === T_IS_NOT_EQUAL) { 53 | $error = '"%s" is not allowed, use "!==" instead'; 54 | $data = array($tokens[$stackPtr]['content']); 55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'IsNotEqualNotAllowed', $data); 56 | if ($fix === true) { 57 | $phpcsFile->fixer->beginChangeset(); 58 | $phpcsFile->fixer->replaceToken($stackPtr, '!=='); 59 | $phpcsFile->fixer->endChangeset(); 60 | } 61 | } 62 | 63 | }//end process() 64 | 65 | 66 | }//end class 67 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Files/FilenameMatchesClassSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter\Sniffs\Files; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Filename Matches Class Sniff 18 | * 19 | * Checks that the filename matches the class name. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class FilenameMatchesClassSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return array( 35 | T_CLASS, 36 | T_INTERFACE, 37 | ); 38 | 39 | }//end register() 40 | 41 | 42 | /** 43 | * Processes this sniff, when one of its tokens is encountered. 44 | * 45 | * @param File $phpcsFile The file being scanned. 46 | * @param int $stackPtr The position of the current token in 47 | * the stack passed in $tokens. 48 | * 49 | * @return int 50 | */ 51 | public function process(File $phpcsFile, $stackPtr) 52 | { 53 | 54 | $fileName = basename($phpcsFile->getFilename()); 55 | 56 | $className = trim($phpcsFile->getDeclarationName($stackPtr)); 57 | 58 | if ($fileName !== $className.'.php') { 59 | $data = array( 60 | $fileName, 61 | $className.'.php', 62 | ); 63 | $error = 'Filename "%s" doesn\'t match the expected filename "%s"'; 64 | $phpcsFile->addError($error, 1, 'NotFound', $data); 65 | $phpcsFile->recordMetric(1, 'Filename matches class', 'no'); 66 | } else { 67 | $phpcsFile->recordMetric(1, 'Filename matches class', 'yes'); 68 | } 69 | 70 | // Ignore the rest of the file. 71 | return ($phpcsFile->numTokens + 1); 72 | 73 | }//end process() 74 | 75 | 76 | }//end class 77 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/WhiteSpace/BooleanNotSpaceAfterSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\WhiteSpace; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Boolean Not Space After Sniff 18 | * 19 | * Checks there is a space after '!'. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class BooleanNotSpaceAfterSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return array(T_BOOLEAN_NOT); 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param File $phpcsFile The current file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | $nextToken = $tokens[($stackPtr + 1)]; 53 | if (T_WHITESPACE !== $nextToken['code']) { 54 | $error = 'There must be a space after !'; 55 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'BooleanNotNoWhiteSpaceAfter'); 56 | 57 | if ($fix === true) { 58 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 59 | $phpcsFile->fixer->beginChangeset(); 60 | for ($i = ($nextContentPtr + 1); $i < $stackPtr; $i++) { 61 | $phpcsFile->fixer->replaceToken($i, ''); 62 | } 63 | 64 | $phpcsFile->fixer->addContent(($stackPtr), ' '); 65 | $phpcsFile->fixer->endChangeset(); 66 | $phpcsFile->recordMetric($stackPtr, 'Boolean not space after', 'yes'); 67 | }//end if 68 | }//end if 69 | 70 | }//end process() 71 | 72 | 73 | }//end class 74 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Files/OneClassPerFileSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Files; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * One Class Per File Sniff 18 | * 19 | * Checks that only one class is declared per file. Unless the file 20 | * is allowed multiple classes. 21 | * 22 | * @author Louis Linehan 23 | */ 24 | class OneClassPerFileSniff implements Sniff 25 | { 26 | 27 | /** 28 | * Files that are allowed multiple classes 29 | * 30 | * @var array 31 | */ 32 | public $filesAllowedMultiClass = array( 33 | 'Exception.php', 34 | 'Exceptions.php', 35 | 'CustomExceptions.php', 36 | 'Response.php', 37 | ); 38 | 39 | 40 | /** 41 | * Returns an array of tokens this test wants to listen for. 42 | * 43 | * @return array 44 | */ 45 | public function register() 46 | { 47 | return array(T_CLASS); 48 | 49 | }//end register() 50 | 51 | 52 | /** 53 | * Processes this sniff, when one of its tokens is encountered. 54 | * 55 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 56 | * @param int $stackPtr The position of the current token in 57 | * the stack passed in $tokens. 58 | * 59 | * @return void 60 | */ 61 | public function process(File $phpcsFile, $stackPtr) 62 | { 63 | $nextClass = $phpcsFile->findNext($this->register(), ($stackPtr + 1)); 64 | 65 | $fileName = basename($phpcsFile->getFilename()); 66 | 67 | if ($nextClass !== false) { 68 | if (in_array($fileName, $this->filesAllowedMultiClass) === false) { 69 | $error = 'Only one class is allowed in a file'; 70 | $phpcsFile->addError($error, $nextClass, 'MultipleFound'); 71 | } 72 | } 73 | 74 | }//end process() 75 | 76 | 77 | }//end class 78 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "7ab4a8599ae64217d846fedb6a29abda", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "squizlabs/php_codesniffer", 12 | "version": "3.0.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 16 | "reference": "f9eaf037edf22fdfccf04cb0ab57ebcb1e166219" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f9eaf037edf22fdfccf04cb0ab57ebcb1e166219", 21 | "reference": "f9eaf037edf22fdfccf04cb0ab57ebcb1e166219", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-simplexml": "*", 26 | "ext-tokenizer": "*", 27 | "ext-xmlwriter": "*", 28 | "php": ">=5.4.0" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "~4.0" 32 | }, 33 | "bin": [ 34 | "bin/phpcs", 35 | "bin/phpcbf" 36 | ], 37 | "type": "library", 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "3.x-dev" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "BSD-3-Clause" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Greg Sherwood", 50 | "role": "lead" 51 | } 52 | ], 53 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 54 | "homepage": "http://www.squizlabs.com/php-codesniffer", 55 | "keywords": [ 56 | "phpcs", 57 | "standards" 58 | ], 59 | "time": "2017-06-14T01:23:49+00:00" 60 | } 61 | ], 62 | "aliases": [], 63 | "minimum-stability": "stable", 64 | "stability-flags": [], 65 | "prefer-stable": false, 66 | "prefer-lowest": false, 67 | "platform": [], 68 | "platform-dev": [] 69 | } 70 | -------------------------------------------------------------------------------- /CodeIgniter4/Util/Common.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Util; 12 | 13 | use PHP_CodeSniffer\Util\Common as BaseCommon; 14 | 15 | /** 16 | * Common 17 | * 18 | * Extrends common functions. 19 | * 20 | * @author Louis Linehan 21 | */ 22 | class Common extends BaseCommon 23 | { 24 | 25 | /** 26 | * A list of all PHP magic methods. 27 | * 28 | * @var array 29 | */ 30 | public static $magicMethods = array( 31 | 'construct' => true, 32 | 'destruct' => true, 33 | 'call' => true, 34 | 'callstatic' => true, 35 | 'get' => true, 36 | 'set' => true, 37 | 'isset' => true, 38 | 'unset' => true, 39 | 'sleep' => true, 40 | 'wakeup' => true, 41 | 'tostring' => true, 42 | 'set_state' => true, 43 | 'clone' => true, 44 | 'invoke' => true, 45 | 'debuginfo' => true, 46 | ); 47 | 48 | /** 49 | * Allowed public methodNames 50 | * 51 | * @var array 52 | */ 53 | public static $publicMethodNames = array('_remap' => true); 54 | 55 | 56 | /** 57 | * Is lower snake case 58 | * 59 | * @param string $string The string to verify. 60 | * 61 | * @return boolean 62 | */ 63 | public static function isLowerSnakeCase($string) 64 | { 65 | if (strcmp($string, strtolower($string)) !== 0) { 66 | return false; 67 | } 68 | 69 | if (strpos($string, ' ') !== false) { 70 | return false; 71 | } 72 | 73 | return true; 74 | 75 | }//end isLowerSnakeCase() 76 | 77 | 78 | /** 79 | * Has an underscore prefix 80 | * 81 | * @param string $string The string to verify. 82 | * 83 | * @return boolean 84 | */ 85 | public static function hasUnderscorePrefix($string) 86 | { 87 | if (strpos($string, '_') !== 0) { 88 | return false; 89 | } 90 | 91 | return true; 92 | 93 | }//end hasUnderscorePrefix() 94 | 95 | 96 | }//end class 97 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/NamingConventions/ValidFunctionNameSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\NamingConventions; 12 | 13 | use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; 14 | use CodeIgniter4\Util\Common; 15 | use PHP_CodeSniffer\Files\File; 16 | 17 | /** 18 | * Valid Function Name Sniff 19 | * 20 | * @author Louis Linehan 21 | */ 22 | class ValidFunctionNameSniff extends AbstractScopeSniff 23 | { 24 | 25 | 26 | /** 27 | * Defines which token(s) in which scope(s) will be proceed. 28 | */ 29 | public function __construct() 30 | { 31 | parent::__construct(array(T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT), array(T_FUNCTION), true); 32 | 33 | }//end __construct() 34 | 35 | 36 | /** 37 | * Processes the tokens outside the scope. 38 | * 39 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed. 40 | * @param int $stackPtr The position where this token was 41 | * found. 42 | * 43 | * @return void 44 | */ 45 | protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) 46 | { 47 | $functionName = $phpcsFile->getDeclarationName($stackPtr); 48 | if ($functionName === null) { 49 | return; 50 | } 51 | 52 | // Is this a magic function. i.e., it is prefixed with "__"? 53 | if (preg_match('|^__[^_]|', $functionName) !== 0) { 54 | $magicPart = strtolower(substr($functionName, 2)); 55 | if (isset(Common::$magicMethods[$magicPart]) === false) { 56 | $errorData = array($functionName); 57 | $error = 'Function name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; 58 | $phpcsFile->addError($error, $stackPtr, 'FunctionDoubleUnderscore', $errorData); 59 | } 60 | 61 | return; 62 | } 63 | 64 | if (Common::isLowerSnakeCase($functionName) === false 65 | || $functionName !== strtolower($functionName) 66 | ) { 67 | $errorData = array($functionName); 68 | $error = 'Function "%s" must be snake_case'; 69 | $phpcsFile->addError($error, $stackPtr, 'FunctionNotSnakeCase', $errorData); 70 | } 71 | 72 | $warningLimit = 50; 73 | if (strlen($functionName) > $warningLimit) { 74 | $errorData = array( 75 | $functionName, 76 | $warningLimit, 77 | ); 78 | $warning = 'Function "%s" is over "%s" chars'; 79 | $phpcsFile->addWarning($warning, $stackPtr, 'FunctionNameIsLong', $errorData); 80 | } 81 | 82 | }//end processTokenOutsideScope() 83 | 84 | 85 | /** 86 | * Processes the tokens within the scope. 87 | * 88 | * @param File $phpcsFile The file being processed. 89 | * @param int $stackPtr The position where this token was 90 | * found. 91 | * @param int $currScope The position of the current scope. 92 | * 93 | * @return void 94 | */ 95 | protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) 96 | { 97 | 98 | }//end processTokenWithinScope() 99 | 100 | 101 | }//end class 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeIgniter4-Standard 2 | 3 | CodeIgniter 4 Standard for [PHP_CodeSniffer 3](https://github.com/squizlabs/PHP_CodeSniffer). 4 | 5 | Version 1.0.0-beta0007 6 | 7 | This is currently a work in progress. 8 | 9 | ## Install 10 | 11 | ### Composer install. 12 | 13 | As this is a beta version you will need to add `"minimum-stability": "dev"` to your 'composer.json' file. 14 | 15 | `cd /Path/To/MyProject` 16 | `composer require louisl/codeigniter4-standard:1.* --dev` 17 | 18 | Set the `phpcs standard path` and `phpcbf standard path` in your editor/plugin config to: 19 | 20 | `/Path/To/MyProject/vendor/louisl/codeigniter4-standard/CodeIgniter4/ruleset.xml` 21 | 22 | ### Download install 23 | 24 | Download [CodeIgniter4-Standard](https://github.com/louisl/CodeIgniter4-Standard/archive/v1.0.0-beta0007.zip). 25 | 26 | Set `standard ` paths to your local filesystem: 27 | 28 | `'/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 29 | 30 | ### Command line use 31 | 32 | #### Sniffing errors & warnings (reporting). 33 | 34 | Single file. 35 | 36 | `phpcs /Path/To/MyFile.php --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 37 | 38 | Directory (recursive). 39 | 40 | `phpcs /Path/To/MyProject --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 41 | 42 | #### Fixing fixable errors. 43 | 44 | Single file. 45 | 46 | `phpcbf /Path/To/MyFile.php --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 47 | 48 | Directory (recursive). 49 | 50 | `phpcbf /Path/To/MyProject --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 51 | 52 | ## Example editor configs 53 | 54 | ### SublimeText project config. 55 | 56 | Project > Edit Project 57 | 58 | Set it to your preference. 59 | 60 | ``` 61 | { 62 | "SublimeLinter": 63 | { 64 | "linters": 65 | { 66 | "phpcs": 67 | { 68 | "@disable": false, 69 | "cmd": "/Path/To/php_codesniffer/bin/phpcs", 70 | // Or if installed globally. "cmd": "phpcs", 71 | "standard": "/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml" 72 | } 73 | } 74 | }, 75 | "folders": 76 | [ 77 | { 78 | "path": "/Path/To/MyProject" 79 | } 80 | ], 81 | "settings": 82 | { 83 | "phpcs": 84 | { 85 | "extensions_to_execute": 86 | [ 87 | "php" 88 | ], 89 | "phpcs_executable_path": "/Path/To/php_codesniffer/bin/phpcs", 90 | // Or if installed globally. "phpcbf_executable_path": "phpcs", 91 | "phpcs_additional_args": 92 | { 93 | "--standard": "/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml", 94 | // Optional don't show warnings 95 | // "-n": "" 96 | }, 97 | "phpcbf_executable_path": "/Path/To/php_codesniffer/bin/phpcbf", 98 | // Or if installed globally. "phpcbf_executable_path": "phpcbf", 99 | "phpcbf_additional_args": 100 | { 101 | "--standard": "/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml", 102 | // Optional don't fix warnings (if they're fixable) 103 | // "-n": "" 104 | }, 105 | // Execute the sniffer on file save. (Using contextual menu instead) 106 | "phpcs_execute_on_save": false, 107 | // Show the error list after save. (Using sublime linter instead) 108 | "phpcs_show_errors_on_save": false, 109 | // Show the errors in the quick panel so you can then goto line. (Gets annoying) 110 | "phpcs_show_quick_panel": false, 111 | // Turn the debug output on/off. 112 | "show_debug": false 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | ## Credits 119 | 120 | Thanks to Greg Sherwood, Marc McIntyre, Andy Grunwald, Thomas Ernest and Erik Torsner, for providing open source code which helped me build this standard and a big thanks to [Squiz Labs](http://www.squizlabs.com) for creating [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). 121 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/NamingConventions/ValidMethodNameSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\NamingConventions; 12 | 13 | use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; 14 | use CodeIgniter4\Util\Common; 15 | use PHP_CodeSniffer\Files\File; 16 | 17 | /** 18 | * Valid Method Name Sniff 19 | * 20 | * Checks class methods are lowerCameCase. 21 | * Checks public methods are not prefixed with "_" except 22 | * methods defined in allowedPublicMethodNames. 23 | * Checks private and protected methods are prefixed with "_". 24 | * Checks functions are snake_case. 25 | * Warns if names are longer than 50 characters. 26 | * 27 | * @author Louis Linehan 28 | */ 29 | class ValidMethodNameSniff extends AbstractScopeSniff 30 | { 31 | 32 | 33 | /** 34 | * Defines which token(s) in which scope(s) will be proceed. 35 | */ 36 | public function __construct() 37 | { 38 | parent::__construct(array(T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT), array(T_FUNCTION), true); 39 | 40 | }//end __construct() 41 | 42 | 43 | /** 44 | * Processes a token within the scope that this test is listening to. 45 | * 46 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where the token was found. 47 | * @param int $stackPtr The position in the stack where 48 | * this token was found. 49 | * 50 | * @return void 51 | */ 52 | protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) 53 | { 54 | 55 | }//end processTokenOutsideScope() 56 | 57 | 58 | /** 59 | * Processes the tokens within the scope. 60 | * 61 | * @param File $phpcsFile The file being processed. 62 | * @param int $stackPtr The position where this token was 63 | * found. 64 | * @param int $currScope The position of the current scope. 65 | * 66 | * @return void 67 | */ 68 | protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) 69 | { 70 | $methodName = $phpcsFile->getDeclarationName($stackPtr); 71 | if ($methodName === null) { 72 | // Ignore closures. 73 | return; 74 | } 75 | 76 | $className = $phpcsFile->getDeclarationName($currScope); 77 | 78 | // Is this a magic method. i.e., is prefixed with "__"? 79 | if (preg_match('|^__[^_]|', $methodName) !== 0) { 80 | $magicPart = strtolower(substr($methodName, 2)); 81 | if (isset(Common::$magicMethods[$magicPart]) === false) { 82 | $errorData = array($className.'::'.$methodName); 83 | $error = 'Method name "%s" is invalid; only PHP magic methods should be prefixed with a double underscore'; 84 | $phpcsFile->addError($error, $stackPtr, 'MethodDoubleUnderscore', $errorData); 85 | } 86 | 87 | return; 88 | } 89 | 90 | // Get the method name without underscore prefix if it exists. 91 | if (strrpos($methodName, '_') === 0) { 92 | $namePart = substr($methodName, 1); 93 | } else { 94 | $namePart = $methodName; 95 | } 96 | 97 | // Naming check. 98 | if (Common::isCamelCaps($namePart, false, true, false) === false) { 99 | $errorData = array($methodName); 100 | $error = 'Method "%s" must be lowerCamelCase'; 101 | $phpcsFile->addError($error, $stackPtr, 'MethodNotLowerCamelCase', $errorData); 102 | } 103 | 104 | // Methods must not be prefixed with an underscore except those in publicMethodNames. 105 | if (strrpos($methodName, '_') === 0) { 106 | if (isset(Common::$publicMethodNames[$methodName]) === false) { 107 | $methodProps = $phpcsFile->getMethodProperties($stackPtr); 108 | $scope = $methodProps['scope']; 109 | $errorData = array($className.'::'.$methodName); 110 | $error = ucfirst($scope).' method "%s" must not be prefixed with an underscore'; 111 | $phpcsFile->addError($error, $stackPtr, 'MethodMustNotHaveUnderscore', $errorData); 112 | } 113 | } 114 | 115 | // Warn if method name is over 50 chars. 116 | $warningLimit = 50; 117 | if (strlen($methodName) > $warningLimit) { 118 | $errorData = array( 119 | $methodName, 120 | $warningLimit, 121 | ); 122 | 123 | $warning = 'Method "%s" is over "%s" chars'; 124 | $phpcsFile->addWarning($warning, $stackPtr, 'MethodNameIsLong', $errorData); 125 | } 126 | 127 | }//end processTokenWithinScope() 128 | 129 | 130 | }//end class 131 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/ControlStructures/ControlStructureSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\ControlStructures; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Control Structure Spacing Sniff 18 | * 19 | * Checks that control structures have the correct spacing around brackets. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class ControlStructureSpacingSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * How many spaces should follow the opening bracket. 29 | * 30 | * @var integer 31 | */ 32 | public $requiredSpacesAfterOpen = 0; 33 | 34 | /** 35 | * How many spaces should precede the closing bracket. 36 | * 37 | * @var integer 38 | */ 39 | public $requiredSpacesBeforeClose = 0; 40 | 41 | 42 | /** 43 | * Returns an array of tokens this test wants to listen for. 44 | * 45 | * @return array 46 | */ 47 | public function register() 48 | { 49 | return array( 50 | T_IF, 51 | T_WHILE, 52 | T_FOREACH, 53 | T_FOR, 54 | T_SWITCH, 55 | T_DO, 56 | T_ELSE, 57 | T_ELSEIF, 58 | T_TRY, 59 | T_CATCH, 60 | ); 61 | 62 | }//end register() 63 | 64 | 65 | /** 66 | * Processes this test, when one of its tokens is encountered. 67 | * 68 | * @param File $phpcsFile The file being scanned. 69 | * @param int $stackPtr The position of the current token 70 | * in the stack passed in $tokens. 71 | * 72 | * @return void 73 | */ 74 | public function process(File $phpcsFile, $stackPtr) 75 | { 76 | $this->requiredSpacesAfterOpen = (int) $this->requiredSpacesAfterOpen; 77 | $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose; 78 | $tokens = $phpcsFile->getTokens(); 79 | 80 | if (isset($tokens[$stackPtr]['parenthesis_opener']) === false 81 | || isset($tokens[$stackPtr]['parenthesis_closer']) === false 82 | ) { 83 | return; 84 | } 85 | 86 | $parenOpener = $tokens[$stackPtr]['parenthesis_opener']; 87 | $parenCloser = $tokens[$stackPtr]['parenthesis_closer']; 88 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($parenOpener + 1), null, true); 89 | 90 | $spaceAfterOpen = 0; 91 | if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) { 92 | if (strpos($tokens[($parenOpener + 1)]['content'], $phpcsFile->eolChar) !== false) { 93 | $spaceAfterOpen = 'newline'; 94 | } else { 95 | $spaceAfterOpen = strlen($tokens[($parenOpener + 1)]['content']); 96 | } 97 | } 98 | 99 | if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen) { 100 | $error = 'Expected %s spaces after opening bracket; %s found'; 101 | $data = array( 102 | $this->requiredSpacesAfterOpen, 103 | $spaceAfterOpen, 104 | ); 105 | $fix = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data); 106 | if ($fix === true) { 107 | $padding = str_repeat(' ', $this->requiredSpacesAfterOpen); 108 | if ($spaceAfterOpen === 0) { 109 | $phpcsFile->fixer->addContent($parenOpener, $padding); 110 | } else if ($spaceAfterOpen === 'newline') { 111 | $phpcsFile->fixer->replaceToken(($parenOpener + 1), ''); 112 | } else { 113 | $phpcsFile->fixer->replaceToken(($parenOpener + 1), $padding); 114 | } 115 | } 116 | } 117 | 118 | // Spaces before control structure close parenthesis. 119 | if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) { 120 | $spaceBeforeClose = 0; 121 | if ($tokens[($parenCloser - 1)]['code'] === T_WHITESPACE) { 122 | $spaceBeforeClose = strlen(ltrim($tokens[($parenCloser - 1)]['content'], $phpcsFile->eolChar)); 123 | } 124 | 125 | if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose) { 126 | $error = 'Expected %s spaces before closing bracket; %s found'; 127 | $data = array( 128 | $this->requiredSpacesBeforeClose, 129 | $spaceBeforeClose, 130 | ); 131 | $fix = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data); 132 | if ($fix === true) { 133 | $padding = str_repeat(' ', $this->requiredSpacesBeforeClose); 134 | if ($spaceBeforeClose === 0) { 135 | $phpcsFile->fixer->addContentBefore($parenCloser, $padding); 136 | } else { 137 | $phpcsFile->fixer->replaceToken(($parenCloser - 1), $padding); 138 | } 139 | } 140 | } 141 | }//end if 142 | 143 | }//end process() 144 | 145 | 146 | }//end class 147 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Commenting/ClassCommentSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Commenting; 12 | 13 | use CodeIgniter4\Sniffs\Commenting\FileCommentSniff; 14 | use PHP_CodeSniffer\Sniffs\Sniff; 15 | use PHP_CodeSniffer\Files\File; 16 | use PHP_CodeSniffer\Util\Tokens; 17 | 18 | /** 19 | * Class Comment Sniff 20 | * 21 | * Parses and verifies the doc comments for classes. 22 | * 23 | * @author Louis Linehan 24 | */ 25 | class ClassCommentSniff extends FileCommentSniff 26 | { 27 | 28 | /** 29 | * Tags in correct order and related info. 30 | * 31 | * @var array 32 | */ 33 | protected $tags = array( 34 | '@package' => array( 35 | 'required' => true, 36 | 'allow_multiple' => false, 37 | ), 38 | '@subpackage' => array( 39 | 'required' => false, 40 | 'allow_multiple' => false, 41 | ), 42 | '@category' => array( 43 | 'required' => false, 44 | 'allow_multiple' => false, 45 | ), 46 | '@author' => array( 47 | 'required' => false, 48 | 'allow_multiple' => true, 49 | ), 50 | '@copyright' => array( 51 | 'required' => false, 52 | 'allow_multiple' => true, 53 | ), 54 | '@license' => array( 55 | 'required' => false, 56 | 'allow_multiple' => false, 57 | ), 58 | '@link' => array( 59 | 'required' => false, 60 | 'allow_multiple' => true, 61 | ), 62 | '@since' => array( 63 | 'required' => false, 64 | 'allow_multiple' => false, 65 | ), 66 | '@version' => array( 67 | 'required' => false, 68 | 'allow_multiple' => false, 69 | ), 70 | '@see' => array( 71 | 'required' => false, 72 | 'allow_multiple' => true, 73 | ), 74 | '@deprecated' => array( 75 | 'required' => false, 76 | 'allow_multiple' => false, 77 | ), 78 | ); 79 | 80 | 81 | /** 82 | * Returns an array of tokens this test wants to listen for. 83 | * 84 | * @return array 85 | */ 86 | public function register() 87 | { 88 | return array( 89 | T_CLASS, 90 | T_INTERFACE, 91 | ); 92 | 93 | }//end register() 94 | 95 | 96 | /** 97 | * Processes this test, when one of its tokens is encountered. 98 | * 99 | * @param File $phpcsFile The file being scanned. 100 | * @param int $stackPtr The position of the current token 101 | * in the stack passed in $tokens. 102 | * 103 | * @return void 104 | */ 105 | public function process(File $phpcsFile, $stackPtr) 106 | { 107 | $this->currentFile = $phpcsFile; 108 | 109 | $tokens = $phpcsFile->getTokens(); 110 | $type = strtolower($tokens[$stackPtr]['content']); 111 | $errorData = array($type); 112 | 113 | $find = Tokens::$methodPrefixes; 114 | $find[] = T_WHITESPACE; 115 | 116 | $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true); 117 | if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG 118 | && $tokens[$commentEnd]['code'] !== T_COMMENT 119 | ) { 120 | $phpcsFile->addError('Missing class doc comment', $stackPtr, 'Missing'); 121 | $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'no'); 122 | return; 123 | } else { 124 | $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'yes'); 125 | } 126 | 127 | // Try and determine if this is a file comment instead of a class comment. 128 | // We assume that if this is the first comment after the open PHP tag, then 129 | // it is most likely a file comment instead of a class comment. 130 | if ($tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { 131 | $start = ($tokens[$commentEnd]['comment_opener'] - 1); 132 | } else { 133 | $start = $phpcsFile->findPrevious(T_COMMENT, ($commentEnd - 1), null, true); 134 | } 135 | 136 | $prev = $phpcsFile->findPrevious(T_WHITESPACE, $start, null, true); 137 | if ($tokens[$prev]['code'] === T_OPEN_TAG) { 138 | $prevOpen = $phpcsFile->findPrevious(T_OPEN_TAG, ($prev - 1)); 139 | if ($prevOpen === false) { 140 | // This is a comment directly after the first open tag, 141 | // so probably a file comment. 142 | $phpcsFile->addError('Missing class doc comment', $stackPtr, 'Missing'); 143 | return; 144 | } 145 | } 146 | 147 | if ($tokens[$commentEnd]['code'] === T_COMMENT) { 148 | $phpcsFile->addError('You must use "/**" style comments for a class comment', $stackPtr, 'WrongStyle'); 149 | return; 150 | } 151 | 152 | // Check each tag. 153 | $this->processTags($phpcsFile, $stackPtr, $tokens[$commentEnd]['comment_opener']); 154 | 155 | }//end process() 156 | 157 | 158 | }//end class 159 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/PHP/ForbiddenFunctionsSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\PHP; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Forbidden Functions Sniff 18 | * 19 | * Discourages the use of alias functions that are kept in PHP for compatibility 20 | * with older versions. Can be used to forbid the use of any function. 21 | * 22 | * @author Louis Linehan 23 | */ 24 | class ForbiddenFunctionsSniff implements Sniff 25 | { 26 | 27 | /** 28 | * A list of forbidden functions with their alternatives. 29 | * 30 | * The value is NULL if no alternative exists. IE, the 31 | * function should just not be used. 32 | * 33 | * @var array(string => string|null) 34 | */ 35 | public $forbiddenFunctions = array('sizeof' => 'count'); 36 | 37 | /** 38 | * A cache of forbidden function names, for faster lookups. 39 | * 40 | * @var array(string) 41 | */ 42 | protected $forbiddenFunctionNames = array(); 43 | 44 | /** 45 | * If true, forbidden functions will be considered regular expressions. 46 | * 47 | * @var boolean 48 | */ 49 | protected $patternMatch = false; 50 | 51 | /** 52 | * If true, an error will be thrown; otherwise a warning. 53 | * 54 | * @var boolean 55 | */ 56 | public $error = true; 57 | 58 | 59 | /** 60 | * Returns an array of tokens this test wants to listen for. 61 | * 62 | * @return array 63 | */ 64 | public function register() 65 | { 66 | // Everyone has had a chance to figure out what forbidden functions 67 | // they want to check for, so now we can cache out the list. 68 | $this->forbiddenFunctionNames = array_keys($this->forbiddenFunctions); 69 | 70 | if ($this->patternMatch === true) { 71 | foreach ($this->forbiddenFunctionNames as $i => $name) { 72 | $this->forbiddenFunctionNames[$i] = '/'.$name.'/i'; 73 | } 74 | 75 | return array(T_STRING); 76 | } 77 | 78 | // If we are not pattern matching, we need to work out what 79 | // tokens to listen for. 80 | $string = 'forbiddenFunctionNames as $name) { 82 | $string .= $name.'();'; 83 | } 84 | 85 | $register = array(); 86 | 87 | $tokens = token_get_all($string); 88 | array_shift($tokens); 89 | foreach ($tokens as $token) { 90 | if (is_array($token) === true) { 91 | $register[] = $token[0]; 92 | } 93 | } 94 | 95 | return array_unique($register); 96 | 97 | }//end register() 98 | 99 | 100 | /** 101 | * Processes this test, when one of its tokens is encountered. 102 | * 103 | * @param File $phpcsFile The file being scanned. 104 | * @param int $stackPtr The position of the current token in 105 | * the stack passed in $tokens. 106 | * 107 | * @return void 108 | */ 109 | public function process(File $phpcsFile, $stackPtr) 110 | { 111 | $tokens = $phpcsFile->getTokens(); 112 | 113 | $ignore = array( 114 | T_DOUBLE_COLON => true, 115 | T_FUNCTION => true, 116 | T_CONST => true, 117 | T_PUBLIC => true, 118 | T_PRIVATE => true, 119 | T_PROTECTED => true, 120 | T_AS => true, 121 | T_NEW => true, 122 | T_INSTEADOF => true, 123 | T_NS_SEPARATOR => true, 124 | T_IMPLEMENTS => true, 125 | ); 126 | 127 | $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 128 | 129 | // If function call is directly preceded by a NS_SEPARATOR it points to the 130 | // global namespace, so we should still catch it. 131 | if ($tokens[$prevToken]['code'] === T_NS_SEPARATOR) { 132 | $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($prevToken - 1), null, true); 133 | if ($tokens[$prevToken]['code'] === T_STRING) { 134 | // Not in the global namespace. 135 | return; 136 | } 137 | } 138 | 139 | if (isset($ignore[$tokens[$prevToken]['code']]) === true) { 140 | // Not a call to a PHP function. 141 | return; 142 | } 143 | 144 | $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 145 | if (isset($ignore[$tokens[$nextToken]['code']]) === true) { 146 | // Not a call to a PHP function. 147 | return; 148 | } 149 | 150 | if ($tokens[$stackPtr]['code'] === T_STRING && $tokens[$nextToken]['code'] !== T_OPEN_PARENTHESIS) { 151 | // Not a call to a PHP function. 152 | return; 153 | } 154 | 155 | $function = strtolower($tokens[$stackPtr]['content']); 156 | $pattern = null; 157 | 158 | if ($this->patternMatch === true) { 159 | $count = 0; 160 | $pattern = preg_replace( 161 | $this->forbiddenFunctionNames, 162 | $this->forbiddenFunctionNames, 163 | $function, 164 | 1, 165 | $count 166 | ); 167 | 168 | if ($count === 0) { 169 | return; 170 | } 171 | 172 | // Remove the pattern delimiters and modifier. 173 | $pattern = substr($pattern, 1, -2); 174 | } else { 175 | if (in_array($function, $this->forbiddenFunctionNames) === false) { 176 | return; 177 | } 178 | }//end if 179 | 180 | $this->addError($phpcsFile, $stackPtr, $function, $pattern); 181 | 182 | }//end process() 183 | 184 | 185 | /** 186 | * Generates the error or warning for this sniff. 187 | * 188 | * @param File $phpcsFile The file being scanned. 189 | * @param int $stackPtr The position of the forbidden function 190 | * in the token array. 191 | * @param string $function The name of the forbidden function. 192 | * @param string $pattern The pattern used for the match. 193 | * 194 | * @return void 195 | */ 196 | protected function addError($phpcsFile, $stackPtr, $function, $pattern=null) 197 | { 198 | $data = array($function); 199 | $error = 'The use of function %s() is '; 200 | if ($this->error === true) { 201 | $type = 'Found'; 202 | $error .= 'forbidden'; 203 | } else { 204 | $type = 'Discouraged'; 205 | $error .= 'discouraged'; 206 | } 207 | 208 | if ($pattern === null) { 209 | $pattern = $function; 210 | } 211 | 212 | if ($this->forbiddenFunctions[$pattern] !== null 213 | && $this->forbiddenFunctions[$pattern] !== 'null' 214 | ) { 215 | $type .= 'WithAlternative'; 216 | $data[] = $this->forbiddenFunctions[$pattern]; 217 | $error .= '; use %s() instead'; 218 | } 219 | 220 | if ($this->error === true) { 221 | $phpcsFile->addError($error, $stackPtr, $type, $data); 222 | } else { 223 | $phpcsFile->addWarning($error, $stackPtr, $type, $data); 224 | } 225 | 226 | }//end addError() 227 | 228 | 229 | }//end class 230 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | namespace CodeIgniter4\Sniffs\WhiteSpace; 11 | 12 | use PHP_CodeSniffer\Sniffs\Sniff; 13 | use PHP_CodeSniffer\Files\File; 14 | 15 | /** 16 | * Function Closing Brace Space Sniff 17 | * 18 | * Checks that there is one empty line before the closing brace of a function. 19 | * 20 | * @author Louis Linehan 21 | */ 22 | class FunctionClosingBraceSpaceSniff implements Sniff 23 | { 24 | 25 | /** 26 | * A list of tokenizers this sniff supports. 27 | * 28 | * @var array 29 | */ 30 | public $supportedTokenizers = array( 31 | 'PHP', 32 | 'JS', 33 | ); 34 | /** 35 | * Allowed lines before a closing function bracket. 36 | * 37 | * @var array 38 | */ 39 | public $allowedLines = 0; 40 | 41 | /** 42 | * Allowed spaces before a closing function bracket. 43 | * 44 | * @var array 45 | */ 46 | public $allowedNestedLines = 0; 47 | 48 | 49 | /** 50 | * Returns an array of tokens this test wants to listen for. 51 | * 52 | * @return array 53 | */ 54 | public function register() 55 | { 56 | return array( 57 | T_FUNCTION, 58 | T_CLOSURE, 59 | ); 60 | 61 | }//end register() 62 | 63 | 64 | /** 65 | * Processes this test, when one of its tokens is encountered. 66 | * 67 | * @param File $phpcsFile The file being scanned. 68 | * @param int $stackPtr The position of the current token 69 | * in the stack passed in $tokens. 70 | * 71 | * @return void 72 | */ 73 | public function process(File $phpcsFile, $stackPtr) 74 | { 75 | $tokens = $phpcsFile->getTokens(); 76 | 77 | if (isset($tokens[$stackPtr]['scope_closer']) === false) { 78 | // Probably an interface method. 79 | return; 80 | } 81 | 82 | $closeBrace = $tokens[$stackPtr]['scope_closer']; 83 | $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBrace - 1), null, true); 84 | 85 | // Special case for empty JS functions. 86 | if ($phpcsFile->tokenizerType === 'JS' && $prevContent === $tokens[$stackPtr]['scope_opener']) { 87 | // In this case, the opening and closing brace must be 88 | // right next to each other. 89 | if ($tokens[$stackPtr]['scope_closer'] !== ($tokens[$stackPtr]['scope_opener'] + 1)) { 90 | $error = 'The opening and closing braces of empty functions must be directly next to each other; e.g., function () {}'; 91 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBetween'); 92 | if ($fix === true) { 93 | $phpcsFile->fixer->beginChangeset(); 94 | for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $closeBrace; $i++) { 95 | $phpcsFile->fixer->replaceToken($i, ''); 96 | } 97 | 98 | $phpcsFile->fixer->endChangeset(); 99 | } 100 | } 101 | 102 | return; 103 | } 104 | 105 | $nestedFunction = false; 106 | if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true 107 | || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true 108 | || isset($tokens[$stackPtr]['nested_parenthesis']) === true 109 | ) { 110 | $nestedFunction = true; 111 | } 112 | 113 | $braceLine = $tokens[$closeBrace]['line']; 114 | $prevLine = $tokens[$prevContent]['line']; 115 | $found = ($braceLine - $prevLine - 1); 116 | 117 | $afterKeyword = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 118 | $beforeKeyword = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 119 | if ($nestedFunction === true) { 120 | if ($found < 0) { 121 | $error = 'Closing brace of nested function must be on a new line'; 122 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'ContentBeforeClose'); 123 | if ($fix === true) { 124 | $phpcsFile->fixer->addNewlineBefore($closeBrace); 125 | } 126 | } else if ($found > $this->allowedNestedLines) { 127 | $error = 'Expected %s blank lines before closing brace of nested function; %s found'; 128 | $data = array( 129 | $this->allowedNestedLines, 130 | $found, 131 | ); 132 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeNestedClose', $data); 133 | 134 | if ($fix === true) { 135 | $phpcsFile->fixer->beginChangeset(); 136 | $changeMade = false; 137 | for ($i = ($prevContent + 1); $i < $closeBrace; $i++) { 138 | // Try to maintain indentation. 139 | if ($tokens[$i]['line'] === ($braceLine - 1)) { 140 | break; 141 | } 142 | 143 | $phpcsFile->fixer->replaceToken($i, ''); 144 | $changeMade = true; 145 | } 146 | 147 | // Special case for when the last content contains the newline 148 | // token as well, like with a comment. 149 | if ($changeMade === false) { 150 | $phpcsFile->fixer->replaceToken(($prevContent + 1), ''); 151 | } 152 | 153 | $phpcsFile->fixer->endChangeset(); 154 | }//end if 155 | }//end if 156 | } else { 157 | if ($found !== (int) $this->allowedLines) { 158 | if ($found < 0) { 159 | $found = 0; 160 | } 161 | 162 | if ($this->allowedLines === 1) { 163 | $plural = ''; 164 | } else { 165 | $plural = 's'; 166 | } 167 | 168 | $error = 'Expected %s blank line%s before closing function brace; %s found'; 169 | $data = array( 170 | $this->allowedLines, 171 | $plural, 172 | $found, 173 | ); 174 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeClose', $data); 175 | 176 | if ($fix === true) { 177 | if ($found > 1) { 178 | $phpcsFile->fixer->beginChangeset(); 179 | for ($i = ($prevContent + 1); $i < ($closeBrace - 1); $i++) { 180 | $phpcsFile->fixer->replaceToken($i, ''); 181 | } 182 | 183 | $phpcsFile->fixer->replaceToken($i, $phpcsFile->eolChar); 184 | $phpcsFile->fixer->endChangeset(); 185 | } else { 186 | // Try to maintain indentation. 187 | if ($tokens[($closeBrace - 1)]['code'] === T_WHITESPACE) { 188 | $phpcsFile->fixer->addNewlineBefore($closeBrace - 1); 189 | } else { 190 | $phpcsFile->fixer->addNewlineBefore($closeBrace); 191 | } 192 | } 193 | } 194 | }//end if 195 | }//end if 196 | 197 | }//end process() 198 | 199 | 200 | }//end class 201 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/NamingConventions/ValidVariableNameSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\NamingConventions; 12 | 13 | use PHP_CodeSniffer\Sniffs\AbstractVariableSniff; 14 | use CodeIgniter4\Util\Common; 15 | use PHP_CodeSniffer\Files\File; 16 | 17 | class ValidVariableNameSniff extends AbstractVariableSniff 18 | { 19 | 20 | /** 21 | * Tokens to ignore so that we can find a DOUBLE_COLON. 22 | * 23 | * @var array 24 | */ 25 | private $ignore = array( 26 | T_WHITESPACE, 27 | T_COMMENT, 28 | ); 29 | 30 | 31 | /** 32 | * Processes this test, when one of its tokens is encountered. 33 | * 34 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 35 | * @param int $stackPtr The position of the current token in the 36 | * stack passed in $tokens. 37 | * 38 | * @return void 39 | */ 40 | protected function processVariable(File $phpcsFile, $stackPtr) 41 | { 42 | $tokens = $phpcsFile->getTokens(); 43 | $varName = ltrim($tokens[$stackPtr]['content'], '$'); 44 | 45 | $phpReservedVars = array( 46 | '_SERVER', 47 | '_GET', 48 | '_POST', 49 | '_REQUEST', 50 | '_SESSION', 51 | '_ENV', 52 | '_COOKIE', 53 | '_FILES', 54 | 'GLOBALS', 55 | 'http_response_header', 56 | 'HTTP_RAW_POST_DATA', 57 | 'php_errormsg', 58 | ); 59 | 60 | // If it's a php reserved var, then its ok. 61 | if (in_array($varName, $phpReservedVars) === true) { 62 | return; 63 | } 64 | 65 | $objOperator = $phpcsFile->findNext(array(T_WHITESPACE), ($stackPtr + 1), null, true); 66 | if ($tokens[$objOperator]['code'] === T_OBJECT_OPERATOR) { 67 | // Check to see if we are using a variable from an object. 68 | $var = $phpcsFile->findNext(array(T_WHITESPACE), ($objOperator + 1), null, true); 69 | if ($tokens[$var]['code'] === T_STRING) { 70 | $bracket = $phpcsFile->findNext(array(T_WHITESPACE), ($var + 1), null, true); 71 | if ($tokens[$bracket]['code'] !== T_OPEN_PARENTHESIS) { 72 | $objVarName = $tokens[$var]['content']; 73 | 74 | // There is no way for us to know if the var is public or 75 | // private, so we have to ignore a leading underscore if there is 76 | // one and just check the main part of the variable name. 77 | $originalVarName = $objVarName; 78 | if (substr($objVarName, 0, 1) === '_') { 79 | $objVarName = substr($objVarName, 1); 80 | } 81 | 82 | if (Common::isCamelCaps($objVarName, false, true, false) === false) { 83 | $error = 'Object Variable "%s" must be lowerCamelCase'; 84 | $data = array($originalVarName); 85 | $phpcsFile->addError($error, $var, 'ObjectVariableNotLowerCamelCase', $data); 86 | } 87 | }//end if 88 | }//end if 89 | }//end if 90 | 91 | // There is no way for us to know if the var is public or private, 92 | // so we have to ignore a leading underscore if there is one and just 93 | // check the main part of the variable name. 94 | $originalVarName = $varName; 95 | if (substr($varName, 0, 1) === '_') { 96 | $objOperator = $phpcsFile->findPrevious(array(T_WHITESPACE), ($stackPtr - 1), null, true); 97 | if ($tokens[$objOperator]['code'] === T_DOUBLE_COLON) { 98 | // The variable lives within a class, and is referenced like 99 | // this: MyClass::$_variable, so we don't know its scope. 100 | $inClass = true; 101 | } else { 102 | $inClass = $phpcsFile->hasCondition($stackPtr, array(T_CLASS, T_INTERFACE, T_TRAIT)); 103 | } 104 | 105 | if ($inClass === true) { 106 | $varName = substr($varName, 1); 107 | } 108 | } 109 | 110 | if (Common::isCamelCaps($varName, false, true, false) === false) { 111 | $error = 'Variable "%s" must be lowerCamelCase'; 112 | $data = array($originalVarName); 113 | $phpcsFile->addError($error, $stackPtr, 'VariableNotLowerCamelCase', $data); 114 | } 115 | 116 | }//end processVariable() 117 | 118 | 119 | /** 120 | * Processes class member variables. 121 | * 122 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 123 | * @param int $stackPtr The position of the current token in the 124 | * stack passed in $tokens. 125 | * 126 | * @return void 127 | */ 128 | protected function processMemberVar(File $phpcsFile, $stackPtr) 129 | { 130 | $tokens = $phpcsFile->getTokens(); 131 | 132 | $varName = ltrim($tokens[$stackPtr]['content'], '$'); 133 | $memberProps = $phpcsFile->getMemberProperties($stackPtr); 134 | if (empty($memberProps) === true) { 135 | // Couldn't get any info about this variable, which 136 | // generally means it is invalid or possibly has a parse 137 | // error. Any errors will be reported by the core, so 138 | // we can ignore it. 139 | return; 140 | } 141 | 142 | // Methods must not be prefixed with an underscore except those allowed in publicMethodNames. 143 | if (substr($varName, 0, 1) === '_') { 144 | if (isset(Common::$publicMethodNames[$varName]) === false) { 145 | $scope = $memberProps['scope']; 146 | $errorData = array($varName); 147 | $error = ucfirst($scope).' property "%s" must not be prefixed with an underscore'; 148 | $phpcsFile->addError($error, $stackPtr, 'PropertyMustNotHaveUnderscore', $errorData); 149 | return; 150 | } 151 | } 152 | 153 | if (Common::isCamelCaps($varName, false, true, false) === false) { 154 | $errorData = array($varName); 155 | $error = 'Property "%s" is not in valid camel caps format'; 156 | $phpcsFile->addError($error, $stackPtr, 'PropertyNotLowerCamelCase', $errorData); 157 | } 158 | 159 | }//end processMemberVar() 160 | 161 | 162 | /** 163 | * Processes the variable found within a double quoted string. 164 | * 165 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 166 | * @param int $stackPtr The position of the double quoted 167 | * string. 168 | * 169 | * @return void 170 | */ 171 | protected function processVariableInString(File $phpcsFile, $stackPtr) 172 | { 173 | $tokens = $phpcsFile->getTokens(); 174 | 175 | $phpReservedVars = array( 176 | '_SERVER', 177 | '_GET', 178 | '_POST', 179 | '_REQUEST', 180 | '_SESSION', 181 | '_ENV', 182 | '_COOKIE', 183 | '_FILES', 184 | 'GLOBALS', 185 | 'http_response_header', 186 | 'HTTP_RAW_POST_DATA', 187 | 'php_errormsg', 188 | ); 189 | 190 | if (preg_match_all('|[^\\\]\${?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)|', $tokens[$stackPtr]['content'], $matches) !== 0) { 191 | foreach ($matches[1] as $varName) { 192 | // If it's a php reserved var, then its ok. 193 | if (in_array($varName, $phpReservedVars) === true) { 194 | continue; 195 | } 196 | 197 | if (Common::isCamelCaps($varName, false, true, false) === false) { 198 | $error = 'Variable "%s" is not in valid camel caps format'; 199 | $data = array($varName); 200 | $phpcsFile->addError($error, $stackPtr, 'StringNotCamelCaps', $data); 201 | } 202 | } 203 | } 204 | 205 | }//end processVariableInString() 206 | 207 | 208 | }//end class 209 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/ControlStructures/AllmanControlSignatureSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\ControlStructures; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * Allman Control Signature Sniff 18 | * 19 | * Verifies that control statements conform to their coding standards. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class AllmanControlSignatureSniff implements Sniff 24 | { 25 | 26 | /** 27 | * A list of tokenizers this sniff supports. 28 | * 29 | * @var array 30 | */ 31 | public $supportedTokenizers = array( 32 | 'PHP', 33 | 'JS', 34 | ); 35 | 36 | 37 | /** 38 | * Returns an array of tokens this test wants to listen for. 39 | * 40 | * @return int[] 41 | */ 42 | public function register() 43 | { 44 | return array( 45 | T_TRY, 46 | T_CATCH, 47 | T_DO, 48 | T_WHILE, 49 | T_FOR, 50 | T_IF, 51 | T_FOREACH, 52 | T_ELSE, 53 | T_ELSEIF, 54 | ); 55 | 56 | }//end register() 57 | 58 | 59 | /** 60 | * Processes this test, when one of its tokens is encountered. 61 | * 62 | * @param File $phpcsFile The file being scanned. 63 | * @param int $stackPtr The position of the current token in the 64 | * stack passed in $tokens. 65 | * 66 | * @return void 67 | */ 68 | public function process(File $phpcsFile, $stackPtr) 69 | { 70 | $tokens = $phpcsFile->getTokens(); 71 | 72 | // Scope keyword should be on a new line. 73 | if (isset($tokens[$stackPtr]['scope_opener']) === true 74 | || $tokens[$stackPtr]['code'] === T_WHILE 75 | || $tokens[($stackPtr)]['code'] === T_ELSE 76 | ) { 77 | // If this is alternate syntax ":" instead of "{" then skip it. 78 | if (isset($tokens[$stackPtr]['scope_opener']) === true) { 79 | $openingBracePtr = $tokens[$stackPtr]['scope_opener']; 80 | if ($tokens[$openingBracePtr]['code'] === T_COLON) { 81 | return; 82 | } 83 | } 84 | 85 | $prevContentPtr = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 86 | 87 | // Skip if this T_IF is part of an else if. 88 | if ($tokens[($stackPtr)]['code'] !== T_IF 89 | && $tokens[($prevContentPtr)]['code'] !== T_ELSE 90 | ) { 91 | $keywordLine = $tokens[($stackPtr)]['line']; 92 | $prevContentLine = $tokens[($prevContentPtr)]['line']; 93 | 94 | if ($keywordLine === $prevContentLine) { 95 | $data = array($tokens[$stackPtr]['content']); 96 | $error = 'Scope keyword "%s" should be on a new line'; 97 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'ScopeKeywordOnNewLine', $data); 98 | if ($fix === true) { 99 | $phpcsFile->fixer->beginChangeset(); 100 | $phpcsFile->fixer->addNewlineBefore($stackPtr); 101 | $phpcsFile->fixer->endChangeset(); 102 | } 103 | } 104 | } 105 | }//end if 106 | 107 | // Expect 1 space after keyword, Skip T_ELSE, T_DO, T_TRY. 108 | if (isset($tokens[$stackPtr]['scope_opener']) === true 109 | && $tokens[($stackPtr)]['code'] !== T_ELSE 110 | && $tokens[($stackPtr)]['code'] !== T_DO 111 | && $tokens[($stackPtr)]['code'] !== T_TRY 112 | ) { 113 | $found = 1; 114 | if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { 115 | $found = 0; 116 | } else if ($tokens[($stackPtr + 1)]['content'] !== ' ') { 117 | if (strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) !== false) { 118 | $found = 'newline'; 119 | } else { 120 | $found = strlen($tokens[($stackPtr + 1)]['content']); 121 | } 122 | } 123 | 124 | if ($found !== 1) { 125 | $error = 'Expected 1 space after scope keyword "%s", found %s'; 126 | $data = array( 127 | strtoupper($tokens[$stackPtr]['content']), 128 | $found, 129 | ); 130 | 131 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterScopeKeyword', $data); 132 | if ($fix === true) { 133 | if ($found === 0) { 134 | $phpcsFile->fixer->addContent($stackPtr, ' '); 135 | } else { 136 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); 137 | } 138 | } 139 | } 140 | }//end if 141 | 142 | // Opening brace should be on a new line. 143 | if (isset($tokens[$stackPtr]['scope_opener']) === true) { 144 | $openingBracePtr = $tokens[$stackPtr]['scope_opener']; 145 | $braceLine = $tokens[$openingBracePtr]['line']; 146 | 147 | // If this is alternate syntax ":" instead of "{" then skip it. 148 | if (isset($tokens[$stackPtr]['scope_opener']) === true) { 149 | $openingBracePtr = $tokens[$stackPtr]['scope_opener']; 150 | if ($tokens[$openingBracePtr]['code'] === T_COLON) { 151 | return; 152 | } 153 | } 154 | 155 | if ($tokens[($stackPtr)]['code'] === T_ELSE 156 | || $tokens[($stackPtr)]['code'] === T_TRY 157 | || $tokens[($stackPtr)]['code'] === T_DO 158 | ) { 159 | $scopeLine = $tokens[$stackPtr]['line']; 160 | // Measure from the scope opener. 161 | $lineDifference = ($braceLine - $scopeLine); 162 | } else { 163 | $closerLine = $tokens[$tokens[$stackPtr]['parenthesis_closer']]['line']; 164 | // Measure from the scope closing parenthesis. 165 | $lineDifference = ($braceLine - $closerLine); 166 | } 167 | 168 | if ($lineDifference !== 1) { 169 | $data = array( 170 | $tokens[$openingBracePtr]['content'], 171 | $tokens[$stackPtr]['content'], 172 | ); 173 | if (isset($closerLine) === true) { 174 | $error = 'Opening brace "%s" should be on a new line after "%s (...)"'; 175 | } else { 176 | $error = 'Opening brace "%s" should be on a new line after the keyword "%s"'; 177 | } 178 | 179 | $fix = $phpcsFile->addFixableError($error, $openingBracePtr, 'ScopeOpeningBraceOnNewLine', $data); 180 | if ($fix === true) { 181 | $prevContentPtr = $phpcsFile->findPrevious(T_WHITESPACE, ($openingBracePtr - 1), null, true); 182 | $phpcsFile->fixer->beginChangeset(); 183 | for ($i = ($prevContentPtr + 1); $i < $openingBracePtr; $i++) { 184 | $phpcsFile->fixer->replaceToken($i, ''); 185 | } 186 | 187 | $phpcsFile->fixer->addNewlineBefore($openingBracePtr); 188 | $phpcsFile->fixer->endChangeset(); 189 | } 190 | }//end if 191 | }//end if 192 | 193 | // No empty lines after opening brace. 194 | if (isset($tokens[$stackPtr]['scope_opener']) === true) { 195 | $openerPtr = $tokens[$stackPtr]['scope_opener']; 196 | 197 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($openerPtr + 1), null, true); 198 | 199 | $braceLine = $tokens[$openerPtr]['line']; 200 | $nextContentLine = $tokens[$nextContentPtr]['line']; 201 | 202 | $lineDifference = ($nextContentLine - $braceLine); 203 | 204 | if ($lineDifference === 0 || $lineDifference > 1) { 205 | $data = array($tokens[$openerPtr]['content']); 206 | $error = 'Expected content on line after "%s"'; 207 | $fix = $phpcsFile->addFixableError($error, $openerPtr, 'NewlineAfterOpeningBrace', $data); 208 | if ($fix === true) { 209 | $phpcsFile->fixer->beginChangeset(); 210 | for ($i = ($openerPtr + 1); $i < $nextContentPtr; $i++) { 211 | $phpcsFile->fixer->replaceToken($i, ''); 212 | } 213 | 214 | $phpcsFile->fixer->addContent($openerPtr, $phpcsFile->eolChar); 215 | $phpcsFile->fixer->endChangeset(); 216 | } 217 | } 218 | }//end if 219 | 220 | // Closing brace should be on a new line. 221 | if (isset($tokens[$stackPtr]['scope_closer']) === true) { 222 | $closerPtr = $tokens[$stackPtr]['scope_closer']; 223 | 224 | $prevContentPtr = $phpcsFile->findPrevious(T_WHITESPACE, ($closerPtr - 1), null, true); 225 | 226 | $braceLine = $tokens[$closerPtr]['line']; 227 | $prevContentLine = $tokens[$prevContentPtr]['line']; 228 | 229 | $lineDifference = ($braceLine - $prevContentLine); 230 | 231 | if ($lineDifference !== 1) { 232 | $data = array($tokens[$closerPtr]['content']); 233 | $error = 'Closing brace "%s" should be on a new line'; 234 | $fix = $phpcsFile->addFixableError($error, $closerPtr, 'ClosingBraceOnNewLine', $data); 235 | if ($fix === true) { 236 | $phpcsFile->fixer->beginChangeset(); 237 | for ($i = ($prevContentPtr + 1); $i < $closerPtr; $i++) { 238 | $phpcsFile->fixer->replaceToken($i, ''); 239 | } 240 | 241 | $phpcsFile->fixer->addNewlineBefore($closerPtr); 242 | $phpcsFile->fixer->endChangeset(); 243 | } 244 | } 245 | }//end if 246 | 247 | }//end process() 248 | 249 | 250 | }//end class 251 | -------------------------------------------------------------------------------- /CodeIgniter4/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeIgniter 4 coding standard for PHP_CodeSniffer 4 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 0 34 | 35 | 38 | 39 | 42 | 43 | 47 | 48 | 0 49 | 50 | 53 | 54 | 57 | 58 | 61 | 62 | 65 | 66 | 69 | 70 | 74 | 75 | 78 | 79 | 82 | 83 | 0 84 | 85 | 88 | 89 | 92 | 93 | 96 | 97 | 100 | 101 | 104 | 105 | 108 | 109 | 115 | 116 | 123 | 124 | 132 | 133 | 134 | 135 | 138 | 139 | 140 | 141 | 142 | 143 | 146 | 147 | 150 | 151 | 152 | 153 | 154 | 155 | 158 | 159 | 162 | 163 | 166 | 167 | 170 | 171 | 174 | 175 | 178 | 179 | 182 | 183 | 186 | 187 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 200 | 201 | 204 | 205 | 208 | 209 | 210 | 211 | 212 | 213 | 221 | 224 | 225 | 228 | 229 | 230 | 231 | 232 | 233 | 236 | 237 | 240 | 241 | 244 | 245 | 248 | 249 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 261 | 262 | 263 | 264 | 265 | 266 | 269 | 270 | 273 | 274 | 277 | 278 | 281 | 282 | 285 | 286 | 290 | 291 | 297 | 298 | 301 | 302 | 305 | 306 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Commenting/FileCommentSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Commenting; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | 16 | /** 17 | * File Comment Sniff 18 | * 19 | * Check a doc comment exists. 20 | * Check the order of the tags. 21 | * Check the indentation of each tag. 22 | * Check required and optional tags and the format of their content. 23 | * 24 | * @author Louis Linehan 25 | */ 26 | class FileCommentSniff implements Sniff 27 | { 28 | 29 | /** 30 | * Tags in correct order and related info. 31 | * 32 | * @var array 33 | */ 34 | protected $tags = array( 35 | '@package' => array( 36 | 'required' => true, 37 | 'allow_multiple' => false, 38 | ), 39 | '@subpackage' => array( 40 | 'required' => false, 41 | 'allow_multiple' => false, 42 | ), 43 | '@category' => array( 44 | 'required' => false, 45 | 'allow_multiple' => false, 46 | ), 47 | '@author' => array( 48 | 'required' => true, 49 | 'allow_multiple' => true, 50 | ), 51 | '@copyright' => array( 52 | 'required' => false, 53 | 'allow_multiple' => true, 54 | ), 55 | '@license' => array( 56 | 'required' => true, 57 | 'allow_multiple' => false, 58 | ), 59 | '@link' => array( 60 | 'required' => false, 61 | 'allow_multiple' => true, 62 | ), 63 | '@since' => array( 64 | 'required' => false, 65 | 'allow_multiple' => false, 66 | ), 67 | '@version' => array( 68 | 'required' => false, 69 | 'allow_multiple' => false, 70 | ), 71 | '@see' => array( 72 | 'required' => false, 73 | 'allow_multiple' => true, 74 | ), 75 | '@deprecated' => array( 76 | 'required' => false, 77 | 'allow_multiple' => false, 78 | ), 79 | ); 80 | 81 | 82 | /** 83 | * Returns an array of tokens this test wants to listen for. 84 | * 85 | * @return array 86 | */ 87 | public function register() 88 | { 89 | return array(T_OPEN_TAG); 90 | 91 | }//end register() 92 | 93 | 94 | /** 95 | * Processes this test, when one of its tokens is encountered. 96 | * 97 | * @param File $phpcsFile The file being scanned. 98 | * @param int $stackPtr The position of the current token 99 | * in the stack passed in $tokens. 100 | * 101 | * @return int 102 | */ 103 | public function process(File $phpcsFile, $stackPtr) 104 | { 105 | $tokens = $phpcsFile->getTokens(); 106 | 107 | // Find the next non whitespace token. 108 | $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 109 | 110 | // Allow namespace at top of file. 111 | if ($tokens[$commentStart]['code'] === T_NAMESPACE) { 112 | $semicolon = $phpcsFile->findNext(T_SEMICOLON, ($commentStart + 1)); 113 | $commentStart = $phpcsFile->findNext(T_WHITESPACE, ($semicolon + 1), null, true); 114 | } 115 | 116 | // Ignore vim header. 117 | if ($tokens[$commentStart]['code'] === T_COMMENT) { 118 | if (strstr($tokens[$commentStart]['content'], 'vim:') !== false) { 119 | $commentStart = $phpcsFile->findNext( 120 | T_WHITESPACE, 121 | ($commentStart + 1), 122 | null, 123 | true 124 | ); 125 | } 126 | } 127 | 128 | $errorToken = ($stackPtr + 1); 129 | if (isset($tokens[$errorToken]) === false) { 130 | $errorToken--; 131 | } 132 | 133 | if ($tokens[$commentStart]['code'] === T_CLOSE_TAG) { 134 | // We are only interested if this is the first open tag. 135 | return ($phpcsFile->numTokens + 1); 136 | } else if ($tokens[$commentStart]['code'] === T_COMMENT) { 137 | $error = 'You must use "/**" style comments for a file comment'; 138 | $phpcsFile->addError($error, $errorToken, 'WrongStyle'); 139 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes'); 140 | return ($phpcsFile->numTokens + 1); 141 | } else if ($commentStart === false 142 | || $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG 143 | ) { 144 | $phpcsFile->addError('Missing file doc comment', $errorToken, 'Missing'); 145 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no'); 146 | return ($phpcsFile->numTokens + 1); 147 | } else { 148 | $phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes'); 149 | } 150 | 151 | // Check each tag. 152 | $this->processTags($phpcsFile, $stackPtr, $commentStart); 153 | 154 | // Ignore the rest of the file. 155 | return ($phpcsFile->numTokens + 1); 156 | 157 | }//end process() 158 | 159 | 160 | /** 161 | * Processes each required or optional tag. 162 | * 163 | * @param File $phpcsFile The file being scanned. 164 | * @param int $stackPtr The position of the current token 165 | * in the stack passed in $tokens. 166 | * @param int $commentStart Position in the stack where the comment started. 167 | * 168 | * @return void 169 | */ 170 | protected function processTags(File $phpcsFile, $stackPtr, $commentStart) 171 | { 172 | $tokens = $phpcsFile->getTokens(); 173 | 174 | if (get_class($this) === 'FileCommentSniff') { 175 | $docBlock = 'file'; 176 | } else { 177 | $docBlock = 'class'; 178 | } 179 | 180 | $commentEnd = $tokens[$commentStart]['comment_closer']; 181 | 182 | $foundTags = array(); 183 | $tagTokens = array(); 184 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) { 185 | $name = $tokens[$tag]['content']; 186 | if (isset($this->tags[$name]) === false) { 187 | continue; 188 | } 189 | 190 | if ($this->tags[$name]['allow_multiple'] === false && isset($tagTokens[$name]) === true) { 191 | $error = 'Only one %s tag is allowed in a %s comment'; 192 | $data = array( 193 | $name, 194 | $docBlock, 195 | ); 196 | $phpcsFile->addError($error, $tag, 'Duplicate'.ucfirst(substr($name, 1)).'Tag', $data); 197 | } 198 | 199 | $foundTags[] = $name; 200 | $tagTokens[$name][] = $tag; 201 | 202 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); 203 | if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { 204 | $error = 'Content missing for %s tag in %s comment'; 205 | $data = array( 206 | $name, 207 | $docBlock, 208 | ); 209 | $phpcsFile->addError($error, $tag, 'Empty'.ucfirst(substr($name, 1)).'Tag', $data); 210 | continue; 211 | } 212 | }//end foreach 213 | 214 | // Check if the tags are in the correct position. 215 | $pos = 0; 216 | foreach ($this->tags as $tag => $tagData) { 217 | if (isset($tagTokens[$tag]) === false) { 218 | if ($tagData['required'] === true) { 219 | $error = 'Missing %s tag in %s comment'; 220 | $data = array( 221 | $tag, 222 | $docBlock, 223 | ); 224 | $phpcsFile->addError($error, $commentEnd, 'Missing'.ucfirst(substr($tag, 1)).'Tag', $data); 225 | } 226 | 227 | continue; 228 | } else { 229 | $method = 'process'.substr($tag, 1); 230 | if (method_exists($this, $method) === true) { 231 | // Process each tag if a method is defined. 232 | call_user_func(array($this, $method), $phpcsFile, $tagTokens[$tag]); 233 | } 234 | } 235 | 236 | if (isset($foundTags[$pos]) === false) { 237 | break; 238 | } 239 | 240 | if ($foundTags[$pos] !== $tag) { 241 | $error = 'The tag in position %s should be the %s tag'; 242 | $data = array( 243 | ($pos + 1), 244 | $tag, 245 | ); 246 | $phpcsFile->addError($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1)).'TagOrder', $data); 247 | } 248 | 249 | // Account for multiple tags. 250 | $pos++; 251 | while (isset($foundTags[$pos]) === true && $foundTags[$pos] === $tag) { 252 | $pos++; 253 | } 254 | }//end foreach 255 | 256 | }//end processTags() 257 | 258 | 259 | /** 260 | * Process the package tag. 261 | * 262 | * @param File $phpcsFile The file being scanned. 263 | * @param array $tags The tokens for these tags. 264 | * 265 | * @return void 266 | */ 267 | protected function processPackage(File $phpcsFile, array $tags) 268 | { 269 | $tokens = $phpcsFile->getTokens(); 270 | foreach ($tags as $tag) { 271 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 272 | // No content. 273 | continue; 274 | } 275 | 276 | $content = $tokens[($tag + 2)]['content']; 277 | 278 | $isInvalidPackage = false; 279 | 280 | if (strpos($content, '/') !== false) { 281 | $parts = explode('/', $content); 282 | $newName = join('\\', $parts); 283 | 284 | $isInvalidPackage = true; 285 | } 286 | 287 | if ($isInvalidPackage === true) { 288 | $error = 'Package name "%s" is not valid. Use "%s" instead'; 289 | $validName = trim($newName, '_'); 290 | $data = array( 291 | $content, 292 | $validName, 293 | ); 294 | $phpcsFile->addWarning($error, $tag, 'InvalidPackage', $data); 295 | } 296 | }//end foreach 297 | 298 | }//end processPackage() 299 | 300 | 301 | /** 302 | * Process the subpackage tag. 303 | * 304 | * @param File $phpcsFile The file being scanned. 305 | * @param array $tags The tokens for these tags. 306 | * 307 | * @return void 308 | */ 309 | protected function processSubpackage(File $phpcsFile, array $tags) 310 | { 311 | $tokens = $phpcsFile->getTokens(); 312 | foreach ($tags as $tag) { 313 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 314 | // No content. 315 | continue; 316 | } 317 | 318 | $warning = 'The subpackage tag is considered deprecated. It is recommended to use the @package tag instead.'; 319 | 320 | $phpcsFile->addWarning($warning, $tag, 'SubpackageDepreciated'); 321 | }//end foreach 322 | 323 | }//end processSubpackage() 324 | 325 | 326 | /** 327 | * Process the category tag. 328 | * 329 | * @param File $phpcsFile The file being scanned. 330 | * @param array $tags The tokens for these tags. 331 | * 332 | * @return void 333 | */ 334 | protected function processCategory(File $phpcsFile, array $tags) 335 | { 336 | $tokens = $phpcsFile->getTokens(); 337 | foreach ($tags as $tag) { 338 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 339 | // No content. 340 | continue; 341 | } 342 | 343 | $warning = 'The category tag is considered deprecated. It is recommended to use the @package tag instead.'; 344 | 345 | $phpcsFile->addWarning($warning, $tag, 'CategoryDepreciated'); 346 | }//end foreach 347 | 348 | }//end processCategory() 349 | 350 | 351 | /** 352 | * Process the author tag(s) that this header comment has. 353 | * 354 | * @param File $phpcsFile The file being scanned. 355 | * @param array $tags The tokens for these tags. 356 | * 357 | * @return void 358 | */ 359 | protected function processAuthor(File $phpcsFile, array $tags) 360 | { 361 | $tokens = $phpcsFile->getTokens(); 362 | foreach ($tags as $tag) { 363 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 364 | // No content. 365 | continue; 366 | } 367 | 368 | $content = $tokens[($tag + 2)]['content']; 369 | 370 | // If it has an @ it's probably contains email address. 371 | if (strrpos($content, '@') !== false) { 372 | $local = '\da-zA-Z-_+'; 373 | // Dot character cannot be the first or last character in the local-part. 374 | $localMiddle = $local.'.\w'; 375 | if (preg_match('/^([^<]*)\s+<(['.$local.'](['.$localMiddle.']*['.$local.'])*@[\da-zA-Z][-.\w]*[\da-zA-Z]\.[a-zA-Z]{2,7})>$/', $content) === 0) { 376 | $error = '"@author" with email must be "Display Name "'; 377 | $phpcsFile->addError($error, $tag, 'InvalidAuthors'); 378 | } 379 | } 380 | } 381 | 382 | }//end processAuthor() 383 | 384 | 385 | /** 386 | * Process the copyright tags. 387 | * 388 | * @param File $phpcsFile The file being scanned. 389 | * @param array $tags The tokens for these tags. 390 | * 391 | * @return void 392 | */ 393 | protected function processCopyright(File $phpcsFile, array $tags) 394 | { 395 | $tokens = $phpcsFile->getTokens(); 396 | foreach ($tags as $tag) { 397 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 398 | // No content. 399 | continue; 400 | } 401 | 402 | $content = $tokens[($tag + 2)]['content']; 403 | $matches = array(); 404 | if (preg_match('/^([0-9]{4})((.{1})([0-9]{4}))? (.+)$/', $content, $matches) !== 0) { 405 | // Check earliest-latest year order. 406 | if ($matches[3] !== '') { 407 | if ($matches[3] !== '-') { 408 | $error = 'A hyphen must be used between the earliest and latest year'; 409 | $phpcsFile->addError($error, $tag, 'CopyrightHyphen'); 410 | } 411 | 412 | if ($matches[4] !== '' && $matches[4] < $matches[1]) { 413 | $error = "Invalid year span \"$matches[1]$matches[3]$matches[4]\" found; consider \"$matches[4]-$matches[1]\" instead"; 414 | $phpcsFile->addWarning($error, $tag, 'InvalidCopyright'); 415 | } 416 | } 417 | } else { 418 | $error = '"@copyright" must be "YYYY [- YYYY] Name of the copyright holder"'; 419 | $phpcsFile->addError($error, $tag, 'IncompleteCopyright'); 420 | } 421 | }//end foreach 422 | 423 | }//end processCopyright() 424 | 425 | 426 | /** 427 | * Process the license tag. 428 | * 429 | * @param File $phpcsFile The file being scanned. 430 | * @param array $tags The tokens for these tags. 431 | * 432 | * @return void 433 | */ 434 | protected function processLicense(File $phpcsFile, array $tags) 435 | { 436 | $tokens = $phpcsFile->getTokens(); 437 | foreach ($tags as $tag) { 438 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 439 | // No content. 440 | continue; 441 | } 442 | 443 | $content = $tokens[($tag + 2)]['content']; 444 | $matches = array(); 445 | preg_match('/^([^\s]+)\s+(.*)/', $content, $matches); 446 | 447 | if (count($matches) !== 3) { 448 | $error = '@license tag must contain a URL and a license name'; 449 | $phpcsFile->addError($error, $tag, 'IncompleteLicense'); 450 | } 451 | 452 | // Check the url is before the text part if it's included. 453 | $parts = explode(' ', $content); 454 | if ((count($parts) > 1)) { 455 | $matches = array(); 456 | preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $parts[0], $matches); 457 | if (count($matches) !== 1) { 458 | $error = 'The URL must come before the license name'; 459 | $phpcsFile->addError($error, $tag, 'LicenseURLNotFirst'); 460 | } 461 | } 462 | }//end foreach 463 | 464 | }//end processLicense() 465 | 466 | 467 | /** 468 | * Process the link tag. 469 | * 470 | * @param File $phpcsFile The file being scanned. 471 | * @param array $tags The tokens for these tags. 472 | * 473 | * @return void 474 | */ 475 | protected function processLink(File $phpcsFile, array $tags) 476 | { 477 | $tokens = $phpcsFile->getTokens(); 478 | foreach ($tags as $tag) { 479 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 480 | // No content. 481 | continue; 482 | } 483 | 484 | $content = $tokens[($tag + 2)]['content']; 485 | $matches = array(); 486 | 487 | // Check the url is before the text part if it's included. 488 | $parts = explode(' ', $content); 489 | if ((count($parts) > 1)) { 490 | $matches = array(); 491 | preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $parts[0], $matches); 492 | if (count($matches) !== 1) { 493 | $error = 'The URL must come before the description'; 494 | $phpcsFile->addError($error, $tag, 'LinkURLNotFirst'); 495 | } 496 | } 497 | }//end foreach 498 | 499 | }//end processLink() 500 | 501 | 502 | /** 503 | * Process the version tag. 504 | * 505 | * @param File $phpcsFile The file being scanned. 506 | * @param array $tags The tokens for these tags. 507 | * 508 | * @return void 509 | */ 510 | protected function processVersion(File $phpcsFile, array $tags) 511 | { 512 | $tokens = $phpcsFile->getTokens(); 513 | foreach ($tags as $tag) { 514 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 515 | // No content. 516 | continue; 517 | } 518 | 519 | $content = $tokens[($tag + 2)]['content']; 520 | // Split into parts if content has a space. 521 | $parts = explode(' ', $content); 522 | // Check if the first part contains a semantic version number. 523 | $matches = array(); 524 | preg_match('/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/', $parts[0], $matches); 525 | 526 | if (strstr($content, 'CVS:') === false 527 | && strstr($content, 'SVN:') === false 528 | && strstr($content, 'GIT:') === false 529 | && strstr($content, 'HG:') === false 530 | && count($matches) === 0 531 | ) { 532 | $error = 'It is recommended that @version is a semantic version number or is a VCS version vector "GIT: " or "SVN: " or "HG: " or "CVS: ".'; 533 | $phpcsFile->addWarning($error, $tag, 'InvalidVersion'); 534 | } 535 | }//end foreach 536 | 537 | }//end processVersion() 538 | 539 | 540 | }//end class 541 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Functions/FunctionDeclarationSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 Louis Linehan 8 | * @license https://github.com/louisl/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Sniffs\Functions; 12 | 13 | use PHP_CodeSniffer\Sniffs\Sniff; 14 | use PHP_CodeSniffer\Files\File; 15 | use PHP_CodeSniffer\Util\Tokens; 16 | use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceKernighanRitchieSniff; 17 | use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceBsdAllmanSniff; 18 | 19 | /** 20 | * Function Declaration Sniff 21 | * 22 | * Ensure single and multi-line function declarations are defined correctly. 23 | * 24 | * @author Louis Linehan 25 | */ 26 | class FunctionDeclarationSniff implements Sniff 27 | { 28 | 29 | /** 30 | * A list of tokenizers this sniff supports. 31 | * 32 | * @var array 33 | */ 34 | public $supportedTokenizers = array( 35 | 'PHP', 36 | 'JS', 37 | ); 38 | 39 | /** 40 | * The number of spaces code should be indented. 41 | * 42 | * @var integer 43 | */ 44 | public $indent = 4; 45 | 46 | 47 | /** 48 | * Returns an array of tokens this test wants to listen for. 49 | * 50 | * @return array 51 | */ 52 | public function register() 53 | { 54 | return array( 55 | T_FUNCTION, 56 | T_CLOSURE, 57 | ); 58 | 59 | }//end register() 60 | 61 | 62 | /** 63 | * Processes this test, when one of its tokens is encountered. 64 | * 65 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 66 | * @param int $stackPtr The position of the current token 67 | * in the stack passed in $tokens. 68 | * 69 | * @return void 70 | */ 71 | public function process(File $phpcsFile, $stackPtr) 72 | { 73 | $tokens = $phpcsFile->getTokens(); 74 | 75 | if (isset($tokens[$stackPtr]['parenthesis_opener']) === false 76 | || isset($tokens[$stackPtr]['parenthesis_closer']) === false 77 | || $tokens[$stackPtr]['parenthesis_opener'] === null 78 | || $tokens[$stackPtr]['parenthesis_closer'] === null 79 | ) { 80 | return; 81 | } 82 | 83 | $openBracket = $tokens[$stackPtr]['parenthesis_opener']; 84 | $closeBracket = $tokens[$stackPtr]['parenthesis_closer']; 85 | 86 | if (strtolower($tokens[$stackPtr]['content']) === 'function') { 87 | // Must be one space after the FUNCTION keyword. 88 | if ($tokens[($stackPtr + 1)]['content'] === $phpcsFile->eolChar) { 89 | $spaces = 'newline'; 90 | } else if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { 91 | $spaces = strlen($tokens[($stackPtr + 1)]['content']); 92 | } else { 93 | $spaces = 0; 94 | } 95 | 96 | if ($spaces !== 1) { 97 | $error = 'Expected 1 space after FUNCTION keyword; %s found'; 98 | $data = array($spaces); 99 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterFunction', $data); 100 | if ($fix === true) { 101 | if ($spaces === 0) { 102 | $phpcsFile->fixer->addContent($stackPtr, ' '); 103 | } else { 104 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); 105 | } 106 | } 107 | } 108 | }//end if 109 | 110 | // Must be one space before the opening parenthesis. For closures, this is 111 | // enforced by the first check because there is no content between the keywords 112 | // and the opening parenthesis. 113 | if ($tokens[$stackPtr]['code'] === T_FUNCTION) { 114 | if ($tokens[($openBracket - 1)]['content'] === $phpcsFile->eolChar) { 115 | $spaces = 'newline'; 116 | } else if ($tokens[($openBracket - 1)]['code'] === T_WHITESPACE) { 117 | $spaces = strlen($tokens[($openBracket - 1)]['content']); 118 | } else { 119 | $spaces = 0; 120 | } 121 | 122 | if ($spaces !== 0) { 123 | $error = 'Expected 0 spaces before opening parenthesis; %s found'; 124 | $data = array($spaces); 125 | $fix = $phpcsFile->addFixableError($error, $openBracket, 'SpaceBeforeOpenParenthesis', $data); 126 | if ($fix === true) { 127 | $phpcsFile->fixer->replaceToken(($openBracket - 1), ''); 128 | } 129 | } 130 | }//end if 131 | 132 | // Must be one space before and after USE keyword for closures. 133 | if ($tokens[$stackPtr]['code'] === T_CLOSURE) { 134 | $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']); 135 | if ($use !== false) { 136 | if ($tokens[($use + 1)]['code'] !== T_WHITESPACE) { 137 | $length = 0; 138 | } else if ($tokens[($use + 1)]['content'] === "\t") { 139 | $length = '\t'; 140 | } else { 141 | $length = strlen($tokens[($use + 1)]['content']); 142 | } 143 | 144 | if ($length !== 1) { 145 | $error = 'Expected 1 space after USE keyword; found %s'; 146 | $data = array($length); 147 | $fix = $phpcsFile->addFixableError($error, $use, 'SpaceAfterUse', $data); 148 | if ($fix === true) { 149 | if ($length === 0) { 150 | $phpcsFile->fixer->addContent($use, ' '); 151 | } else { 152 | $phpcsFile->fixer->replaceToken(($use + 1), ' '); 153 | } 154 | } 155 | } 156 | 157 | if ($tokens[($use - 1)]['code'] !== T_WHITESPACE) { 158 | $length = 0; 159 | } else if ($tokens[($use - 1)]['content'] === "\t") { 160 | $length = '\t'; 161 | } else { 162 | $length = strlen($tokens[($use - 1)]['content']); 163 | } 164 | 165 | if ($length !== 1) { 166 | $error = 'Expected 1 space before USE keyword; found %s'; 167 | $data = array($length); 168 | $fix = $phpcsFile->addFixableError($error, $use, 'SpaceBeforeUse', $data); 169 | if ($fix === true) { 170 | if ($length === 0) { 171 | $phpcsFile->fixer->addContentBefore($use, ' '); 172 | } else { 173 | $phpcsFile->fixer->replaceToken(($use - 1), ' '); 174 | } 175 | } 176 | } 177 | }//end if 178 | }//end if 179 | 180 | if ($this->isMultiLineDeclaration($phpcsFile, $stackPtr, $openBracket, $tokens) === true) { 181 | $this->processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens); 182 | } else { 183 | $this->processSingleLineDeclaration($phpcsFile, $stackPtr, $tokens); 184 | } 185 | 186 | }//end process() 187 | 188 | 189 | /** 190 | * Determine if this is a multi-line function declaration. 191 | * 192 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 193 | * @param int $stackPtr The position of the current token 194 | * in the stack passed in $tokens. 195 | * @param int $openBracket The position of the opening bracket 196 | * in the stack passed in $tokens. 197 | * @param array $tokens The stack of tokens that make up 198 | * the file. 199 | * 200 | * @return void 201 | */ 202 | public function isMultiLineDeclaration($phpcsFile, $stackPtr, $openBracket, $tokens) 203 | { 204 | $bracketsToCheck = array($stackPtr => $openBracket); 205 | 206 | // Closures may use the USE keyword and so be multi-line in this way. 207 | if ($tokens[$stackPtr]['code'] === T_CLOSURE) { 208 | $use = $phpcsFile->findNext(T_USE, ($tokens[$openBracket]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']); 209 | if ($use !== false) { 210 | $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1)); 211 | if ($open !== false) { 212 | $bracketsToCheck[$use] = $open; 213 | } 214 | } 215 | } 216 | 217 | foreach ($bracketsToCheck as $stackPtr => $openBracket) { 218 | // If the first argument is on a new line, this is a multi-line 219 | // function declaration, even if there is only one argument. 220 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($openBracket + 1), null, true); 221 | if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { 222 | return true; 223 | } 224 | 225 | $closeBracket = $tokens[$openBracket]['parenthesis_closer']; 226 | 227 | $end = $phpcsFile->findEndOfStatement($openBracket + 1); 228 | while ($tokens[$end]['code'] === T_COMMA) { 229 | // If the next bit of code is not on the same line, this is a 230 | // multi-line function declaration. 231 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), $closeBracket, true); 232 | if ($next === false) { 233 | continue(2); 234 | } 235 | 236 | if ($tokens[$next]['line'] !== $tokens[$end]['line']) { 237 | return true; 238 | } 239 | 240 | $end = $phpcsFile->findEndOfStatement($next); 241 | } 242 | 243 | // We've reached the last argument, so see if the next content 244 | // (should be the close bracket) is also on the same line. 245 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), $closeBracket, true); 246 | if ($next !== false && $tokens[$next]['line'] !== $tokens[$end]['line']) { 247 | return true; 248 | } 249 | }//end foreach 250 | 251 | return false; 252 | 253 | }//end isMultiLineDeclaration() 254 | 255 | 256 | /** 257 | * Processes single-line declarations. 258 | * 259 | * Just uses the Generic BSD-Allman brace sniff. 260 | * 261 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 262 | * @param int $stackPtr The position of the current token 263 | * in the stack passed in $tokens. 264 | * @param array $tokens The stack of tokens that make up 265 | * the file. 266 | * 267 | * @return void 268 | */ 269 | public function processSingleLineDeclaration($phpcsFile, $stackPtr, $tokens) 270 | { 271 | if ($tokens[$stackPtr]['code'] === T_CLOSURE) { 272 | $sniff = new OpeningFunctionBraceKernighanRitchieSniff(); 273 | } else { 274 | $sniff = new OpeningFunctionBraceBsdAllmanSniff(); 275 | } 276 | 277 | $sniff->checkClosures = true; 278 | $sniff->process($phpcsFile, $stackPtr); 279 | 280 | }//end processSingleLineDeclaration() 281 | 282 | 283 | /** 284 | * Processes multi-line declarations. 285 | * 286 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 287 | * @param int $stackPtr The position of the current token 288 | * in the stack passed in $tokens. 289 | * @param array $tokens The stack of tokens that make up 290 | * the file. 291 | * 292 | * @return void 293 | */ 294 | public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) 295 | { 296 | // We need to work out how far indented the function 297 | // declaration itself is, so we can work out how far to 298 | // indent parameters. 299 | $functionIndent = 0; 300 | for ($i = ($stackPtr - 1); $i >= 0; $i--) { 301 | if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) { 302 | $i++; 303 | break; 304 | } 305 | } 306 | 307 | if ($tokens[$i]['code'] === T_WHITESPACE) { 308 | $functionIndent = strlen($tokens[$i]['content']); 309 | } 310 | 311 | // The closing parenthesis must be on a new line, even 312 | // when checking abstract function definitions. 313 | $closeBracket = $tokens[$stackPtr]['parenthesis_closer']; 314 | $prev = $phpcsFile->findPrevious( 315 | T_WHITESPACE, 316 | ($closeBracket - 1), 317 | null, 318 | true 319 | ); 320 | 321 | if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) { 322 | if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { 323 | $error = 'The closing parenthesis of a multi-line function declaration must be on a new line'; 324 | $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine'); 325 | if ($fix === true) { 326 | $phpcsFile->fixer->addNewlineBefore($closeBracket); 327 | } 328 | } 329 | } 330 | 331 | // If this is a closure and is using a USE statement, the closing 332 | // parenthesis we need to look at from now on is the closing parenthesis 333 | // of the USE statement. 334 | if ($tokens[$stackPtr]['code'] === T_CLOSURE) { 335 | $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']); 336 | if ($use !== false) { 337 | $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1)); 338 | $closeBracket = $tokens[$open]['parenthesis_closer']; 339 | 340 | $prev = $phpcsFile->findPrevious( 341 | T_WHITESPACE, 342 | ($closeBracket - 1), 343 | null, 344 | true 345 | ); 346 | 347 | if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) { 348 | if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { 349 | $error = 'The closing parenthesis of a multi-line use declaration must be on a new line'; 350 | $fix = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine'); 351 | if ($fix === true) { 352 | $phpcsFile->fixer->addNewlineBefore($closeBracket); 353 | } 354 | } 355 | } 356 | }//end if 357 | }//end if 358 | 359 | // Each line between the parenthesis should be indented 4 spaces. 360 | $openBracket = $tokens[$stackPtr]['parenthesis_opener']; 361 | $lastLine = $tokens[$openBracket]['line']; 362 | for ($i = ($openBracket + 1); $i < $closeBracket; $i++) { 363 | if ($tokens[$i]['line'] !== $lastLine) { 364 | if ($i === $tokens[$stackPtr]['parenthesis_closer'] 365 | || ($tokens[$i]['code'] === T_WHITESPACE 366 | && (($i + 1) === $closeBracket 367 | || ($i + 1) === $tokens[$stackPtr]['parenthesis_closer'])) 368 | ) { 369 | // Closing braces need to be indented to the same level 370 | // as the function. 371 | $expectedIndent = $functionIndent; 372 | } else { 373 | $expectedIndent = ($functionIndent + $this->indent); 374 | } 375 | 376 | // We changed lines, so this should be a whitespace indent token. 377 | if ($tokens[$i]['code'] !== T_WHITESPACE) { 378 | $foundIndent = 0; 379 | } else if ($tokens[$i]['line'] !== $tokens[($i + 1)]['line']) { 380 | // This is an empty line, so don't check the indent. 381 | $foundIndent = $expectedIndent; 382 | 383 | $error = 'Blank lines are not allowed in a multi-line function declaration'; 384 | $fix = $phpcsFile->addFixableError($error, $i, 'EmptyLine'); 385 | if ($fix === true) { 386 | $phpcsFile->fixer->replaceToken($i, ''); 387 | } 388 | } else { 389 | $foundIndent = strlen($tokens[$i]['content']); 390 | } 391 | 392 | if ($expectedIndent !== $foundIndent) { 393 | $error = 'Multi-line function declaration not indented correctly; expected %s spaces but found %s'; 394 | $data = array( 395 | $expectedIndent, 396 | $foundIndent, 397 | ); 398 | 399 | $fix = $phpcsFile->addFixableError($error, $i, 'Indent', $data); 400 | if ($fix === true) { 401 | $spaces = str_repeat(' ', $expectedIndent); 402 | if ($foundIndent === 0) { 403 | $phpcsFile->fixer->addContentBefore($i, $spaces); 404 | } else { 405 | $phpcsFile->fixer->replaceToken($i, $spaces); 406 | } 407 | } 408 | } 409 | 410 | $lastLine = $tokens[$i]['line']; 411 | }//end if 412 | 413 | if ($tokens[$i]['code'] === T_ARRAY || $tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { 414 | // Skip arrays as they have their own indentation rules. 415 | if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { 416 | $i = $tokens[$i]['bracket_closer']; 417 | } else { 418 | $i = $tokens[$i]['parenthesis_closer']; 419 | } 420 | 421 | $lastLine = $tokens[$i]['line']; 422 | continue; 423 | } 424 | }//end for 425 | 426 | if (isset($tokens[$stackPtr]['scope_opener']) === false) { 427 | return; 428 | } 429 | 430 | $openBracket = $tokens[$stackPtr]['parenthesis_opener']; 431 | $this->processBracket($phpcsFile, $openBracket, $tokens, 'function'); 432 | 433 | if ($tokens[$stackPtr]['code'] !== T_CLOSURE) { 434 | return; 435 | } 436 | 437 | $use = $phpcsFile->findNext(T_USE, ($tokens[$stackPtr]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']); 438 | if ($use === false) { 439 | return; 440 | } 441 | 442 | $openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null); 443 | $this->processBracket($phpcsFile, $openBracket, $tokens, 'use'); 444 | 445 | // Also check spacing. 446 | if ($tokens[($use - 1)]['code'] === T_WHITESPACE) { 447 | $gap = strlen($tokens[($use - 1)]['content']); 448 | } else { 449 | $gap = 0; 450 | } 451 | 452 | }//end processMultiLineDeclaration() 453 | 454 | 455 | /** 456 | * Processes the contents of a single set of brackets. 457 | * 458 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 459 | * @param int $openBracket The position of the open bracket 460 | * in the stack passed in $tokens. 461 | * @param array $tokens The stack of tokens that make up 462 | * the file. 463 | * @param string $type The type of the token the brackets 464 | * belong to (function or use). 465 | * 466 | * @return void 467 | */ 468 | public function processBracket($phpcsFile, $openBracket, $tokens, $type='function') 469 | { 470 | $errorPrefix = ''; 471 | if ($type === 'use') { 472 | $errorPrefix = 'Use'; 473 | } 474 | 475 | $closeBracket = $tokens[$openBracket]['parenthesis_closer']; 476 | 477 | // The open bracket should be the last thing on the line. 478 | if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) { 479 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($openBracket + 1), null, true); 480 | if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) { 481 | $error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket'; 482 | $fix = $phpcsFile->addFixableError($error, $next, $errorPrefix.'FirstParamSpacing'); 483 | if ($fix === true) { 484 | $phpcsFile->fixer->addNewline($openBracket); 485 | } 486 | } 487 | } 488 | 489 | // Each line between the brackets should contain a single parameter. 490 | $lastComma = null; 491 | for ($i = ($openBracket + 1); $i < $closeBracket; $i++) { 492 | // Skip brackets, like arrays, as they can contain commas. 493 | if (isset($tokens[$i]['bracket_opener']) === true) { 494 | $i = $tokens[$i]['bracket_closer']; 495 | continue; 496 | } 497 | 498 | if (isset($tokens[$i]['parenthesis_opener']) === true) { 499 | $i = $tokens[$i]['parenthesis_closer']; 500 | continue; 501 | } 502 | 503 | if ($tokens[$i]['code'] !== T_COMMA) { 504 | continue; 505 | } 506 | 507 | $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); 508 | if ($tokens[$next]['line'] === $tokens[$i]['line']) { 509 | $error = 'Multi-line '.$type.' declarations must define one parameter per line'; 510 | $fix = $phpcsFile->addFixableError($error, $next, $errorPrefix.'OneParamPerLine'); 511 | if ($fix === true) { 512 | $phpcsFile->fixer->addNewline($i); 513 | } 514 | } 515 | }//end for 516 | 517 | }//end processBracket() 518 | 519 | 520 | }//end class 521 | --------------------------------------------------------------------------------