├── .gitignore ├── CodeIgniter4 ├── Tests │ ├── WhiteSpace │ │ ├── BooleanNotSpaceAfterUnitTest.inc │ │ ├── BooleanNotSpaceAfterUnitTest.inc.fixed │ │ ├── DisallowTabsInAlignmentUnitTest.inc │ │ ├── DisallowTabsInAlignmentUnitTest.inc.fixed │ │ ├── VerticalEmptyLinesUnitTest.inc.fixed │ │ ├── VerticalEmptyLinesUnitTest.inc │ │ ├── BooleanNotSpaceAfterUnitTest.php │ │ ├── DisallowTabsInAlignmentUnitTest.php │ │ └── VerticalEmptyLinesUnitTest.php │ ├── NamingConventions │ │ ├── ValidVariableNameUnitTest.inc │ │ ├── ValidMethodNameUnitTest.inc │ │ ├── ValidVariableNameUnitTest.php │ │ └── ValidMethodNameUnitTest.php │ ├── Operators │ │ ├── BooleanOrUnitTest.inc │ │ ├── BooleanOrUnitTest.inc.fixed │ │ ├── BooleanAndUnitTest.inc │ │ ├── BooleanAndUnitTest.inc.fixed │ │ ├── BooleanOrUnitTest.php │ │ └── BooleanAndUnitTest.php │ ├── ControlStructures │ │ ├── AllmanControlSignatureUnitTest.php │ │ ├── AllmanControlSignatureUnitTest.inc │ │ └── AllmanControlSignatureUnitTest.inc.fixed │ └── Arrays │ │ ├── ArrayDeclarationUnitTest.php │ │ ├── ArrayDeclarationUnitTest.inc │ │ └── ArrayDeclarationUnitTest.inc.fixed ├── Sniffs │ ├── PHP │ │ ├── DiscouragedFunctionsSniff.php │ │ └── ForbiddenFunctionsSniff.php │ ├── Operators │ │ ├── BooleanOrSniff.php │ │ ├── BooleanAndSniff.php │ │ ├── IsIdenticalSniff.php │ │ └── IsNotIdenticalSniff.php │ ├── Files │ │ ├── OneClassPerFileSniff.php │ │ ├── FilenameMatchesClassSniff.php │ │ └── HelperFileSniff.php │ ├── WhiteSpace │ │ ├── BooleanNotSpaceAfterSniff.php │ │ ├── VerticalEmptyLinesSniff.php │ │ ├── DisallowTabsInAlignmentSniff.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 ├── CONTRIBUTING.md ├── phpunit.xml.dist ├── composer.json ├── LICENSE ├── .travis.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | composer.lock 3 | /vendor/ 4 | /build 5 | phpunit.xml 6 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/WhiteSpace/BooleanNotSpaceAfterUnitTest.inc: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/Operators/BooleanOrUnitTest.inc: -------------------------------------------------------------------------------- 1 | 201, 10 | ]; 11 | 12 | $a = 'a'; 13 | $array = 'array'; 14 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/Operators/BooleanAndUnitTest.inc: -------------------------------------------------------------------------------- 1 | 201, 10 | ]; 11 | 12 | $a = 'a'; 13 | $array = 'array'; 14 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/NamingConventions/ValidMethodNameUnitTest.inc: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/WhiteSpace/VerticalEmptyLinesUnitTest.inc.fixed: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./vendor/squizlabs/php_codesniffer/tests/AllTests.php 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ./CodeIgniter4 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeigniter4/codeigniter4-standard", 3 | "type": "phpcodesniffer-standard", 4 | "description": "CodeIgniter 4 Standard for PHP_CodeSniffer 3.", 5 | "license":"MIT", 6 | "abandoned": "codeigniter/coding-standard", 7 | "authors": [ 8 | { 9 | "name": "Louis Linehan", 10 | "email": "louis.linehan@gmail.com", 11 | "homepage": "https://github.com/louisl", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | }, 17 | "require-dev": { 18 | "squizlabs/php_codesniffer": "^3.1", 19 | "satooshi/php-coveralls": "^1.0", 20 | "phpunit/phpunit": "^7.0", 21 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7" 22 | }, 23 | "scripts": { 24 | "test": "phpunit --filter CodeIgniter4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 British Columbia Institute of Technology (https://bcit.ca/) 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 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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|null) 34 | */ 35 | public $forbiddenFunctions = [ 36 | 'error_log' => null, 37 | 'print_r' => null, 38 | 'var_dump' => null, 39 | ]; 40 | 41 | /** 42 | * Set error to false to show warnings. 43 | * 44 | * @var boolean 45 | */ 46 | public $error = false; 47 | 48 | }//end class 49 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/WhiteSpace/BooleanNotSpaceAfterUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\WhiteSpace; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class BooleanNotSpaceAfterUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 3 => 1, 31 | 4 => 0, 32 | ); 33 | 34 | }//end getErrorList() 35 | 36 | 37 | /** 38 | * Returns the lines where warnings should occur. 39 | * 40 | * The key of the array should represent the line number and the value 41 | * should represent the number of warnings that should occur on that line. 42 | * 43 | * @return array 44 | */ 45 | public function getWarningList() 46 | { 47 | return array(); 48 | 49 | }//end getWarningList() 50 | 51 | 52 | }//end class 53 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/Operators/BooleanOrUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\Operators; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class BooleanOrUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 3 => 1, 31 | 4 => 1, 32 | 5 => 1, 33 | 6 => 2, 34 | ); 35 | 36 | }//end getErrorList() 37 | 38 | 39 | /** 40 | * Returns the lines where warnings should occur. 41 | * 42 | * The key of the array should represent the line number and the value 43 | * should represent the number of warnings that should occur on that line. 44 | * 45 | * @return array 46 | */ 47 | public function getWarningList() 48 | { 49 | return array(); 50 | 51 | }//end getWarningList() 52 | 53 | 54 | }//end class 55 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/Operators/BooleanAndUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\Operators; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class BooleanAndUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 3 => 1, 31 | 4 => 1, 32 | 5 => 1, 33 | 7 => 2, 34 | ); 35 | 36 | }//end getErrorList() 37 | 38 | 39 | /** 40 | * Returns the lines where warnings should occur. 41 | * 42 | * The key of the array should represent the line number and the value 43 | * should represent the number of warnings that should occur on that line. 44 | * 45 | * @return array 46 | */ 47 | public function getWarningList() 48 | { 49 | return array(); 50 | 51 | }//end getWarningList() 52 | 53 | 54 | }//end class 55 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/NamingConventions/ValidVariableNameUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\NamingConventions; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class ValidVariableNameUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 3 => 1, 31 | 4 => 1, 32 | 5 => 1, 33 | ); 34 | 35 | }//end getErrorList() 36 | 37 | 38 | /** 39 | * Returns the lines where warnings should occur. 40 | * 41 | * The key of the array should represent the line number and the value 42 | * should represent the number of warnings that should occur on that line. 43 | * 44 | * @return array 45 | */ 46 | public function getWarningList() 47 | { 48 | return array(); 49 | 50 | }//end getWarningList() 51 | 52 | 53 | }//end class 54 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/WhiteSpace/DisallowTabsInAlignmentUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\WhiteSpace; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class DisallowTabsInAlignmentUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 9 => 1, 31 | 12 => 1, 32 | 13 => 1, 33 | ); 34 | 35 | }//end getErrorList() 36 | 37 | 38 | /** 39 | * Returns the lines where warnings should occur. 40 | * 41 | * The key of the array should represent the line number and the value 42 | * should represent the number of warnings that should occur on that line. 43 | * 44 | * @return array 45 | */ 46 | public function getWarningList() 47 | { 48 | return array(); 49 | 50 | }//end getWarningList() 51 | 52 | 53 | }//end class 54 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/NamingConventions/ValidMethodNameUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\NamingConventions; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class ValidMethodNameUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 5 => 0, 31 | 6 => 1, 32 | 7 => 1, 33 | 8 => 1, 34 | 9 => 1, 35 | ); 36 | 37 | }//end getErrorList() 38 | 39 | 40 | /** 41 | * Returns the lines where warnings should occur. 42 | * 43 | * The key of the array should represent the line number and the value 44 | * should represent the number of warnings that should occur on that line. 45 | * 46 | * @return array 47 | */ 48 | public function getWarningList() 49 | { 50 | return array(); 51 | 52 | }//end getWarningList() 53 | 54 | 55 | }//end class 56 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/WhiteSpace/VerticalEmptyLinesUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\WhiteSpace; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class VerticalEmptyLinesUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Returns the lines where errors should occur. 21 | * 22 | * The key of the array should represent the line number and the value 23 | * should represent the number of errors that should occur on that line. 24 | * 25 | * @return array 26 | */ 27 | public function getErrorList() 28 | { 29 | return array( 30 | 13 => 1, 31 | 19 => 1, 32 | 23 => 1, 33 | 24 => 1, 34 | 30 => 1, 35 | 38 => 1, 36 | 41 => 1, 37 | 44 => 1, 38 | 45 => 1, 39 | 46 => 1, 40 | 47 => 1, 41 | ); 42 | 43 | }//end getErrorList() 44 | 45 | 46 | /** 47 | * Returns the lines where warnings should occur. 48 | * 49 | * The key of the array should represent the line number and the value 50 | * should represent the number of warnings that should occur on that line. 51 | * 52 | * @return array 53 | */ 54 | public function getWarningList() 55 | { 56 | return array(); 57 | 58 | }//end getWarningList() 59 | 60 | 61 | }//end class 62 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Operators/BooleanOrSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 [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 = [$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 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 [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 = [$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 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 [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 = [$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 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 [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 = [$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/OneClassPerFileSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 = [ 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 [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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI 2 | 3 | # Use new container based environment 4 | sudo: false 5 | 6 | # Declare project language. 7 | language: php 8 | 9 | env: 10 | global: 11 | # Name and folder of the the standard to test. 12 | - STANDARD="CodeIgniter4" 13 | # Upload covarage to coveralls. 14 | - COVERALLS="1" 15 | # Xdebug3 needs this. 16 | - XDEBUG_MODE=coverage 17 | 18 | matrix: 19 | fast_finish: true 20 | 21 | # Declare versions of PHP to use. Use one decimal max. 22 | include: 23 | # aliased to a recent 5.4.x version 24 | # - php: '5.4' 25 | # aliased to a recent 5.5.x version 26 | # - php: '5.5' 27 | # aliased to a recent 5.6.x version 28 | # - php: '5.6' 29 | # aliased to a recent 7.x version 30 | # - php: '7.0' 31 | # aliased to a recent 7.x version 32 | # - php: '7.1' 33 | - php: '7.2' 34 | - php: '7.3' 35 | - php: '7.4' 36 | # - php: '8.0' 37 | # aliased to a recent hhvm version 38 | # - php: 'hhvm' 39 | # php nightly 40 | # - php: 'nightly' 41 | 42 | # allow_failures: 43 | # - php: 'hhvm' 44 | # - php: 'nightly' 45 | 46 | before_install: 47 | # Remove xdebug. Needed for coverage. 48 | # - phpenv config-rm xdebug.ini 49 | 50 | install: 51 | # Update composer to latest version. 52 | - composer self-update 53 | # Install project composer deps in composer.json 54 | - composer install --no-interaction 55 | 56 | before_script: 57 | # Rehash the php environment if testing on several PHP versions. 58 | # - phpenv rehash 59 | 60 | script: 61 | # Check for PHP syntax errors. 62 | - find -L . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l 63 | # - Check files match the PHPCS standard. 64 | - ./vendor/bin/phpcs --ignore=*/Tests/* ./$STANDARD/ --standard=./vendor/squizlabs/php_codesniffer/phpcs.xml.dist 65 | # Change the default standard. 66 | - ./vendor/bin/phpcs --config-set installed_paths $TRAVIS_BUILD_DIR/$STANDARD 67 | # Verify it's installed. 68 | - ./vendor/bin/phpcs -i 69 | # Run unit tests for the standard. 70 | - ./vendor/bin/phpunit --debug --filter $STANDARD 71 | 72 | after_success: 73 | - if [[ "$COVERALLS" == "1" && "$TRAVIS_PHP_VERSION" == "7.4" ]]; then ./vendor/bin/coveralls -v -x ./build/logs/coverage/clover/clover.xml; fi 74 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/WhiteSpace/BooleanNotSpaceAfterSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 [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/WhiteSpace/VerticalEmptyLinesSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | use CodeIgniter4\Util\Common; 16 | 17 | /** 18 | * Vertical Empty Lines Sniff 19 | * 20 | * Checks for consecutive empty vertical lines. 21 | * 22 | * @author Louis Linehan 23 | */ 24 | class VerticalEmptyLinesSniff implements Sniff 25 | { 26 | 27 | /** 28 | * Consecutive empty vertical lines allowed. 29 | * 30 | * @var integer 31 | */ 32 | public $allowed = 1; 33 | 34 | 35 | /** 36 | * Returns an array of tokens this test wants to listen for. 37 | * 38 | * @return array 39 | */ 40 | public function register() 41 | { 42 | return [T_OPEN_TAG]; 43 | 44 | }//end register() 45 | 46 | 47 | /** 48 | * Processes this test, when one of its tokens is encountered. 49 | * 50 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 51 | * @param int $stackPtr The position of the current token in 52 | * the stack passed in $tokens. 53 | * 54 | * @return int 55 | */ 56 | public function process(File $phpcsFile, $stackPtr) 57 | { 58 | $errors = []; 59 | $tokens = $phpcsFile->getTokens(); 60 | for ($i = 1; $i < $phpcsFile->numTokens; $i++) { 61 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); 62 | 63 | $lines = ($tokens[$nextContentPtr]['line'] - $tokens[$i]['line'] - 1); 64 | $errorLine = (($nextContentPtr - $lines) + $this->allowed - 1); 65 | 66 | if ($lines > ($this->allowed) && in_array($errorLine, $errors) === false) { 67 | $errors[] = $errorLine; 68 | 69 | $data = [ 70 | $this->allowed, 71 | Common::pluralize('line', $this->allowed), 72 | ]; 73 | $error = 'Expected only %s empty %s'; 74 | $fix = $phpcsFile->addFixableError($error, $errorLine, 'VerticalEmptyLines', $data); 75 | if ($fix === true) { 76 | $phpcsFile->fixer->replaceToken($errorLine, ''); 77 | } 78 | } 79 | } 80 | 81 | // Ignore the rest of the file. 82 | return ($phpcsFile->numTokens + 1); 83 | 84 | }//end process() 85 | 86 | 87 | }//end class 88 | -------------------------------------------------------------------------------- /CodeIgniter4/Util/Common.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | * Extends 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 = [ 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 = ['_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 | /** 97 | * Pluralize 98 | * 99 | * Basic pluralize intended for use in error messages 100 | * tab/s, space/s, error/s etc. 101 | * 102 | * @param string $string String. 103 | * @param float $num Number. 104 | * 105 | * @return string 106 | */ 107 | public static function pluralize($string, $num) 108 | { 109 | if ($num > 1) { 110 | return $string.'s'; 111 | } else { 112 | return $string; 113 | } 114 | 115 | }//end pluralize() 116 | 117 | 118 | }//end class 119 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Files/FilenameMatchesClassSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | * If the file has a bad filename. 28 | * 29 | * Change to true and check it later to avoid displaying multiple errors. 30 | * 31 | * @var boolean 32 | */ 33 | protected $badFilename = false; 34 | 35 | 36 | /** 37 | * Returns an array of tokens this test wants to listen for. 38 | * 39 | * @return array 40 | */ 41 | public function register() 42 | { 43 | return [ 44 | T_CLASS, 45 | T_INTERFACE, 46 | T_TRAIT, 47 | ]; 48 | 49 | }//end register() 50 | 51 | 52 | /** 53 | * Processes this sniff, when one of its tokens is encountered. 54 | * 55 | * @param 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 int 60 | */ 61 | public function process(File $phpcsFile, $stackPtr) 62 | { 63 | 64 | $tokens = $phpcsFile->getTokens(); 65 | 66 | $fileName = basename($phpcsFile->getFilename()); 67 | 68 | if (strpos($fileName, '_helper.php') !== false) { 69 | return; 70 | } 71 | 72 | $className = trim($phpcsFile->getDeclarationName($stackPtr)); 73 | 74 | if (strpos($className, 'Migration') === 0 && strpos($fileName, '_') !== false) { 75 | return; 76 | } 77 | 78 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 79 | $type = $tokens[$stackPtr]['content']; 80 | 81 | if ($fileName !== $className.'.php' && $this->badFilename === false) { 82 | $data = [ 83 | $fileName, 84 | $className.'.php', 85 | ]; 86 | $error = 'Filename "%s" doesn\'t match the expected filename "%s"'; 87 | $phpcsFile->addError($error, $nextContentPtr, ucfirst($type).'BadFilename', $data); 88 | $phpcsFile->recordMetric($nextContentPtr, 'Filename matches '.$type, 'no'); 89 | $this->badFilename = true; 90 | } else { 91 | $phpcsFile->recordMetric($nextContentPtr, 'Filename matches '.$type, 'yes'); 92 | } 93 | 94 | // Ignore the rest of the file. 95 | return ($phpcsFile->numTokens + 1); 96 | 97 | }//end process() 98 | 99 | 100 | }//end class 101 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Files/HelperFileSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | * Helper File Sniff 18 | * 19 | * Checks *_helper.php files only contain functions 20 | * and that the filename is lower snake_case. 21 | * 22 | * @author Louis Linehan 23 | */ 24 | class HelperFileSniff implements Sniff 25 | { 26 | 27 | /** 28 | * Files that are allowed multiple classes 29 | * 30 | * @var array 31 | */ 32 | public $unwantedTokens = [ 33 | T_CLASS, 34 | T_ANON_CLASS, 35 | T_INTERFACE, 36 | T_TRAIT, 37 | ]; 38 | 39 | /** 40 | * If the file has a bad filename. 41 | * 42 | * Change to true and check it later to avoid displaying multiple errors. 43 | * 44 | * @var boolean 45 | */ 46 | protected $badFilename = false; 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 [ 57 | T_FUNCTION, 58 | T_CLASS, 59 | T_ANON_CLASS, 60 | T_INTERFACE, 61 | T_TRAIT, 62 | ]; 63 | 64 | }//end register() 65 | 66 | 67 | /** 68 | * Processes this sniff, when one of its tokens is encountered. 69 | * 70 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 71 | * @param int $stackPtr The position of the current token in 72 | * the stack passed in $tokens. 73 | * 74 | * @return void 75 | */ 76 | public function process(File $phpcsFile, $stackPtr) 77 | { 78 | $fileName = basename($phpcsFile->getFilename()); 79 | if (strpos($fileName, '_helper.php') === false) { 80 | return; 81 | } else { 82 | // Check the filename. 83 | $expectedFilename = preg_replace('/_{2,}/', '_', strtolower($fileName)); 84 | if ($fileName !== $expectedFilename && $this->badFilename === false) { 85 | $data = [ 86 | $fileName, 87 | $expectedFilename, 88 | ]; 89 | $error = 'Helper filename "%s" doesn\'t match the expected filename "%s"'; 90 | $phpcsFile->addError($error, 1, 'HelperBadFilename', $data); 91 | $this->badFilename = true; 92 | } 93 | 94 | // Check for class, interface, trait etc. 95 | $tokens = $phpcsFile->getTokens(); 96 | if (in_array($tokens[$stackPtr]['code'], $this->unwantedTokens) === true) { 97 | $error = 'Helper files must only contain functions'; 98 | $phpcsFile->addError($error, $stackPtr, 'HelperOnlyFunctions'); 99 | } 100 | }//end if 101 | 102 | }//end process() 103 | 104 | 105 | }//end class 106 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/ControlStructures/AllmanControlSignatureUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\ControlStructures; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class AllmanControlSignatureUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Get a list of CLI values to set before the file is tested. 21 | * 22 | * @param string $testFile The name of the file being tested. 23 | * @param \PHP_CodeSniffer\Config $config The config data for the test run. 24 | * 25 | * @return void 26 | */ 27 | public function setCliValues($testFile, $config) 28 | { 29 | $config->tabWidth = 4; 30 | 31 | }//end setCliValues() 32 | 33 | 34 | /** 35 | * Returns the lines where errors should occur. 36 | * 37 | * The key of the array should represent the line number and the value 38 | * should represent the number of errors that should occur on that line. 39 | * 40 | * @return array 41 | */ 42 | public function getErrorList() 43 | { 44 | return array( 45 | 3 => 1, 46 | 5 => 1, 47 | 10 => 1, 48 | 18 => 4, 49 | 20 => 1, 50 | 22 => 1, 51 | 24 => 1, 52 | 28 => 2, 53 | 32 => 3, 54 | 34 => 1, 55 | 38 => 2, 56 | 42 => 3, 57 | 44 => 1, 58 | 48 => 2, 59 | 52 => 3, 60 | 54 => 1, 61 | 56 => 2, 62 | 60 => 1, 63 | 62 => 2, 64 | 66 => 7, 65 | 68 => 1, 66 | 70 => 2, 67 | 74 => 1, 68 | 76 => 3, 69 | 80 => 7, 70 | 82 => 2, 71 | 86 => 2, 72 | 90 => 2, 73 | 95 => 1, 74 | 99 => 1, 75 | 102 => 1, 76 | 104 => 2, 77 | 108 => 5, 78 | 112 => 2, 79 | 113 => 1, 80 | 115 => 2, 81 | 120 => 2, 82 | 122 => 1, 83 | 123 => 1, 84 | 126 => 1, 85 | 130 => 2, 86 | 148 => 1, 87 | 151 => 1, 88 | 154 => 1, 89 | 175 => 1, 90 | 185 => 2, 91 | 206 => 1, 92 | 208 => 2, 93 | ); 94 | 95 | }//end getErrorList() 96 | 97 | 98 | /** 99 | * Returns the lines where warnings should occur. 100 | * 101 | * The key of the array should represent the line number and the value 102 | * should represent the number of warnings that should occur on that line. 103 | * 104 | * @return array 105 | */ 106 | public function getWarningList() 107 | { 108 | return array(); 109 | 110 | }//end getWarningList() 111 | 112 | 113 | }//end class 114 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/Arrays/ArrayDeclarationUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/CodeIgniter4-Standard/blob/master/LICENSE MIT License 9 | */ 10 | 11 | namespace CodeIgniter4\Tests\Arrays; 12 | 13 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; 14 | 15 | class ArrayDeclarationUnitTest extends AbstractSniffUnitTest 16 | { 17 | 18 | 19 | /** 20 | * Get a list of CLI values to set before the file is tested. 21 | * 22 | * @param string $testFile The name of the file being tested. 23 | * @param \PHP_CodeSniffer\Config $config The config data for the test run. 24 | * 25 | * @return void 26 | */ 27 | public function setCliValues($testFile, $config) 28 | { 29 | $config->tabWidth = 4; 30 | 31 | }//end setCliValues() 32 | 33 | 34 | /** 35 | * Returns the lines where errors should occur. 36 | * 37 | * The key of the array should represent the line number and the value 38 | * should represent the number of errors that should occur on that line. 39 | * 40 | * @return array 41 | */ 42 | public function getErrorList() 43 | { 44 | return array( 45 | 6 => 1, 46 | 7 => 1, 47 | 8 => 1, 48 | 9 => 1, 49 | 10 => 1, 50 | 11 => 1, 51 | 12 => 1, 52 | 13 => 1, 53 | 14 => 2, 54 | 15 => 1, 55 | 16 => 1, 56 | 22 => 1, 57 | 29 => 1, 58 | 30 => 1, 59 | 36 => 1, 60 | 37 => 2, 61 | 42 => 2, 62 | 43 => 2, 63 | 44 => 2, 64 | 45 => 2, 65 | 47 => 2, 66 | 48 => 2, 67 | 49 => 2, 68 | 52 => 2, 69 | 56 => 2, 70 | 60 => 1, 71 | 61 => 1, 72 | 62 => 1, 73 | 64 => 1, 74 | 70 => 1, 75 | 71 => 1, 76 | 77 => 1, 77 | 80 => 1, 78 | 81 => 1, 79 | 82 => 1, 80 | 85 => 2, 81 | 87 => 1, 82 | 89 => 1, 83 | 92 => 1, 84 | 93 => 1, 85 | 98 => 1, 86 | 99 => 1, 87 | 100 => 1, 88 | 101 => 1, 89 | 104 => 1, 90 | 105 => 1, 91 | 106 => 1, 92 | 107 => 1, 93 | 112 => 1, 94 | 113 => 1, 95 | 116 => 1, 96 | 117 => 1, 97 | 118 => 1, 98 | 119 => 1, 99 | 120 => 1, 100 | ); 101 | 102 | }//end getErrorList() 103 | 104 | 105 | /** 106 | * Returns the lines where warnings should occur. 107 | * 108 | * The key of the array should represent the line number and the value 109 | * should represent the number of warnings that should occur on that line. 110 | * 111 | * @return array 112 | */ 113 | public function getWarningList() 114 | { 115 | return array(); 116 | 117 | }//end getWarningList() 118 | 119 | 120 | }//end class 121 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/NamingConventions/ValidFunctionNameSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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([T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT], [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 = [$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 = [$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 = [ 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 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/ControlStructures/AllmanControlSignatureUnitTest.inc: -------------------------------------------------------------------------------- 1 | 0); 6 | 7 | do 8 | { 9 | echo $i; 10 | } while ($i > 0); 11 | 12 | do 13 | { 14 | echo $i; 15 | } 16 | while ($i > 0); 17 | 18 | do { echo $i; } while ($i > 0); 19 | 20 | do{ 21 | echo $i; 22 | }while($i > 0); 23 | 24 | while ($i < 1) { 25 | echo $i; 26 | } 27 | 28 | while($i < 1){ 29 | echo $i; 30 | } 31 | 32 | while ($i < 1) { echo $i; } 33 | 34 | for ($i = 1; $i < 1; $i++) { 35 | echo $i; 36 | } 37 | 38 | for($i = 1; $i < 1; $i++){ 39 | echo $i; 40 | } 41 | 42 | for ($i = 1; $i < 1; $i++) { echo $i; } 43 | 44 | if ($i == 0) { 45 | $i = 1; 46 | } 47 | 48 | if($i == 0){ 49 | $i = 1; 50 | } 51 | 52 | if ($i == 0) { $i = 1; } 53 | 54 | if ($i == 0) { 55 | $i = 1; 56 | } else { 57 | $i = 0; 58 | } 59 | 60 | if ($i == 0) { 61 | $i = 1; 62 | }else{ 63 | $i = 0; 64 | } 65 | 66 | if ($i == 0) { $i = 1; } else { $i = 0; } 67 | 68 | if ($i == 0) { 69 | $i = 1; 70 | } else if ($i == 2) { 71 | $i = 0; 72 | } 73 | 74 | if ($i == 0) { 75 | $i = 1; 76 | }else if($i == 2){ 77 | $i = 0; 78 | } 79 | 80 | if ($i == 0) { $i = 1; } else if ($i == 2) { $i = 0; } 81 | 82 | if ($i == 0) { // comments are allowed 83 | $i = 1; 84 | } 85 | 86 | if ($i == 0) {// comments are allowed 87 | $i = 1; 88 | } 89 | 90 | if ($i == 0) { /* comments are allowed*/ 91 | $i = 1; 92 | } 93 | 94 | if ($i == 0) 95 | { // this is ok 96 | $i = 1; 97 | } 98 | 99 | if ($i == 0) /* this is ok */ { 100 | } 101 | 102 | try { 103 | $code = 'this'; 104 | } catch (Exception $e) { 105 | // Caught! 106 | } 107 | 108 | try { $code = 'this'; } catch (Exception $e) { 109 | // Caught! 110 | } 111 | 112 | do { echo $i; 113 | } while ($i > 0); 114 | 115 | if ($i === 0) { 116 | 117 | $i = 1 118 | } 119 | 120 | if ($a) { 121 | 122 | } 123 | elseif ($b) { 124 | } 125 | 126 | foreach ($items as $item) { 127 | echo $item; 128 | } 129 | 130 | foreach($items as $item){ 131 | echo $item; 132 | } 133 | 134 | if ($a && $b) // && $c) 135 | { 136 | } 137 | 138 | if ($a == 5) : 139 | echo "a equals 5"; 140 | echo "..."; 141 | elseif ($a == 6) : 142 | echo "a equals 6"; 143 | echo "!!!"; 144 | else : 145 | echo "a is neither 5 nor 6"; 146 | endif; 147 | 148 | try { 149 | // try body 150 | } 151 | catch (FirstExceptionType $e) { 152 | // catch body 153 | } 154 | catch (OtherExceptionType $e) { 155 | // catch body 156 | } 157 | 158 | switch($foo) { 159 | 160 | case 'bar': 161 | break; 162 | 163 | } 164 | 165 | if ($foo) : 166 | endif; 167 | 168 | ?> 169 | 170 | getRow()): ?> 171 |

172 | 173 | 174 | 177 |
178 | 181 |
182 | 190 | 191 | 192 | 193 | 194 | 195 | hello 196 | 197 | 198 | 200 | hello 201 | 202 | 203 | 201, 7 | 'unsupported_response_type' => 400, 8 | 'invalid_scope' => 400, 9 | 'temporarily_unavailable' => 400, 10 | 'invalid_grant' => [ 11 | 'invalid_data' => 400, 12 | 'access_denied' => 401, 13 | 'forbidden' => 403, 14 | 'resource_not_found' => 404], 15 | 'conflict' => 409, 16 | ]; 17 | 18 | public $globals = [ 19 | 'before' => [ 20 | // 'csrf' 21 | ], 22 | 'after' => [ 23 | 'toolbar' 24 | ] 25 | ]; 26 | 27 | protected $defaultHTTPMethods = [ 28 | 'options', 29 | 'get', 30 | 'head', 31 | ]; 32 | 33 | protected function isValidLuhn() 34 | { 35 | $sumTable = [ 36 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 37 | [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] 38 | ]; 39 | } 40 | 41 | protected $cards = [ 42 | 'American Express' => ['name' => 'amex', 'length' => '15', 'prefixes' => '34,37', 'checkdigit' => true], 43 | 'DinersClub' => ['name' => 'dinersclub', 'length' => '14,16', 'prefixes' => '300,301,302,303,304,305,309,36,38,39,54,55', 'checkdigit' => true], 44 | 'MasterCard' => ['name' => 'mastercard', 'length' => '16', 'prefixes' => '51,52,53,54,55,22,23,24,25,26,27', 'checkdigit' => true], 45 | 'Visa' => ['name' => 'visa', 'length' => '13,16,19', 'prefixes' => '4', 'checkdigit' => true], 46 | // Canadian Cards 47 | 'CIBC Convenience Card' => ['name' => 'cibc', 'length' => '16', 'prefixes' => '4506', 'checkdigit' => false], 48 | 'Royal Bank of Canada Client Card' => ['name' => 'rbc', 'length' => '16', 'prefixes' => '45', 'checkdigit' => false], 49 | 'TD Canada Trust Access Card' => ['name' => 'tdtrust', 'length' => '16', 'prefixes' => '589297', 'checkdigit' => false], 50 | ]; 51 | 52 | $a = array 53 | // comment 54 | ( 'a', 'b' ); 55 | 56 | $a = array /* comment */ ( 'a', 'b' ); 57 | 58 | protected static $statusCodes = [ 59 | // 1xx: Informational 60 | 100 => 'Continue', 61 | 101 => 'Switching Protocols', 62 | 102 => 'Processing', // http://www.iana.org/go/rfc2518 63 | // 2xx: Success 64 | 200 => 'OK', 65 | ]; 66 | 67 | public function error() 68 | { 69 | return [ 70 | 'code' => '', 71 | 'message' => pg_last_error($this->connID) 72 | ]; 73 | } 74 | 75 | public function error2() 76 | { 77 | $this->QBWhere[] = ['condition' => $like_statement, 'escape' => $escape]; 78 | 79 | $this->QBWhere[] = [ 80 | 'condition' => $like_statement, 81 | 'escape' => $escape, 82 | ]; 83 | } 84 | 85 | public $arr = array( ); 86 | 87 | public $arr = [ ]; 88 | 89 | public $arr = [1,]; 90 | 91 | public $arr = [ 92 | 'a'=> 1, 93 | 'b'=> 2, 94 | 'c' => 3, 95 | ]; 96 | 97 | $cache->save($cacheKeyPagination, [ 98 | 'page' => $this->pager->getCurrentPage(), 99 | 'perPage' => $this->pager->getPerPage(), 100 | 'total' => $this->pager->getPerPage() * $this->pager->getPageCount(), 101 | ] , 15); 102 | 103 | return preg_replace( 104 | array( 105 | '##i', 106 | '#`]+)).*?\>#i' 107 | ), '\\2', $str 108 | ); 109 | 110 | $this->db->table($this->table) 111 | ->insert([ 112 | 'version' => $version, 113 | 'name' => $this->name, 114 | ]); 115 | 116 | $message->setCustomProperty('user_info', array( 117 | 'id' => $id, 118 | 'name' => 'Test message' 119 | ) 120 | ); 121 | 122 | $users = [ 123 | [ 124 | 'id' => 1, 125 | 'name' => 'John', 126 | 'email' => 'john@example.com', 127 | 'fact' => 'Loves coding', 128 | ], 129 | [ 130 | 'id' => 2, 131 | 'name' => 'Jim', 132 | 'email' => 'jim@example.com', 133 | 'fact' => 'Developed on CodeIgniter', 134 | ], 135 | [ 136 | 'id' => 3, 137 | 'name' => 'Jane', 138 | 'email' => 'jane@example.com', 139 | 'fact' => 'Lives in the USA', [ 140 | 'hobbies' => [ 141 | 'guitar', 142 | 'cycling', 143 | ], 144 | ] 145 | ], 146 | ]; 147 | } 148 | -------------------------------------------------------------------------------- /CodeIgniter4/Tests/ControlStructures/AllmanControlSignatureUnitTest.inc.fixed: -------------------------------------------------------------------------------- 1 | 0); 8 | 9 | do 10 | { 11 | echo $i; 12 | } 13 | while ($i > 0); 14 | 15 | do 16 | { 17 | echo $i; 18 | } 19 | while ($i > 0); 20 | 21 | do 22 | { 23 | echo $i; 24 | } 25 | while ($i > 0); 26 | 27 | do 28 | { 29 | echo $i; 30 | } 31 | while($i > 0); 32 | 33 | while ($i < 1) 34 | { 35 | echo $i; 36 | } 37 | 38 | while ($i < 1) 39 | { 40 | echo $i; 41 | } 42 | 43 | while ($i < 1) 44 | { 45 | echo $i; 46 | } 47 | 48 | for ($i = 1; $i < 1; $i++) 49 | { 50 | echo $i; 51 | } 52 | 53 | for ($i = 1; $i < 1; $i++) 54 | { 55 | echo $i; 56 | } 57 | 58 | for ($i = 1; $i < 1; $i++) 59 | { 60 | echo $i; 61 | } 62 | 63 | if ($i == 0) 64 | { 65 | $i = 1; 66 | } 67 | 68 | if ($i == 0) 69 | { 70 | $i = 1; 71 | } 72 | 73 | if ($i == 0) 74 | { 75 | $i = 1; 76 | } 77 | 78 | if ($i == 0) 79 | { 80 | $i = 1; 81 | } 82 | else 83 | { 84 | $i = 0; 85 | } 86 | 87 | if ($i == 0) 88 | { 89 | $i = 1; 90 | } 91 | else 92 | { 93 | $i = 0; 94 | } 95 | 96 | if ($i == 0) 97 | { 98 | $i = 1; 99 | } 100 | else 101 | { 102 | $i = 0; 103 | } 104 | 105 | if ($i == 0) 106 | { 107 | $i = 1; 108 | } 109 | else if ($i == 2) 110 | { 111 | $i = 0; 112 | } 113 | 114 | if ($i == 0) 115 | { 116 | $i = 1; 117 | } 118 | else if ($i == 2) 119 | { 120 | $i = 0; 121 | } 122 | 123 | if ($i == 0) 124 | { 125 | $i = 1; 126 | } 127 | else if ($i == 2) 128 | { 129 | $i = 0; 130 | } 131 | 132 | if ($i == 0) 133 | { 134 | // comments are allowed 135 | $i = 1; 136 | } 137 | 138 | if ($i == 0) 139 | { 140 | // comments are allowed 141 | $i = 1; 142 | } 143 | 144 | if ($i == 0) 145 | { 146 | /* comments are allowed*/ 147 | $i = 1; 148 | } 149 | 150 | if ($i == 0) 151 | { 152 | // this is ok 153 | $i = 1; 154 | } 155 | 156 | if ($i == 0) /* this is ok */ 157 | { 158 | } 159 | 160 | try 161 | { 162 | $code = 'this'; 163 | } 164 | catch (Exception $e) 165 | { 166 | // Caught! 167 | } 168 | 169 | try 170 | { 171 | $code = 'this'; 172 | } 173 | catch (Exception $e) 174 | { 175 | // Caught! 176 | } 177 | 178 | do 179 | { 180 | echo $i; 181 | } 182 | while ($i > 0); 183 | 184 | if ($i === 0) 185 | { 186 | $i = 1 187 | } 188 | 189 | if ($a) 190 | { 191 | } 192 | elseif ($b) 193 | { 194 | } 195 | 196 | foreach ($items as $item) 197 | { 198 | echo $item; 199 | } 200 | 201 | foreach ($items as $item) 202 | { 203 | echo $item; 204 | } 205 | 206 | if ($a && $b) // && $c) 207 | { 208 | } 209 | 210 | if ($a == 5) : 211 | echo "a equals 5"; 212 | echo "..."; 213 | elseif ($a == 6) : 214 | echo "a equals 6"; 215 | echo "!!!"; 216 | else : 217 | echo "a is neither 5 nor 6"; 218 | endif; 219 | 220 | try 221 | { 222 | // try body 223 | } 224 | catch (FirstExceptionType $e) 225 | { 226 | // catch body 227 | } 228 | catch (OtherExceptionType $e) 229 | { 230 | // catch body 231 | } 232 | 233 | switch($foo) { 234 | 235 | case 'bar': 236 | break; 237 | 238 | } 239 | 240 | if ($foo) : 241 | endif; 242 | 243 | ?> 244 | 245 | getRow()): ?> 246 |

247 | 248 | 249 | 253 |
254 | 257 |
258 | 268 | 269 | 270 | 271 | 272 | 273 | hello 274 | 275 | 276 | 278 | hello 279 | 280 | 281 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | * Disallow Tabs In Alignment Sniff 18 | * 19 | * Checks for use of tabs after indendation. 20 | * 21 | * @author Louis Linehan 22 | */ 23 | class DisallowTabsInAlignmentSniff implements Sniff 24 | { 25 | 26 | /** 27 | * The --tab-width CLI value that is being used. 28 | * 29 | * @var integer 30 | */ 31 | private $tabWidth = null; 32 | 33 | 34 | /** 35 | * Returns an array of tokens this test wants to listen for. 36 | * 37 | * @return array 38 | */ 39 | public function register() 40 | { 41 | return [T_OPEN_TAG]; 42 | 43 | }//end register() 44 | 45 | 46 | /** 47 | * Processes this test, when one of its tokens is encountered. 48 | * 49 | * @param \PHP_CodeSniffer\Files\File $phpcsFile All the tokens found in the document. 50 | * @param int $stackPtr The position of the current token in 51 | * the stack passed in $tokens. 52 | * 53 | * @return void 54 | */ 55 | public function process(File $phpcsFile, $stackPtr) 56 | { 57 | 58 | if ($this->tabWidth === null) { 59 | if (isset($phpcsFile->config->tabWidth) === false || $phpcsFile->config->tabWidth === 0) { 60 | // We have no idea how wide tabs are, so assume 4 spaces for fixing. 61 | // It shouldn't really matter because alignment and spacing sniffs 62 | // elsewhere in the standard should fix things up. 63 | $this->tabWidth = 4; 64 | } else { 65 | $this->tabWidth = $phpcsFile->config->tabWidth; 66 | } 67 | } 68 | 69 | $checkTokens = [ 70 | T_WHITESPACE => true, 71 | T_INLINE_HTML => true, 72 | T_DOC_COMMENT_WHITESPACE => true, 73 | ]; 74 | 75 | $tokens = $phpcsFile->getTokens(); 76 | 77 | for ($i = ($stackPtr); $i < $phpcsFile->numTokens; $i++) { 78 | // Skip whitespace at the start of a new line and tokens not consdered white space. 79 | if ($tokens[$i]['column'] === 1 || isset($checkTokens[$tokens[$i]['code']]) === false) { 80 | continue; 81 | } 82 | 83 | // If tabs are being converted to spaces by the tokeniser, the 84 | // original content should be checked instead of the converted content. 85 | if (isset($tokens[$i]['orig_content']) === true) { 86 | $content = $tokens[$i]['orig_content']; 87 | } else { 88 | $content = $tokens[$i]['content']; 89 | } 90 | 91 | if (strpos($content, "\t") !== false) { 92 | // Try to maintain intended alignment by counting tabs and spaces. 93 | $countTabs = substr_count($content, "\t"); 94 | $countSpaces = substr_count($content, " "); 95 | 96 | if ($countTabs === 1) { 97 | $tabsPlural = ''; 98 | } else { 99 | $tabsPlural = 's'; 100 | } 101 | 102 | if ($countSpaces === 1) { 103 | $spacesPlural = ''; 104 | } else { 105 | $spacesPlural = 's'; 106 | } 107 | 108 | $data = [ 109 | $countTabs, 110 | $tabsPlural, 111 | $countSpaces, 112 | $spacesPlural, 113 | ]; 114 | $error = 'Spaces must be used for alignment; %s tab%s and %s space%s found'; 115 | 116 | // The fix might make some lines misaligned if the tab didn't fill the number 117 | // of 'tabWidth' spaces, other alignment and spacing sniffs should fix that. 118 | $fix = $phpcsFile->addFixableError($error, $i, 'TabsUsedInAlignment', $data); 119 | if ($fix === true) { 120 | $phpcsFile->fixer->beginChangeset(); 121 | $spaces = str_repeat(' ', (($this->tabWidth * $countTabs) + $countSpaces)); 122 | $phpcsFile->fixer->replaceToken($i, $spaces); 123 | $phpcsFile->fixer->endChangeset(); 124 | }//end if 125 | }//end if 126 | }//end for 127 | 128 | // Ignore the rest of the file. 129 | return ($phpcsFile->numTokens + 1); 130 | 131 | }//end process() 132 | 133 | 134 | }//end class 135 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/NamingConventions/ValidMethodNameSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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([T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT], [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 = [$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 = [$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 = [$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 = [ 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Deprecated 2 | 3 | The new [CodeIgniter Coding Standard](https://github.com/CodeIgniter/coding-standard) is officially published! 4 | This switch uses PHP CS Fixer instead of Code Sniffer and applies a lot of changes (most notably, PSR-12 compliance). 5 | Visit the repo for more information. This library will remain in place but may not be maintained and is no longer 6 | considered compliant with official CodeIgniter 4 framework styles. 7 | 8 | # CodeIgniter4-Standard 9 | 10 | [CodeIgniter](https://codeigniter.com) 4 coding standard for use with [PHP_CodeSniffer 3](https://github.com/squizlabs/PHP_CodeSniffer). 11 | 12 | Version 1 13 | 14 | | Master | Develop | 15 | | :---: | :---: | 16 | | [![Build Status](https://travis-ci.org/bcit-ci/CodeIgniter4-Standard.svg?branch=master)](https://travis-ci.org/bcit-ci/CodeIgniter4-Standard) | [![Build Status](https://travis-ci.org/bcit-ci/CodeIgniter4-Standard.svg?branch=develop)](https://travis-ci.org/bcit-ci/CodeIgniter4-Standard) | 17 | | [![Coverage Status](https://coveralls.io/repos/github/bcit-ci/CodeIgniter4-Standard/badge.svg?branch=master)](https://coveralls.io/github/bcit-ci/CodeIgniter4-Standard?branch=master) | [![Coverage Status](https://coveralls.io/repos/github/bcit-ci/CodeIgniter4-Standard/badge.svg?branch=develop)](https://coveralls.io/github/bcit-ci/CodeIgniter4-Standard?branch=develop) | 18 | 19 | ***This is currently a work in progress.*** 20 | 21 | *Requested at: https://github.com/bcit-ci/CodeIgniter4/issues/182* 22 | 23 | ## Requirements 24 | 25 | [PHP_CodeSniffer 3](https://github.com/squizlabs/PHP_CodeSniffer). (3.1.1 or greater). 26 | 27 | PHP (7.1 or greater) with mbstring extension. 28 | 29 | ## Install 30 | 31 | ### Composer install 32 | 33 | `cd /Path/To/MyProject` 34 | `composer require codeigniter4/codeigniter4-standard --dev` 35 | 36 | Set the `phpcs standard path` and `phpcbf standard path` in your editor/plugin config to: 37 | 38 | `/Path/To/MyProject/vendor/codeigniter4/codeigniter4-standard/CodeIgniter4/ruleset.xml` 39 | 40 | ### Download install 41 | 42 | Download [CodeIgniter4-Standard](https://github.com/bcit-ci/CodeIgniter4-Standard/archive/v1.0.1.zip). 43 | 44 | Set `standard ` paths to your local filesystem: 45 | 46 | `'/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 47 | 48 | ### Global install 49 | 50 | Globally [install PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer/blob/master/README.md) with one of the various methods. 51 | 52 | Once complete you should be able to execute `phpcs -i` on the command line. 53 | 54 | You should see something like:- 55 | 56 | `The installed coding standards are MySource, PEAR, PSR1, PSR2, Squiz and Zend.` 57 | 58 | Either clone this repository... 59 | 60 | `git clone -b master --depth 1 https://github.com/bcit-ci/CodeIgniter4-Standard.git`. 61 | 62 | or use composer... 63 | 64 | `composer global require codeigniter4/codeigniter4-standard` 65 | 66 | or download. 67 | 68 | Take note of the paths where they were installed. 69 | 70 | Create a symbolic link to the `CodeIgniter4-Standard/CodeIgniter4` directory in `php_codesniffer/src/Standards/` eg. 71 | 72 | `ln -s ~/Documents/Projects/CodeIgniter4-Standard/CodeIgniter4 ~/.composer/vendor/squizlabs/php_codesniffer/src/Standards/CodeIgniter4` 73 | 74 | or copy the `CodeIgniter4-Standard/CodeIgniter4` directory to `php_codesniffer/src/Standards/` 75 | 76 | Executing `phpcs -i` should now show CodeIgniter4 installed eg. 77 | 78 | `The installed coding standards are CodeIgniter4, MySource, PEAR, PSR1, PSR2, Squiz and Zend.` 79 | 80 | You should now be able to set 'CodeIgniter4' as the phpcs standard in the plugin/editor/IDE of your choice. 81 | 82 | ### Command line use 83 | 84 | #### Sniffing errors & warnings (reporting). 85 | 86 | Single file... 87 | 88 | `phpcs /Path/To/MyFile.php --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 89 | 90 | or if globally installed. 91 | 92 | `phpcs /Path/To/MyFile.php --standard=CodeIgniter4` 93 | 94 | Directory (recursive). 95 | 96 | `phpcs /Path/To/MyProject --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 97 | 98 | or if globally installed. 99 | 100 | `phpcs /Path/To/MyProject --standard=CodeIgniter4` 101 | 102 | #### Fixing fixable errors. 103 | 104 | Single file. 105 | 106 | `phpcbf /Path/To/MyFile.php --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 107 | 108 | or if globally installed. 109 | 110 | `phpcbf /Path/To/MyFile.php --standard=CodeIgniter4` 111 | 112 | Directory (recursive). 113 | 114 | `phpcbf /Path/To/MyProject --standard='/Path/To/CodeIgniter4-Standard/CodeIgniter4/ruleset.xml'` 115 | 116 | or if globally installed. 117 | 118 | `phpcbf /Path/To/MyProject --standard=CodeIgniter4` 119 | 120 | ## Credits 121 | 122 | 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). 123 | 124 | Thanks to [EllisLab](https://ellislab.com) for originally creating CodeIgniter and the [British Columbia Institute of Technology](https://bcit.ca/) for continuing the project. Thanks to all the developers and contibutors working on [CodeIgniter 4](https://github.com/bcit-ci/CodeIgniter4). 125 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/ControlStructures/ControlStructureSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | * How many spaces should follow the opening bracket. 28 | * 29 | * @var integer 30 | */ 31 | public $requiredSpacesAfterOpen = 0; 32 | 33 | /** 34 | * How many spaces should precede the closing bracket. 35 | * 36 | * @var integer 37 | */ 38 | public $requiredSpacesBeforeClose = 0; 39 | 40 | 41 | /** 42 | * Returns an array of tokens this test wants to listen for. 43 | * 44 | * @return array 45 | */ 46 | public function register() 47 | { 48 | return [ 49 | T_IF, 50 | T_WHILE, 51 | T_FOREACH, 52 | T_FOR, 53 | T_SWITCH, 54 | T_DO, 55 | T_ELSE, 56 | T_ELSEIF, 57 | T_TRY, 58 | T_CATCH, 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 | $this->requiredSpacesAfterOpen = (int) $this->requiredSpacesAfterOpen; 76 | $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose; 77 | $tokens = $phpcsFile->getTokens(); 78 | 79 | if (isset($tokens[$stackPtr]['parenthesis_opener']) === false 80 | || isset($tokens[$stackPtr]['parenthesis_closer']) === false 81 | ) { 82 | return; 83 | } 84 | 85 | $parenOpener = $tokens[$stackPtr]['parenthesis_opener']; 86 | $parenCloser = $tokens[$stackPtr]['parenthesis_closer']; 87 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($parenOpener + 1), null, true); 88 | 89 | $spaceAfterOpen = 0; 90 | if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) { 91 | if (strpos($tokens[($parenOpener + 1)]['content'], $phpcsFile->eolChar) !== false) { 92 | $spaceAfterOpen = 'newline'; 93 | } else { 94 | $spaceAfterOpen = strlen($tokens[($parenOpener + 1)]['content']); 95 | } 96 | } 97 | 98 | if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen) { 99 | $error = 'Expected %s spaces after opening bracket; %s found'; 100 | $data = [ 101 | $this->requiredSpacesAfterOpen, 102 | $spaceAfterOpen, 103 | ]; 104 | $fix = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data); 105 | if ($fix === true) { 106 | $padding = str_repeat(' ', $this->requiredSpacesAfterOpen); 107 | if ($spaceAfterOpen === 0) { 108 | $phpcsFile->fixer->addContent($parenOpener, $padding); 109 | } else if ($spaceAfterOpen === 'newline') { 110 | $phpcsFile->fixer->replaceToken(($parenOpener + 1), ''); 111 | } else { 112 | $phpcsFile->fixer->replaceToken(($parenOpener + 1), $padding); 113 | } 114 | } 115 | } 116 | 117 | // Spaces before control structure close parenthesis. 118 | if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) { 119 | $spaceBeforeClose = 0; 120 | if ($tokens[($parenCloser - 1)]['code'] === T_WHITESPACE) { 121 | $spaceBeforeClose = strlen(ltrim($tokens[($parenCloser - 1)]['content'], $phpcsFile->eolChar)); 122 | } 123 | 124 | if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose) { 125 | $error = 'Expected %s spaces before closing bracket; %s found'; 126 | $data = [ 127 | $this->requiredSpacesBeforeClose, 128 | $spaceBeforeClose, 129 | ]; 130 | $fix = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data); 131 | if ($fix === true) { 132 | $padding = str_repeat(' ', $this->requiredSpacesBeforeClose); 133 | if ($spaceBeforeClose === 0) { 134 | $phpcsFile->fixer->addContentBefore($parenCloser, $padding); 135 | } else { 136 | $phpcsFile->fixer->replaceToken(($parenCloser - 1), $padding); 137 | } 138 | } 139 | } 140 | }//end if 141 | 142 | }//end process() 143 | 144 | 145 | }//end class 146 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Commenting/ClassCommentSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 = [ 34 | '@package' => [ 35 | 'required' => false, 36 | 'allow_multiple' => false, 37 | ], 38 | '@subpackage' => [ 39 | 'required' => false, 40 | 'allow_multiple' => false, 41 | ], 42 | '@category' => [ 43 | 'required' => false, 44 | 'allow_multiple' => false, 45 | ], 46 | '@author' => [ 47 | 'required' => false, 48 | 'allow_multiple' => true, 49 | ], 50 | '@copyright' => [ 51 | 'required' => false, 52 | 'allow_multiple' => true, 53 | ], 54 | '@license' => [ 55 | 'required' => false, 56 | 'allow_multiple' => false, 57 | ], 58 | '@link' => [ 59 | 'required' => false, 60 | 'allow_multiple' => true, 61 | ], 62 | '@since' => [ 63 | 'required' => false, 64 | 'allow_multiple' => false, 65 | ], 66 | '@version' => [ 67 | 'required' => false, 68 | 'allow_multiple' => false, 69 | ], 70 | '@see' => [ 71 | 'required' => false, 72 | 'allow_multiple' => true, 73 | ], 74 | '@deprecated' => [ 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 [ 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 = [$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/Tests/Arrays/ArrayDeclarationUnitTest.inc.fixed: -------------------------------------------------------------------------------- 1 | 201, 7 | 'unsupported_response_type' => 400, 8 | 'invalid_scope' => 400, 9 | 'temporarily_unavailable' => 400, 10 | 'invalid_grant' => [ 11 | 'invalid_data' => 400, 12 | 'access_denied' => 401, 13 | 'forbidden' => 403, 14 | 'resource_not_found' => 404, 15 | ], 16 | 'conflict' => 409, 17 | ]; 18 | 19 | public $globals = [ 20 | 'before' => [ 21 | // 'csrf' 22 | ], 23 | 'after' => [ 24 | 'toolbar' 25 | ], 26 | ]; 27 | 28 | protected $defaultHTTPMethods = [ 29 | 'options', 30 | 'get', 31 | 'head', 32 | ]; 33 | 34 | protected function isValidLuhn() 35 | { 36 | $sumTable = [ 37 | [ 38 | 0, 39 | 1, 40 | 2, 41 | 3, 42 | 4, 43 | 5, 44 | 6, 45 | 7, 46 | 8, 47 | 9, 48 | ], 49 | [ 50 | 0, 51 | 2, 52 | 4, 53 | 6, 54 | 8, 55 | 1, 56 | 3, 57 | 5, 58 | 7, 59 | 9, 60 | ], 61 | ]; 62 | } 63 | 64 | protected $cards = [ 65 | 'American Express' => [ 66 | 'name' => 'amex', 67 | 'length' => '15', 68 | 'prefixes' => '34,37', 69 | 'checkdigit' => true, 70 | ], 71 | 'DinersClub' => [ 72 | 'name' => 'dinersclub', 73 | 'length' => '14,16', 74 | 'prefixes' => '300,301,302,303,304,305,309,36,38,39,54,55', 75 | 'checkdigit' => true, 76 | ], 77 | 'MasterCard' => [ 78 | 'name' => 'mastercard', 79 | 'length' => '16', 80 | 'prefixes' => '51,52,53,54,55,22,23,24,25,26,27', 81 | 'checkdigit' => true, 82 | ], 83 | 'Visa' => [ 84 | 'name' => 'visa', 85 | 'length' => '13,16,19', 86 | 'prefixes' => '4', 87 | 'checkdigit' => true, 88 | ], 89 | // Canadian Cards 90 | 'CIBC Convenience Card' => [ 91 | 'name' => 'cibc', 92 | 'length' => '16', 93 | 'prefixes' => '4506', 94 | 'checkdigit' => false, 95 | ], 96 | 'Royal Bank of Canada Client Card' => [ 97 | 'name' => 'rbc', 98 | 'length' => '16', 99 | 'prefixes' => '45', 100 | 'checkdigit' => false, 101 | ], 102 | 'TD Canada Trust Access Card' => [ 103 | 'name' => 'tdtrust', 104 | 'length' => '16', 105 | 'prefixes' => '589297', 106 | 'checkdigit' => false, 107 | ], 108 | ]; 109 | 110 | $a = 111 | // comment 112 | [ 113 | 'a', 114 | 'b', 115 | ]; 116 | 117 | $a = /* comment */ [ 118 | 'a', 119 | 'b', 120 | ]; 121 | 122 | protected static $statusCodes = [ 123 | // 1xx: Informational 124 | 100 => 'Continue', 125 | 101 => 'Switching Protocols', 126 | 102 => 'Processing', // http://www.iana.org/go/rfc2518 127 | // 2xx: Success 128 | 200 => 'OK', 129 | ]; 130 | 131 | public function error() 132 | { 133 | return [ 134 | 'code' => '', 135 | 'message' => pg_last_error($this->connID), 136 | ]; 137 | } 138 | 139 | public function error2() 140 | { 141 | $this->QBWhere[] = [ 142 | 'condition' => $like_statement, 143 | 'escape' => $escape, 144 | ]; 145 | 146 | $this->QBWhere[] = [ 147 | 'condition' => $like_statement, 148 | 'escape' => $escape, 149 | ]; 150 | } 151 | 152 | public $arr = []; 153 | 154 | public $arr = []; 155 | 156 | public $arr = [1]; 157 | 158 | public $arr = [ 159 | 'a' => 1, 160 | 'b' => 2, 161 | 'c' => 3, 162 | ]; 163 | 164 | $cache->save($cacheKeyPagination, [ 165 | 'page' => $this->pager->getCurrentPage(), 166 | 'perPage' => $this->pager->getPerPage(), 167 | 'total' => $this->pager->getPerPage() * $this->pager->getPageCount(), 168 | ] , 15); 169 | 170 | return preg_replace([ 171 | '##i', 172 | '#`]+)).*?\>#i', 173 | ], '\\2', $str 174 | ); 175 | 176 | $this->db->table($this->table) 177 | ->insert([ 178 | 'version' => $version, 179 | 'name' => $this->name, 180 | ]); 181 | 182 | $message->setCustomProperty('user_info', [ 183 | 'id' => $id, 184 | 'name' => 'Test message', 185 | ]); 186 | 187 | $users = [ 188 | [ 189 | 'id' => 1, 190 | 'name' => 'John', 191 | 'email' => 'john@example.com', 192 | 'fact' => 'Loves coding', 193 | ], 194 | [ 195 | 'id' => 2, 196 | 'name' => 'Jim', 197 | 'email' => 'jim@example.com', 198 | 'fact' => 'Developed on CodeIgniter', 199 | ], 200 | [ 201 | 'id' => 3, 202 | 'name' => 'Jane', 203 | 'email' => 'jane@example.com', 204 | 'fact' => 'Lives in the USA', [ 205 | 'hobbies' => [ 206 | 'guitar', 207 | 'cycling', 208 | ], 209 | ] 210 | ], 211 | ]; 212 | } 213 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 | * Function Closing Brace Space Sniff 18 | * 19 | * Checks that there is [allowedLines|allowedNestedLines] empty lines before the 20 | * closing brace of a function. 21 | * 22 | * @author Louis Linehan 23 | */ 24 | class FunctionClosingBraceSpaceSniff implements Sniff 25 | { 26 | 27 | /** 28 | * A list of tokenizers this sniff supports. 29 | * 30 | * @var array 31 | */ 32 | public $supportedTokenizers = [ 33 | 'PHP', 34 | 'JS', 35 | ]; 36 | 37 | /** 38 | * Allowed lines before a closing function bracket. 39 | * 40 | * @var array 41 | */ 42 | public $allowedLines = 0; 43 | 44 | /** 45 | * Allowed spaces before a closing function bracket. 46 | * 47 | * @var array 48 | */ 49 | public $allowedNestedLines = 0; 50 | 51 | 52 | /** 53 | * Returns an array of tokens this test wants to listen for. 54 | * 55 | * @return array 56 | */ 57 | public function register() 58 | { 59 | return [ 60 | T_FUNCTION, 61 | T_CLOSURE, 62 | ]; 63 | 64 | }//end register() 65 | 66 | 67 | /** 68 | * Processes this test, when one of its tokens is encountered. 69 | * 70 | * @param File $phpcsFile The file being scanned. 71 | * @param int $stackPtr The position of the current token 72 | * in the stack passed in $tokens. 73 | * 74 | * @return void 75 | */ 76 | public function process(File $phpcsFile, $stackPtr) 77 | { 78 | $tokens = $phpcsFile->getTokens(); 79 | 80 | if (isset($tokens[$stackPtr]['scope_closer']) === false) { 81 | // Probably an interface method. 82 | return; 83 | } 84 | 85 | $closeBrace = $tokens[$stackPtr]['scope_closer']; 86 | $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBrace - 1), null, true); 87 | 88 | // Special case for empty JS functions. 89 | if ($phpcsFile->tokenizerType === 'JS' && $prevContent === $tokens[$stackPtr]['scope_opener']) { 90 | // In this case, the opening and closing brace must be 91 | // right next to each other. 92 | if ($tokens[$stackPtr]['scope_closer'] !== ($tokens[$stackPtr]['scope_opener'] + 1)) { 93 | $error = 'The opening and closing braces of empty functions must be directly next to each other; e.g., function () {}'; 94 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBetween'); 95 | if ($fix === true) { 96 | $phpcsFile->fixer->beginChangeset(); 97 | for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $closeBrace; $i++) { 98 | $phpcsFile->fixer->replaceToken($i, ''); 99 | } 100 | 101 | $phpcsFile->fixer->endChangeset(); 102 | } 103 | } 104 | 105 | return; 106 | } 107 | 108 | $nestedFunction = false; 109 | if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true 110 | || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true 111 | || isset($tokens[$stackPtr]['nested_parenthesis']) === true 112 | ) { 113 | $nestedFunction = true; 114 | } 115 | 116 | $braceLine = $tokens[$closeBrace]['line']; 117 | $prevLine = $tokens[$prevContent]['line']; 118 | $found = ($braceLine - $prevLine - 1); 119 | 120 | $afterKeyword = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 121 | $beforeKeyword = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 122 | if ($nestedFunction === true) { 123 | if ($found < 0) { 124 | $error = 'Closing brace of nested function must be on a new line'; 125 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'ContentBeforeClose'); 126 | if ($fix === true) { 127 | $phpcsFile->fixer->addNewlineBefore($closeBrace); 128 | } 129 | } else if ($found > $this->allowedNestedLines) { 130 | $error = 'Expected %s blank lines before closing brace of nested function; %s found'; 131 | $data = [ 132 | $this->allowedNestedLines, 133 | $found, 134 | ]; 135 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeNestedClose', $data); 136 | 137 | if ($fix === true) { 138 | $phpcsFile->fixer->beginChangeset(); 139 | $changeMade = false; 140 | for ($i = ($prevContent + 1); $i < $closeBrace; $i++) { 141 | // Try to maintain indentation. 142 | if ($tokens[$i]['line'] === ($braceLine - 1)) { 143 | break; 144 | } 145 | 146 | $phpcsFile->fixer->replaceToken($i, ''); 147 | $changeMade = true; 148 | } 149 | 150 | // Special case for when the last content contains the newline 151 | // token as well, like with a comment. 152 | if ($changeMade === false) { 153 | $phpcsFile->fixer->replaceToken(($prevContent + 1), ''); 154 | } 155 | 156 | $phpcsFile->fixer->endChangeset(); 157 | }//end if 158 | }//end if 159 | } else { 160 | if ($found !== (int) $this->allowedLines) { 161 | if ($this->allowedLines === 1) { 162 | $plural = ''; 163 | } else { 164 | $plural = 's'; 165 | } 166 | 167 | $error = 'Expected %s blank line%s before closing function brace; %s found'; 168 | $data = [ 169 | $this->allowedLines, 170 | $plural, 171 | $found, 172 | ]; 173 | $fix = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeClose', $data); 174 | 175 | if ($fix === true) { 176 | if ($found > $this->allowedLines) { 177 | $phpcsFile->fixer->beginChangeset(); 178 | for ($i = ($prevContent + 1); $i < ($closeBrace); $i++) { 179 | $phpcsFile->fixer->replaceToken($i, ''); 180 | } 181 | 182 | $phpcsFile->fixer->replaceToken(($closeBrace - 1), $phpcsFile->eolChar); 183 | $phpcsFile->fixer->endChangeset(); 184 | } else { 185 | $phpcsFile->fixer->addNewlineBefore($closeBrace); 186 | } 187 | } 188 | }//end if 189 | }//end if 190 | 191 | }//end process() 192 | 193 | 194 | }//end class 195 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/PHP/ForbiddenFunctionsSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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|null) 34 | */ 35 | public $forbiddenFunctions = ['sizeof' => 'count']; 36 | 37 | /** 38 | * A cache of forbidden function names, for faster lookups. 39 | * 40 | * @var array(string) 41 | */ 42 | protected $forbiddenFunctionNames = []; 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 [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 = []; 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 = [ 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 = [$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/NamingConventions/ValidVariableNameSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 = [ 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 = [ 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([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([T_WHITESPACE], ($objOperator + 1), null, true); 69 | if ($tokens[$var]['code'] === T_STRING) { 70 | $bracket = $phpcsFile->findNext([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 = [$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([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, [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 = [$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 = [$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 = [$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 = [ 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 = [$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 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 = [ 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 [ 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 = [$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 = [ 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 = [ 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 = [$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 = [$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 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 0 33 | 34 | 37 | 38 | 41 | 42 | 43 | 0 44 | 45 | 46 | 0 47 | 48 | 49 | 0 50 | 51 | 52 | 0 53 | 54 | 55 | 0 56 | 57 | 60 | 61 | 64 | 65 | warning 66 | 67 | 68 | 71 | 72 | Comments should not appear after statements 73 | 74 | 77 | 78 | 81 | 82 | 90 | 93 | 96 | 97 | 101 | 102 | 105 | 106 | 109 | 110 | 0 111 | 112 | 115 | 116 | 119 | 120 | 123 | 124 | 127 | 128 | 131 | 132 | 135 | 136 | 142 | 143 | 150 | 151 | 154 | 155 | 164 | 165 | 166 | 167 | 170 | 171 | 172 | 173 | 174 | 175 | 178 | 179 | 182 | 183 | 186 | 187 | 188 | 189 | 190 | 191 | 194 | 195 | 198 | 199 | 202 | 203 | 206 | 207 | 210 | 211 | 214 | 215 | 218 | 219 | 222 | 223 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 236 | 237 | 240 | 241 | 244 | 245 | 246 | 247 | 248 | 249 | 252 | 253 | 256 | 257 | 261 | 262 | 265 | 266 | 269 | 270 | 273 | 274 | 277 | 278 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 290 | 291 | 292 | 293 | 294 | 295 | 298 | 299 | 302 | 303 | 306 | 307 | 310 | 311 | 314 | 315 | 319 | 320 | 326 | 327 | 330 | 331 | 334 | 335 | 340 | 341 | 344 | 345 | 346 | 347 | 348 | 349 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Commenting/FileCommentSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 = [ 35 | '@package' => [ 36 | 'required' => false, 37 | 'allow_multiple' => false, 38 | ], 39 | '@subpackage' => [ 40 | 'required' => false, 41 | 'allow_multiple' => false, 42 | ], 43 | '@category' => [ 44 | 'required' => false, 45 | 'allow_multiple' => false, 46 | ], 47 | '@author' => [ 48 | 'required' => false, 49 | 'allow_multiple' => true, 50 | ], 51 | '@copyright' => [ 52 | 'required' => false, 53 | 'allow_multiple' => true, 54 | ], 55 | '@license' => [ 56 | 'required' => false, 57 | 'allow_multiple' => false, 58 | ], 59 | '@link' => [ 60 | 'required' => false, 61 | 'allow_multiple' => true, 62 | ], 63 | '@since' => [ 64 | 'required' => false, 65 | 'allow_multiple' => false, 66 | ], 67 | '@version' => [ 68 | 'required' => false, 69 | 'allow_multiple' => false, 70 | ], 71 | '@see' => [ 72 | 'required' => false, 73 | 'allow_multiple' => true, 74 | ], 75 | '@deprecated' => [ 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 [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 | // Check there is 1 empty line after. 155 | $commentCloser = $tokens[$commentStart]['comment_closer']; 156 | $nextContentPtr = $phpcsFile->findNext(T_WHITESPACE, ($commentCloser + 1), null, true); 157 | $lineDiff = ($tokens[$nextContentPtr]['line'] - $tokens[$commentCloser]['line']); 158 | if ($lineDiff === 1) { 159 | $data = [1]; 160 | $error = 'Expected %s empty line after file doc comment'; 161 | $fix = $phpcsFile->addFixableError($error, ($commentCloser + 1), 'NoEmptyLineAfterFileDocComment', $data); 162 | if ($fix === true) { 163 | $phpcsFile->fixer->beginChangeset(); 164 | $phpcsFile->fixer->addNewlineBefore($nextContentPtr); 165 | $phpcsFile->fixer->endChangeset(); 166 | } 167 | } 168 | 169 | // Ignore the rest of the file. 170 | return ($phpcsFile->numTokens + 1); 171 | 172 | }//end process() 173 | 174 | 175 | /** 176 | * Processes each required or optional tag. 177 | * 178 | * @param File $phpcsFile The file being scanned. 179 | * @param int $stackPtr The position of the current token 180 | * in the stack passed in $tokens. 181 | * @param int $commentStart Position in the stack where the comment started. 182 | * 183 | * @return void 184 | */ 185 | protected function processTags(File $phpcsFile, $stackPtr, $commentStart) 186 | { 187 | $tokens = $phpcsFile->getTokens(); 188 | 189 | if (get_class($this) === 'FileCommentSniff') { 190 | $docBlock = 'file'; 191 | } else { 192 | $docBlock = 'class'; 193 | } 194 | 195 | $commentEnd = $tokens[$commentStart]['comment_closer']; 196 | 197 | $foundTags = []; 198 | $tagTokens = []; 199 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) { 200 | $name = $tokens[$tag]['content']; 201 | if (isset($this->tags[$name]) === false) { 202 | continue; 203 | } 204 | 205 | if ($this->tags[$name]['allow_multiple'] === false && isset($tagTokens[$name]) === true) { 206 | $error = 'Only one %s tag is allowed in a %s comment'; 207 | $data = [ 208 | $name, 209 | $docBlock, 210 | ]; 211 | $phpcsFile->addError($error, $tag, 'Duplicate'.ucfirst(substr($name, 1)).'Tag', $data); 212 | } 213 | 214 | $foundTags[] = $name; 215 | $tagTokens[$name][] = $tag; 216 | 217 | $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); 218 | if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { 219 | $error = 'Content missing for %s tag in %s comment'; 220 | $data = [ 221 | $name, 222 | $docBlock, 223 | ]; 224 | $phpcsFile->addError($error, $tag, 'Empty'.ucfirst(substr($name, 1)).'Tag', $data); 225 | continue; 226 | } 227 | }//end foreach 228 | 229 | // Check if the tags are in the correct position. 230 | $pos = 0; 231 | foreach ($this->tags as $tag => $tagData) { 232 | if (isset($tagTokens[$tag]) === false) { 233 | if ($tagData['required'] === true) { 234 | $error = 'Missing %s tag in %s comment'; 235 | $data = [ 236 | $tag, 237 | $docBlock, 238 | ]; 239 | $phpcsFile->addError($error, $commentEnd, 'Missing'.ucfirst(substr($tag, 1)).'Tag', $data); 240 | } 241 | 242 | continue; 243 | } else { 244 | $method = 'process'.substr($tag, 1); 245 | if (method_exists($this, $method) === true) { 246 | // Process each tag if a method is defined. 247 | call_user_func([$this, $method], $phpcsFile, $tagTokens[$tag]); 248 | } 249 | } 250 | 251 | if (isset($foundTags[$pos]) === false) { 252 | break; 253 | } 254 | 255 | if ($foundTags[$pos] !== $tag) { 256 | $error = 'The tag in position %s should be the %s tag'; 257 | $data = [ 258 | ($pos + 1), 259 | $tag, 260 | ]; 261 | $phpcsFile->addError($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1)).'TagOrder', $data); 262 | } 263 | 264 | // Account for multiple tags. 265 | $pos++; 266 | while (isset($foundTags[$pos]) === true && $foundTags[$pos] === $tag) { 267 | $pos++; 268 | } 269 | }//end foreach 270 | 271 | }//end processTags() 272 | 273 | 274 | /** 275 | * Process the package tag. 276 | * 277 | * @param File $phpcsFile The file being scanned. 278 | * @param array $tags The tokens for these tags. 279 | * 280 | * @return void 281 | */ 282 | protected function processPackage(File $phpcsFile, array $tags) 283 | { 284 | $tokens = $phpcsFile->getTokens(); 285 | foreach ($tags as $tag) { 286 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 287 | // No content. 288 | continue; 289 | } 290 | 291 | $content = $tokens[($tag + 2)]['content']; 292 | 293 | $isInvalidPackage = false; 294 | 295 | if (strpos($content, '/') !== false) { 296 | $parts = explode('/', $content); 297 | $newName = join('\\', $parts); 298 | 299 | $isInvalidPackage = true; 300 | } 301 | 302 | if ($isInvalidPackage === true) { 303 | $error = 'Package name "%s" is not valid. Use "%s" instead'; 304 | $validName = trim($newName, '_'); 305 | $data = [ 306 | $content, 307 | $validName, 308 | ]; 309 | $phpcsFile->addWarning($error, $tag, 'InvalidPackage', $data); 310 | } 311 | }//end foreach 312 | 313 | }//end processPackage() 314 | 315 | 316 | /** 317 | * Process the subpackage tag. 318 | * 319 | * @param File $phpcsFile The file being scanned. 320 | * @param array $tags The tokens for these tags. 321 | * 322 | * @return void 323 | */ 324 | protected function processSubpackage(File $phpcsFile, array $tags) 325 | { 326 | $tokens = $phpcsFile->getTokens(); 327 | foreach ($tags as $tag) { 328 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 329 | // No content. 330 | continue; 331 | } 332 | 333 | $warning = 'The subpackage tag is considered deprecated. It is recommended to use the @package tag instead.'; 334 | 335 | $phpcsFile->addWarning($warning, $tag, 'SubpackageDepreciated'); 336 | }//end foreach 337 | 338 | }//end processSubpackage() 339 | 340 | 341 | /** 342 | * Process the category tag. 343 | * 344 | * @param File $phpcsFile The file being scanned. 345 | * @param array $tags The tokens for these tags. 346 | * 347 | * @return void 348 | */ 349 | protected function processCategory(File $phpcsFile, array $tags) 350 | { 351 | $tokens = $phpcsFile->getTokens(); 352 | foreach ($tags as $tag) { 353 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 354 | // No content. 355 | continue; 356 | } 357 | 358 | $warning = 'The category tag is considered deprecated. It is recommended to use the @package tag instead.'; 359 | 360 | $phpcsFile->addWarning($warning, $tag, 'CategoryDepreciated'); 361 | }//end foreach 362 | 363 | }//end processCategory() 364 | 365 | 366 | /** 367 | * Process the author tag(s) that this header comment has. 368 | * 369 | * @param File $phpcsFile The file being scanned. 370 | * @param array $tags The tokens for these tags. 371 | * 372 | * @return void 373 | */ 374 | protected function processAuthor(File $phpcsFile, array $tags) 375 | { 376 | $tokens = $phpcsFile->getTokens(); 377 | foreach ($tags as $tag) { 378 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 379 | // No content. 380 | continue; 381 | } 382 | 383 | $content = $tokens[($tag + 2)]['content']; 384 | 385 | // If it has an @ it's probably contains email address. 386 | if (strrpos($content, '@') !== false) { 387 | $local = '\da-zA-Z-_+'; 388 | // Dot character cannot be the first or last character in the local-part. 389 | $localMiddle = $local.'.\w'; 390 | if (preg_match('/^([^<]*)\s+<(['.$local.'](['.$localMiddle.']*['.$local.'])*@[\da-zA-Z][-.\w]*[\da-zA-Z]\.[a-zA-Z]{2,7})>$/', $content) === 0) { 391 | $error = '"@author" with email must be "Display Name "'; 392 | $phpcsFile->addError($error, $tag, 'InvalidAuthors'); 393 | } 394 | } 395 | } 396 | 397 | }//end processAuthor() 398 | 399 | 400 | /** 401 | * Process the copyright tags. 402 | * 403 | * @param File $phpcsFile The file being scanned. 404 | * @param array $tags The tokens for these tags. 405 | * 406 | * @return void 407 | */ 408 | protected function processCopyright(File $phpcsFile, array $tags) 409 | { 410 | $tokens = $phpcsFile->getTokens(); 411 | foreach ($tags as $tag) { 412 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 413 | // No content. 414 | continue; 415 | } 416 | 417 | $content = $tokens[($tag + 2)]['content']; 418 | $matches = []; 419 | if (preg_match('/^([0-9]{4})((.{1})([0-9]{4}))? (.+)$/', $content, $matches) !== 0) { 420 | // Check earliest-latest year order. 421 | if ($matches[3] !== '') { 422 | if ($matches[3] !== '-') { 423 | $error = 'A hyphen must be used between the earliest and latest year'; 424 | $phpcsFile->addError($error, $tag, 'CopyrightHyphen'); 425 | } 426 | 427 | if ($matches[4] !== '' && $matches[4] < $matches[1]) { 428 | $error = "Invalid year span \"$matches[1]$matches[3]$matches[4]\" found; consider \"$matches[4]-$matches[1]\" instead"; 429 | $phpcsFile->addWarning($error, $tag, 'InvalidCopyright'); 430 | } 431 | } 432 | } else { 433 | $error = '"@copyright" must be "YYYY [- YYYY] Name of the copyright holder"'; 434 | $phpcsFile->addError($error, $tag, 'IncompleteCopyright'); 435 | } 436 | }//end foreach 437 | 438 | }//end processCopyright() 439 | 440 | 441 | /** 442 | * Process the license tag. 443 | * 444 | * @param File $phpcsFile The file being scanned. 445 | * @param array $tags The tokens for these tags. 446 | * 447 | * @return void 448 | */ 449 | protected function processLicense(File $phpcsFile, array $tags) 450 | { 451 | $tokens = $phpcsFile->getTokens(); 452 | foreach ($tags as $tag) { 453 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 454 | // No content. 455 | continue; 456 | } 457 | 458 | $content = $tokens[($tag + 2)]['content']; 459 | $matches = []; 460 | preg_match('/^([^\s]+)\s+(.*)/', $content, $matches); 461 | 462 | if (count($matches) !== 3) { 463 | $error = '@license tag must contain a URL and a license name'; 464 | $phpcsFile->addError($error, $tag, 'IncompleteLicense'); 465 | } 466 | 467 | // Check the url is before the text part if it's included. 468 | $parts = explode(' ', $content); 469 | if ((count($parts) > 1)) { 470 | $matches = []; 471 | preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $parts[0], $matches); 472 | if (count($matches) !== 1) { 473 | $error = 'The URL must come before the license name'; 474 | $phpcsFile->addError($error, $tag, 'LicenseURLNotFirst'); 475 | } 476 | } 477 | }//end foreach 478 | 479 | }//end processLicense() 480 | 481 | 482 | /** 483 | * Process the link tag. 484 | * 485 | * @param File $phpcsFile The file being scanned. 486 | * @param array $tags The tokens for these tags. 487 | * 488 | * @return void 489 | */ 490 | protected function processLink(File $phpcsFile, array $tags) 491 | { 492 | $tokens = $phpcsFile->getTokens(); 493 | foreach ($tags as $tag) { 494 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 495 | // No content. 496 | continue; 497 | } 498 | 499 | $content = $tokens[($tag + 2)]['content']; 500 | $matches = []; 501 | 502 | // Check the url is before the text part if it's included. 503 | $parts = explode(' ', $content); 504 | if ((count($parts) > 1)) { 505 | $matches = []; 506 | preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $parts[0], $matches); 507 | if (count($matches) !== 1) { 508 | $error = 'The URL must come before the description'; 509 | $phpcsFile->addError($error, $tag, 'LinkURLNotFirst'); 510 | } 511 | } 512 | }//end foreach 513 | 514 | }//end processLink() 515 | 516 | 517 | /** 518 | * Process the version tag. 519 | * 520 | * @param File $phpcsFile The file being scanned. 521 | * @param array $tags The tokens for these tags. 522 | * 523 | * @return void 524 | */ 525 | protected function processVersion(File $phpcsFile, array $tags) 526 | { 527 | $tokens = $phpcsFile->getTokens(); 528 | foreach ($tags as $tag) { 529 | if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { 530 | // No content. 531 | continue; 532 | } 533 | 534 | $content = $tokens[($tag + 2)]['content']; 535 | // Split into parts if content has a space. 536 | $parts = explode(' ', $content); 537 | // Check if the first part contains a semantic version number. 538 | $matches = []; 539 | preg_match('/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/', $parts[0], $matches); 540 | 541 | if (strstr($content, 'CVS:') === false 542 | && strstr($content, 'SVN:') === false 543 | && strstr($content, 'GIT:') === false 544 | && strstr($content, 'HG:') === false 545 | && count($matches) === 0 546 | ) { 547 | $error = 'It is recommended that @version is a semantic version number or is a VCS version vector "GIT: " or "SVN: " or "HG: " or "CVS: ".'; 548 | $phpcsFile->addWarning($error, $tag, 'InvalidVersion'); 549 | } 550 | }//end foreach 551 | 552 | }//end processVersion() 553 | 554 | 555 | }//end class 556 | -------------------------------------------------------------------------------- /CodeIgniter4/Sniffs/Functions/FunctionDeclarationSniff.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2017 British Columbia Institute of Technology 8 | * @license https://github.com/bcit-ci/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 = [ 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 [ 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 = [$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 = [$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 = [$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 = [$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 = [$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 = [ 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 | --------------------------------------------------------------------------------