├── .gitignore ├── .php-cs-fixer.dist.php ├── .php-cs-fixer.dist.php.example ├── LICENSE ├── composer.json ├── loader.php ├── readme.md └── src ├── BlankLineAfterClassOpeningFixer.php ├── Fixer.php ├── FixerName.php └── SpaceInsideParenthesisFixer.php /.gitignore: -------------------------------------------------------------------------------- 1 | .php_cs.cache 2 | 3 | # OS generated files # 4 | ###################### 5 | .DS_Store 6 | .DS_Store? 7 | ._* 8 | .Spotlight-V100 9 | .Trashes 10 | ehthumbs.db 11 | Thumbs.db 12 | /node_modules 13 | /vendor/ 14 | /.php-cs-fixer.cache 15 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | exclude('vendors') 5 | ->in(__DIR__) 6 | ; 7 | 8 | $config = new PhpCsFixer\Config(); 9 | $config 10 | ->setRiskyAllowed(true) 11 | ->setRules([ 12 | '@PHP74Migration' => true, 13 | '@PHP74Migration:risky' => true, 14 | '@PHPUnit75Migration:risky' => true, 15 | '@PhpCsFixer' => true, 16 | '@PhpCsFixer:risky' => true, 17 | 'general_phpdoc_annotation_remove' => ['annotations' => ['expectedDeprecation']], // one should use PHPUnit built-in method instead 18 | 'heredoc_indentation' => false, // TODO switch on when # of PR's is lower 19 | 'modernize_strpos' => true, // needs PHP 8+ or polyfill 20 | 'use_arrow_functions' => false, // TODO switch on when # of PR's is lower 21 | ]) 22 | ->setFinder($finder) 23 | ; 24 | 25 | return $config; 26 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php.example: -------------------------------------------------------------------------------- 1 | exclude('node_modules') 6 | ->exclude('vendors') 7 | ->in( __DIR__ ) 8 | ; 9 | 10 | $config = new PhpCsFixer\Config(); 11 | $config 12 | ->registerCustomFixers([ 13 | new WeDevs\Fixer\SpaceInsideParenthesisFixer(), 14 | new WeDevs\Fixer\BlankLineAfterClassOpeningFixer() 15 | ]) 16 | ->setRiskyAllowed(true) 17 | ->setUsingCache(false) 18 | ->setRules( WeDevs\Fixer\Fixer::rules() ) 19 | ->setFinder( $finder ) 20 | ; 21 | 22 | return $config; 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Tareq Hasan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tareq1988/wp-php-cs-fixer", 3 | "type": "library", 4 | "description": "WordPress rules for php-cs-fixer", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Tareq Hasan", 9 | "email": "tareq@wedevs.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "autoload": { 14 | "psr-4": { 15 | "WeDevs\\Fixer\\": "src" 16 | } 17 | }, 18 | "require": { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /loader.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | require_once __DIR__.'/src/Fixer.php'; 15 | 16 | require_once __DIR__.'/src/FixerName.php'; 17 | 18 | require_once __DIR__.'/src/SpaceInsideParenthesisFixer.php'; 19 | 20 | require_once __DIR__.'/src/BlankLineAfterClassOpeningFixer.php'; 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PHP CS Fixer: WordPress fixers 2 | 3 | A set of custom fixers for [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer), specially for WordPress. 4 | 5 | ### What is php-cs-fixer? 6 | 7 | The [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) or [PHP Coding Standards Fixer](https://cs.symfony.com/) is an awesome tool created by the super awesome people at [Symfony](https://symfony.com/). 8 | 9 | It helps your PHP code/repository to follow a certain coding standard defined by you team. 10 | 11 | ### What are WordPress Fixers? 12 | 13 | WordPress uses a bit different coding standard from the rest of the world. It doesn't follow PSR standards yet. 14 | 15 | The aim of this WordPress specific fixers is to allow WordPress developers to standardize their code according to the [WordPress Coding Standard](https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/). 16 | 17 | #### Available Fixers 18 | 19 | 1. **Space Inside Parenthesis**: This fixer ensures that when defining functions, if/else blocks, or control structures which have parenthesis, a space after the starting parenthesis and before the ending parenthesis exists. Rule name: `WeDevs/space_inside_parenthesis`. 20 | 2. **Blank Line After Class Opening**: PSR standards have the class opening brace on a new line, WordPress follows the same line standard. This ensures after the opening brace, one blank line exists (equals to two `\n`). Rule name: `WeDevs/blank_line_after_class_opening`. 21 | 22 | ## Installation 23 | PHP CS Fixer: custom fixers can be installed by running: 24 | 25 | ```bash 26 | composer require --dev tareq1988/wp-php-cs-fixer 27 | ``` 28 | 29 | ## Usage 30 | In your PHP CS Fixer configuration (`.php-cs-fixer.dist.php`) register fixers and use them: 31 | 32 | ```diff 33 | exclude('node_modules') 39 | ->exclude('vendors') 40 | ->in( __DIR__ ) 41 | ; 42 | 43 | $config = new PhpCsFixer\Config(); 44 | $config 45 | + ->registerCustomFixers([ 46 | + new WeDevs\Fixer\SpaceInsideParenthesisFixer(), 47 | + new WeDevs\Fixer\BlankLineAfterClassOpeningFixer() 48 | + ]) 49 | + ->setRules( WeDevs\Fixer\Fixer::rules() ) 50 | ->setFinder( $finder ) 51 | ; 52 | 53 | return $config; 54 | ``` 55 | 56 | The `WeDevs\Fixer\Fixer::rules()` function simplifies the usage of the WordPress specific rules. However, if you want more control and have different taste, you can copy/paste the rules from the `WeDevs\Fixer\Fixer` class to the `.php_cs` file if you want to. 57 | 58 | ### Example File 59 | 60 | The example [.php_cs.example](https://github.com/tareq1988/wp-php-cs-fixer/blob/master/.php-cs-fixer.dist.php.example) file should be a fine starting point for your plugins. Just drop the file into your plugin folder by renaming to `.php-cs-fixer.dist.php` and you are good to go. 61 | 62 | Upon configuring everything, run `php-cs-fixer fix` from the commandline. 63 | -------------------------------------------------------------------------------- /src/BlankLineAfterClassOpeningFixer.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | final class BlankLineAfterClassOpeningFixer extends AbstractFixer implements WhitespacesAwareFixerInterface 23 | { 24 | use FixerName; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function getDefinition(): FixerDefinitionInterface 30 | { 31 | return new FixerDefinition( 32 | 'There should be one empty line after class opening brace.', 33 | [ 34 | new CodeSample( 35 | 'isAnyTokenKindsFound(Token::getClassyTokenKinds()); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | protected function applyFix(SplFileInfo $file, Tokens $tokens): void 59 | { 60 | foreach ($tokens as $index => $token) { 61 | if (!$token->isClassy()) { 62 | continue; 63 | } 64 | 65 | $startBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); 66 | 67 | if (!$tokens[$startBraceIndex + 1]->isWhitespace()) { 68 | continue; 69 | } 70 | 71 | $this->fixWhitespace($tokens, $startBraceIndex + 1); 72 | } 73 | } 74 | 75 | /** 76 | * Cleanup a whitespace token. 77 | * 78 | * @param int $index 79 | */ 80 | private function fixWhitespace(Tokens $tokens, $index): void 81 | { 82 | $content = $tokens[$index]->getContent(); 83 | 84 | // there should be two new lines 85 | if (2 !== substr_count($content, "\n")) { 86 | $ending = $this->whitespacesConfig->getLineEnding(); 87 | 88 | $emptyLines = $ending.$ending; 89 | $indent = 1 === Preg::match('/^.*\R( *)$/s', $content, $matches) ? $matches[1] : ''; 90 | 91 | $tokens[$index] = new Token([T_WHITESPACE, $emptyLines.$indent]); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Fixer.php: -------------------------------------------------------------------------------- 1 | true, 16 | 'align_multiline_comment' => true, 17 | 'array_syntax' => ['syntax' => 'short'], 18 | 'binary_operator_spaces' => [ 19 | 'operators' => ['=' => 'align', '=>' => 'align'], 20 | ], 21 | 'blank_line_after_opening_tag' => true, 22 | 'blank_line_before_statement' => [ 23 | 'statements' => ['return', 'try', 'if', 'while', 'for', 'foreach', 'do', 'case'], 24 | ], 25 | 'braces' => [ 26 | 'position_after_functions_and_oop_constructs' => 'same', 27 | 'allow_single_line_closure' => false, 28 | ], 29 | 'cast_spaces' => ['space' => 'single'], 30 | //'class_attributes_separation' => ['elements' => ['method', 'const', 'property']], 31 | 'class_definition' => ['single_line' => true], 32 | 'concat_space' => ['spacing' => 'one'], 33 | 'constant_case' => ['case' => 'lower'], 34 | 'dir_constant' => true, 35 | 'elseif' => true, 36 | 'full_opening_tag' => true, 37 | 'fully_qualified_strict_types' => true, 38 | 'function_declaration' => true, 39 | 'WeDevs/space_inside_parenthesis' => true, 40 | 'WeDevs/blank_line_after_class_opening' => true, 41 | 'function_typehint_space' => true, 42 | 'global_namespace_import' => ['import_classes' => true], 43 | 'include' => true, 44 | 'line_ending' => true, 45 | 'list_syntax' => ['syntax' => 'long'], 46 | 'lowercase_cast' => true, 47 | 'lowercase_keywords' => true, 48 | 'lowercase_static_reference' => true, 49 | 'magic_constant_casing' => true, 50 | 'magic_method_casing' => true, 51 | 'method_argument_space' => true, 52 | 'native_function_casing' => true, 53 | 'method_chaining_indentation' => true, 54 | 'native_function_type_declaration_casing' => true, 55 | 'new_with_braces' => true, 56 | 'no_alternative_syntax' => true, 57 | 'no_blank_lines_after_class_opening' => false, 58 | 'no_blank_lines_after_phpdoc' => true, 59 | 'no_empty_comment' => true, 60 | 'no_empty_phpdoc' => true, 61 | 'no_empty_statement' => true, 62 | 'no_extra_blank_lines' => ['tokens' => [ 63 | 'extra', 64 | 'parenthesis_brace_block', 65 | 'square_brace_block', 66 | 'throw', 67 | 'use', 68 | ]], 69 | 'no_leading_import_slash' => true, 70 | 'no_leading_namespace_whitespace' => true, 71 | 'no_mixed_echo_print' => true, 72 | 'no_multiline_whitespace_around_double_arrow' => true, 73 | 'no_short_bool_cast' => true, 74 | 'echo_tag_syntax' => ['format' => 'long'], 75 | 'no_singleline_whitespace_before_semicolons' => true, 76 | 'no_spaces_around_offset' => ['positions' => ['outside']], 77 | 'no_spaces_inside_parenthesis' => false, 78 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true], 79 | 'no_trailing_comma_in_list_call' => true, 80 | 'no_trailing_comma_in_singleline_array' => true, 81 | 'no_trailing_whitespace' => true, 82 | 'no_unneeded_control_parentheses' => true, 83 | 'no_unneeded_curly_braces' => true, 84 | 'no_unneeded_final_method' => true, 85 | 'no_unused_imports' => true, 86 | 'no_whitespace_before_comma_in_array' => true, 87 | 'no_whitespace_in_blank_line' => true, 88 | 'normalize_index_brace' => true, 89 | 'object_operator_without_whitespace' => true, 90 | 'ordered_imports' => true, 91 | 'php_unit_fqcn_annotation' => true, 92 | 'phpdoc_align' => [ 93 | 'align' => 'vertical', 94 | 'tags' => [ 95 | 'method', 96 | 'param', 97 | 'property', 98 | 'return', 99 | 'throws', 100 | 'type', 101 | 'var', 102 | ], 103 | ], 104 | 'phpdoc_annotation_without_dot' => true, 105 | 'phpdoc_indent' => true, 106 | 'general_phpdoc_tag_rename' => [ 107 | 'fix_annotation' => true, 108 | 'fix_inline' => true, 109 | ], 110 | 'phpdoc_no_access' => true, 111 | 'phpdoc_no_alias_tag' => true, 112 | 'phpdoc_no_package' => true, 113 | 'phpdoc_no_useless_inheritdoc' => true, 114 | 'phpdoc_return_self_reference' => true, 115 | 'phpdoc_scalar' => true, 116 | 'phpdoc_separation' => true, 117 | 'phpdoc_single_line_var_spacing' => true, 118 | 'phpdoc_to_comment' => true, 119 | 'phpdoc_trim' => true, 120 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 121 | 'phpdoc_types' => true, 122 | 'phpdoc_types_order' => [ 123 | 'null_adjustment' => 'always_last', 124 | 'sort_algorithm' => 'none', 125 | ], 126 | 'phpdoc_var_without_name' => true, 127 | 'return_type_declaration' => true, 128 | 'semicolon_after_instruction' => true, 129 | 'short_scalar_cast' => true, 130 | 'single_blank_line_before_namespace' => true, 131 | 'single_class_element_per_statement' => true, 132 | 'single_line_comment_style' => [ 133 | 'comment_types' => ['hash'], 134 | ], 135 | 'single_line_throw' => true, 136 | 'single_quote' => true, 137 | 'single_trait_insert_per_statement' => true, 138 | 'space_after_semicolon' => [ 139 | 'remove_in_empty_for_expressions' => true, 140 | ], 141 | 'standardize_increment' => true, 142 | 'standardize_not_equals' => true, 143 | 'ternary_operator_spaces' => true, 144 | 'trailing_comma_in_multiline' => [ 145 | 'elements' => ['arrays'], 146 | ], 147 | 'trim_array_spaces' => false, 148 | 'whitespace_after_comma_in_array' => true, 149 | ]; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/FixerName.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | final class SpaceInsideParenthesisFixer extends AbstractFixer 21 | { 22 | use FixerName; 23 | 24 | private $singleLineWhitespaceOptions = " \t"; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function getDefinition(): FixerDefinitionInterface 30 | { 31 | return new FixerDefinition( 32 | 'There MUST be a space after the opening parenthesis and a space before the closing parenthesis.', 33 | [ 34 | new CodeSample( 35 | 'isTokenKindFound('('); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | protected function applyFix(SplFileInfo $file, Tokens $tokens): void 67 | { 68 | foreach ($tokens as $index => $token) { 69 | if (!$token->equals('(')) { 70 | continue; 71 | } 72 | 73 | // don't process if the next token is `)` 74 | $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index); 75 | 76 | if (')' === $tokens[$nextMeaningfulTokenIndex]->getContent()) { 77 | continue; 78 | } 79 | 80 | $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); 81 | 82 | $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex); 83 | $afterParenthesisToken = $tokens[$afterParenthesisIndex]; 84 | 85 | if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) { 86 | $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']); 87 | $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex); 88 | 89 | // add single-line edge whitespaces inside use parentheses 90 | $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex); 91 | } 92 | 93 | // add single-line edge whitespaces inside parameters list parentheses 94 | $this->fixParenthesisInnerEdge($tokens, $index, $endParenthesisIndex); 95 | } 96 | } 97 | 98 | private function fixParenthesisInnerEdge(Tokens $tokens, $start, $end): void 99 | { 100 | // add single-line whitespace before ) 101 | if (!$tokens[$end - 1]->isWhitespace($this->singleLineWhitespaceOptions) && !str_contains($tokens[$end - 1]->getContent(), "\n")) { 102 | $tokens->ensureWhitespaceAtIndex($end, 0, ' '); 103 | } 104 | 105 | // add single-line whitespace after ( 106 | if (!$tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions) && !str_contains($tokens[$start + 1]->getContent(), "\n")) { 107 | $tokens->ensureWhitespaceAtIndex($start, 1, ' '); 108 | } 109 | } 110 | } 111 | --------------------------------------------------------------------------------