├── .gitignore ├── .php-cs-fixer.php ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── src └── Taptima │ └── CS │ ├── AbstractFixer.php │ ├── Fixer │ ├── AbstractOrderedClassElementsFixer.php │ ├── DoctrineMigrationsFixer.php │ └── OrderedSettersAndGettersFixer.php │ ├── Fixers.php │ ├── Priority.php │ ├── RuleSetFactory.php │ ├── TokenSignatures.php │ └── TokensAnalyzer.php ├── tests ├── Orchestra.php ├── Runner.php ├── TokensAnalyzerIntegration.php ├── TokensAnalyzerIntegration │ ├── InSwitch.php │ ├── LineIndentation.php │ ├── MethodArguments.php │ ├── NextSemiColon.php │ ├── Parenthesis.php │ ├── ReturnedType.php │ └── SizeOfTheLine.php ├── UseCase.php └── UseCase │ ├── DoctrineMigrations │ ├── UselessComments.php │ └── UselessGetDescription.php │ └── OrderedSettersAndGetters │ └── OrderedSettersAndGettersFirst.php └── tools ├── Utils.php ├── doc └── doc.twig /.gitignore: -------------------------------------------------------------------------------- 1 | .php_cs.cache 2 | vendor 3 | 4 | /bin/ 5 | .DS_Store 6 | .idea 7 | .env 8 | .php_cs.cache 9 | 10 | composer.lock 11 | 12 | *.sublime-project 13 | *.sublime-workspace -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | in(__DIR__) 6 | ->append([__FILE__]) 7 | ; 8 | 9 | $config = new PhpCsFixer\Config(); 10 | $config 11 | ->setRules([ 12 | '@Symfony' => true, 13 | '@DoctrineAnnotation' => true, 14 | '@PhpCsFixer' => true, 15 | '@PHP71Migration' => true, 16 | 17 | 'blank_line_after_namespace' => true, 18 | 'single_import_per_statement' => true, 19 | 'single_line_after_imports' => true, 20 | 'no_unused_imports' => true, 21 | 'clean_namespace' => true, 22 | 'lambda_not_used_import' => true, 23 | 'switch_continue_to_break' => true, 24 | 'no_alias_language_construct_call' => true, 25 | 'single_space_after_construct' => true, 26 | 'operator_linebreak' => true, 27 | 28 | 'ordered_imports' => true, 29 | 'global_namespace_import' => true, 30 | 'ordered_class_elements' => [ 31 | 'order' => [ 32 | 'use_trait', 33 | 'constant', 34 | 'constant_public', 35 | 'constant_protected', 36 | 'constant_private', 37 | 'property_public_static', 38 | 'property_public', 39 | 'property_protected_static', 40 | 'property_protected', 41 | 'property_private_static', 42 | 'property_private', 43 | 'construct', 44 | 'destruct', 45 | 'magic', 46 | 'phpunit', 47 | 'method_public_abstract', 48 | 'method_public_abstract_static', 49 | 'method_public_static', 50 | 'method_public', 51 | 'method_protected_abstract', 52 | 'method_protected_abstract_static', 53 | 'method_protected_static', 54 | 'method_protected', 55 | 'method_private_static', 56 | 'method_private', 57 | ], 58 | ], 59 | 60 | 'method_chaining_indentation' => false, 61 | 62 | 'nullable_type_declaration_for_default_null_value' => [ 63 | 'use_nullable_type_declaration' => false, 64 | ], 65 | 66 | 'self_static_accessor' => true, 67 | 68 | 'ternary_to_null_coalescing' => true, 69 | 'binary_operator_spaces' => [ 70 | 'default' => 'single_space', 71 | 'operators' => [ 72 | '=' => 'align_single_space_minimal', 73 | '+=' => 'align_single_space_minimal', 74 | '-=' => 'align_single_space_minimal', 75 | '/=' => 'align_single_space_minimal', 76 | '*=' => 'align_single_space_minimal', 77 | '%=' => 'align_single_space_minimal', 78 | '**=' => 'align_single_space_minimal', 79 | '=>' => 'align_single_space_minimal', 80 | ], 81 | ], 82 | 'yoda_style' => [ 83 | 'equal' => false, 84 | 'identical' => false, 85 | 'less_and_greater' => null, 86 | ], 87 | 'concat_space' => [ 88 | 'spacing' => 'one', 89 | ], 90 | 'array_syntax' => [ 91 | 'syntax' => 'short', 92 | ], 93 | 'braces' => true, 94 | 'elseif' => true, 95 | 'trim_array_spaces' => true, 96 | 97 | 'function_declaration' => true, 98 | 'no_spaces_after_function_name' => true, 99 | 'no_spaces_inside_parenthesis' => true, 100 | 101 | 'cast_spaces' => true, 102 | 'encoding' => true, 103 | 'full_opening_tag' => true, 104 | 'linebreak_after_opening_tag' => true, 105 | 'no_closing_tag' => true, 106 | 'indentation_type' => true, 107 | 'line_ending' => true, 108 | 'single_blank_line_at_eof' => false, 109 | 'no_trailing_whitespace' => true, 110 | 'lowercase_keywords' => true, 111 | 'no_whitespace_in_blank_line' => true, 112 | 'echo_tag_syntax' => true, 113 | 114 | 'doctrine_annotation_braces' => false, 115 | 'doctrine_annotation_array_assignment' => [ 116 | 'operator' => '=', 117 | ], 118 | 119 | 'align_multiline_comment' => [ 120 | 'comment_type' => 'all_multiline', 121 | ], 122 | 123 | 'no_superfluous_phpdoc_tags' => false, 124 | 'phpdoc_order' => true, 125 | 'phpdoc_separation' => true, 126 | 'phpdoc_var_without_name' => false, 127 | 'phpdoc_var_annotation_correct_order' => true, 128 | 'phpdoc_types_order' => [ 129 | 'sort_algorithm' => 'none', 130 | 'null_adjustment' => 'always_last', 131 | ], 132 | 'phpdoc_order_by_value' => [ 133 | 'annotations' => [ 134 | 'method', 135 | 'throws', 136 | 'author', 137 | 'property', 138 | 'internal', 139 | ], 140 | ], 141 | ]) 142 | ->setUsingCache(true) 143 | ->setFinder($finder) 144 | ; 145 | 146 | if (version_compare(PHP_VERSION, '7.1', '>=')) { 147 | $config->setRules(array_merge($config->getRules(), [ 148 | 'list_syntax' => [ 149 | 'syntax' => 'short', 150 | ], 151 | ])); 152 | } 153 | 154 | return $config; 155 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) taptima 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | THIS_FILE := $(lastword $(MAKEFILE_LIST)) 2 | 3 | -include .env 4 | 5 | cs: 6 | bin/php-cs-fixer fix --verbose 7 | 8 | cs-dry-run: 9 | bin/php-cs-fixer fix --verbose --dry-run 10 | 11 | c-inst: 12 | composer install 13 | 14 | gen-doc: 15 | tools/doc > README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-CS-Fixer 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/taptima/php-cs-fixer/v/stable)](https://packagist.org/packages/taptima/php-cs-fixer) 4 | [![License](https://poser.pugx.org/taptima/php-cs-fixer/license)](https://packagist.org/packages/taptima/php-cs-fixer) 5 | 6 | This repository appeared thanks to the [PedroTroller/PhpCSFixer-Custom-Fixers](https://github.com/PedroTroller/PhpCSFixer-Custom-Fixers) and the code from this repository is used here. 7 | 8 | # Installation 9 | 10 | ```bash 11 | composer require --dev taptima/php-cs-fixer dev-master 12 | ``` 13 | 14 | ### Configuration 15 | 16 | ```php 17 | // .php-cs-fixer.php 18 | registerCustomFixers(new Taptima\CS\Fixers()) 23 | // ... 24 | ; 25 | 26 | return $config; 27 | ``` 28 | 29 | You can also include the ruleset used by the Taptima company. It includes the @Symfony, @PSR and other rules to get the best codestyle result. 30 | @Taptima rule set can be viewed [here](https://github.com/taptima/php-cs-fixer/blob/master/src/Taptima/CS/RuleSetFactory.php#L15). 31 | ```php 32 | // .php-cs-fixer.php 33 | setRules( 37 | Taptima\CS\RuleSetFactory::create([ 38 | '@Taptima' => true, 39 | // other rules 40 | ]) 41 | ->taptima() 42 | ->getRules() 43 | ) 44 | ->registerCustomFixers( 45 | new Taptima\CS\Fixers() 46 | ) 47 | 48 | return $config; 49 | ``` 50 | 51 | # Fixers 52 | 53 | 54 | ## Taptima/doctrine_migrations 55 | 56 | Remove useless getDescription(), up(), down() and comments from Doctrine\Migrations\AbstractMigration if needed. 57 | 58 | ### Configuration 59 | 60 | ```php 61 | // .php-cs-fixer.php 62 | setRules([ 67 | // ... 68 | 'Taptima/doctrine_migrations' => true, 69 | // ... 70 | ]) 71 | // ... 72 | ->registerCustomFixers(new Taptima\CS\Fixers()) 73 | ; 74 | 75 | return $config; 76 | ``` 77 | 78 | **OR** using my [rule list builder](doc/rule-set-factory.md). 79 | 80 | ```php 81 | // .php-cs-fixer.php.dist 82 | setRules(Taptima\CS\RuleSetFactory::create() 87 | ->enable('Taptima/doctrine_migrations') 88 | ->getRules() 89 | ]) 90 | // ... 91 | ->registerCustomFixers(new Taptima\CS\Fixers()) 92 | ; 93 | 94 | return $config; 95 | ``` 96 | 97 | ### Fixes 98 | 99 | ```diff 100 | --- Original // 80 chars 101 | +++ New // 102 | @@ @@ // 103 | use Doctrine\DBAL\Schema\Schema; // 104 | use Doctrine\Migrations\AbstractMigration; // 105 | // 106 | -/** // 107 | - * Auto-generated Migration: Please modify to your needs! // 108 | - */ // 109 | final class Version20190323095102 extends AbstractMigration // 110 | { // 111 | - public function getDescription() // 112 | - { // 113 | - return ''; // 114 | - } // 115 | // 116 | public function up(Schema $schema) // 117 | { // 118 | - // this up() migration is auto-generated, please modify it to your needs// 119 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 120 | // 121 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 122 | @@ @@ // 123 | // 124 | public function down(Schema $schema) // 125 | { // 126 | - // this down() migration is auto-generated, please modify it to your needs 127 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 128 | // 129 | $this->addSql('DROP TABLE admin'); // 130 | } // 131 | } // 132 | // 133 | ``` 134 | ### Configuration 135 | 136 | ```php 137 | // .php-cs-fixer.php 138 | setRules([ 143 | // ... 144 | 'Taptima/doctrine_migrations' => [ 'instanceof' => [ 'Doctrine\Migrations\AbstractMigration' ] ], 145 | // ... 146 | ]) 147 | // ... 148 | ->registerCustomFixers(new Taptima\CS\Fixers()) 149 | ; 150 | 151 | return $config; 152 | ``` 153 | 154 | **OR** using my [rule list builder](doc/rule-set-factory.md). 155 | 156 | ```php 157 | // .php-cs-fixer.php.dist 158 | setRules(Taptima\CS\RuleSetFactory::create() 163 | ->enable('Taptima/doctrine_migrations', [ 'instanceof' => [ 'Doctrine\Migrations\AbstractMigration' ] ]) 164 | ->getRules() 165 | ]) 166 | // ... 167 | ->registerCustomFixers(new Taptima\CS\Fixers()) 168 | ; 169 | 170 | return $config; 171 | ``` 172 | 173 | ### Fixes 174 | 175 | ```diff 176 | --- Original // 80 chars 177 | +++ New // 178 | @@ @@ // 179 | use Doctrine\DBAL\Schema\Schema; // 180 | use Doctrine\Migrations\AbstractMigration; // 181 | // 182 | -/** // 183 | - * Auto-generated Migration: Please modify to your needs! // 184 | - */ // 185 | final class Version20190323095102 extends AbstractMigration // 186 | { // 187 | - public function getDescription() // 188 | - { // 189 | - return ''; // 190 | - } // 191 | // 192 | public function up(Schema $schema) // 193 | { // 194 | - // this up() migration is auto-generated, please modify it to your needs// 195 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 196 | // 197 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 198 | @@ @@ // 199 | // 200 | public function down(Schema $schema) // 201 | { // 202 | - // this down() migration is auto-generated, please modify it to your needs 203 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 204 | // 205 | $this->addSql('DROP TABLE admin'); // 206 | } // 207 | } // 208 | // 209 | ``` 210 | 211 | ## Taptima/ordered_setters_and_getters 212 | 213 | Class/interface/trait setters and getters MUST BE ordered (order is setter, isser, hasser, adder, remover, getter). 214 | 215 | ### Configuration 216 | 217 | ```php 218 | // .php-cs-fixer.php 219 | setRules([ 224 | // ... 225 | 'Taptima/ordered_setters_and_getters' => true, 226 | // ... 227 | ]) 228 | // ... 229 | ->registerCustomFixers(new Taptima\CS\Fixers()) 230 | ; 231 | 232 | return $config; 233 | ``` 234 | 235 | **OR** using my [rule list builder](doc/rule-set-factory.md). 236 | 237 | ```php 238 | // .php-cs-fixer.php.dist 239 | setRules(Taptima\CS\RuleSetFactory::create() 244 | ->enable('Taptima/ordered_setters_and_getters') 245 | ->getRules() 246 | ]) 247 | // ... 248 | ->registerCustomFixers(new Taptima\CS\Fixers()) 249 | ; 250 | 251 | return $config; 252 | ``` 253 | 254 | ### Fixes 255 | 256 | ```diff 257 | --- Original // 80 chars 258 | +++ New // 259 | @@ @@ // 260 | $this->firstName = $firstName; // 261 | } // 262 | // 263 | - public function setName($name) // 264 | + public function getFirstName() // 265 | { // 266 | - $this->name = $name; // 267 | + return $this->firstName; // 268 | } // 269 | // 270 | - public function isEnabled() // 271 | + public function setName($name) // 272 | { // 273 | - return $this->enabled; // 274 | + $this->name = $name; // 275 | } // 276 | // 277 | public function getName() // 278 | @@ @@ // 279 | return $this->name; // 280 | } // 281 | // 282 | - public function getIdentifier() // 283 | + public function isEnabled() // 284 | { // 285 | - return $this->identifier; // 286 | + return $this->enabled; // 287 | } // 288 | // 289 | - public function getFirstName() // 290 | + public function getIdentifier() // 291 | { // 292 | - return $this->firstName; // 293 | + return $this->identifier; // 294 | } // 295 | // 296 | public function enable() // 297 | @@ @@ // 298 | } // 299 | // 300 | /** // 301 | - * @return Item // 302 | - */ // 303 | - public function getItems() // 304 | - { // 305 | - return $this->items; // 306 | - } // 307 | - // 308 | - /** // 309 | * @param Item $item // 310 | * @return User // 311 | */ // 312 | @@ @@ // 313 | $this->items->removeElement($item); // 314 | // 315 | return $this; // 316 | + } // 317 | + // 318 | + /** // 319 | + * @return Item // 320 | + */ // 321 | + public function getItems() // 322 | + { // 323 | + return $this->items; // 324 | } // 325 | } // 326 | // 327 | ``` 328 | 329 | # Contributions 330 | 331 | Before to create a pull request to submit your contributon, you must: 332 | - run tests and be sure nothing is broken 333 | - rebuilt the documentation 334 | 335 | ## How to run tests 336 | 337 | ```bash 338 | composer tests 339 | ``` 340 | 341 | ## How to rebuild the documentation 342 | 343 | ```bash 344 | tools/doc > README.md 345 | ``` 346 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taptima/php-cs-fixer", 3 | "description": "Taptima PHP-CS-Fixer fixers", 4 | "license": "MIT", 5 | "keywords": [ 6 | "php-cs-fixer", 7 | "Code Quality" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "Mark Tertishniy", 12 | "email": "m@taptima.ru", 13 | "homepage": "https://taptima.ru", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=7.1", 19 | "doctrine/inflector": "^1.0", 20 | "friendsofphp/php-cs-fixer": "^2.18" 21 | }, 22 | "require-dev": { 23 | "webmozart/assert": "^1.3.0", 24 | "twig/twig": "^2.11.3" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Taptima\\CS\\": "src/Taptima/CS" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "tests\\": "tests" 34 | } 35 | }, 36 | "config": { 37 | "sort-packages": true, 38 | "bin-dir": "bin" 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "1.x-dev" 43 | } 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true, 47 | "scripts": { 48 | "tests": [ 49 | "tests\\Runner::run", 50 | "tests\\Orchestra::run" 51 | ], 52 | "php-cs-fixer": [ 53 | "php-cs-fixer fix --dry-run -vvv --diff" 54 | ], 55 | "lint": [ 56 | "@php-cs-fixer" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Taptima/CS/AbstractFixer.php: -------------------------------------------------------------------------------- 1 | getDocumentation(), 58 | array_map( 59 | function (array $configutation = null) { 60 | return new CodeSample($this->getSampleCode(), $configutation); 61 | }, 62 | $this->getSampleConfigurations() 63 | ) 64 | ); 65 | } 66 | 67 | /** 68 | * @return bool 69 | */ 70 | public function isDeprecated() 71 | { 72 | return false; 73 | } 74 | 75 | /** 76 | * @return string|null 77 | */ 78 | public function getDeprecationReplacement() 79 | { 80 | } 81 | 82 | /** 83 | * @return TokensAnalyzer 84 | */ 85 | protected function analyze(Tokens $tokens) 86 | { 87 | return new TokensAnalyzer($tokens); 88 | } 89 | 90 | /** 91 | * @param string|string[] $fqcn 92 | * 93 | * @return bool 94 | */ 95 | protected function hasUseStatements(Tokens $tokens, $fqcn) 96 | { 97 | return $this->getUseStatements($tokens, $fqcn) !== null; 98 | } 99 | 100 | /** 101 | * @param string|string[] $fqcn 102 | * 103 | * @return array|null 104 | */ 105 | protected function getUseStatements(Tokens $tokens, $fqcn) 106 | { 107 | if (\is_array($fqcn) === false) { 108 | $fqcn = explode('\\', $fqcn); 109 | } 110 | $sequence = [[T_USE]]; 111 | foreach ($fqcn as $component) { 112 | $sequence = array_merge( 113 | $sequence, 114 | [[T_STRING, $component], [T_NS_SEPARATOR]] 115 | ); 116 | } 117 | $sequence[\count($sequence) - 1] = ';'; 118 | 119 | return $tokens->findSequence($sequence); 120 | } 121 | 122 | /** 123 | * @param string|string[] $fqcn 124 | * 125 | * @return bool 126 | */ 127 | protected function extendsClass(Tokens $tokens, $fqcn) 128 | { 129 | if (\is_array($fqcn) === false) { 130 | $fqcn = explode('\\', $fqcn); 131 | } 132 | 133 | if ($this->hasUseStatements($tokens, $fqcn) === false) { 134 | return false; 135 | } 136 | 137 | return $tokens->findSequence([ 138 | [T_CLASS], 139 | [T_STRING], 140 | [T_EXTENDS], 141 | [T_STRING, array_pop($fqcn)], 142 | ]) !== null; 143 | } 144 | 145 | /** 146 | * @param string|string[] $fqcn 147 | * 148 | * @return bool 149 | */ 150 | protected function implementsInterface(Tokens $tokens, $fqcn) 151 | { 152 | if (\is_array($fqcn) === false) { 153 | $fqcn = explode('\\', $fqcn); 154 | } 155 | 156 | if ($this->hasUseStatements($tokens, $fqcn) === false) { 157 | return false; 158 | } 159 | 160 | return $tokens->findSequence([ 161 | [T_CLASS], 162 | [T_STRING], 163 | [T_IMPLEMENTS], 164 | [T_STRING, array_pop($fqcn)], 165 | ]) !== null; 166 | } 167 | 168 | /** 169 | * @param Tokens $tokens 170 | * 171 | * @return Token[] 172 | */ 173 | protected function getComments(Tokens $tokens) 174 | { 175 | $comments = []; 176 | 177 | foreach ($tokens as $index => $token) { 178 | if ($token->isComment()) { 179 | $comments[$index] = $token; 180 | } 181 | } 182 | 183 | return $comments; 184 | } 185 | } -------------------------------------------------------------------------------- /src/Taptima/CS/Fixer/AbstractOrderedClassElementsFixer.php: -------------------------------------------------------------------------------- 1 | count(); $i < $count; ++$i) { 27 | if (!$tokens[$i]->isClassy()) { 28 | continue; 29 | } 30 | 31 | $i = $tokens->getNextTokenOfKind($i, ['{']); 32 | $elements = $this->getElements($tokens, $i); 33 | 34 | if (!$elements) { 35 | continue; 36 | } 37 | 38 | $sorted = $this->sortElements($elements); 39 | 40 | $endIndex = $elements[\count($elements) - 1]['end']; 41 | 42 | if ($sorted !== $elements) { 43 | $this->sortTokens($tokens, $i, $endIndex, $sorted); 44 | } 45 | 46 | $i = $endIndex; 47 | } 48 | } 49 | 50 | /** 51 | * @param int $startIndex 52 | * 53 | * @return array[] 54 | */ 55 | private function getElements(Tokens $tokens, $startIndex) 56 | { 57 | static $elementTokenKinds = [CT::T_USE_TRAIT, T_CONST, T_VARIABLE, T_FUNCTION]; 58 | 59 | ++$startIndex; 60 | $elements = []; 61 | 62 | while (true) { 63 | $element = [ 64 | 'start' => $startIndex, 65 | 'visibility' => 'public', 66 | 'static' => false, 67 | ]; 68 | 69 | for ($i = $startIndex;; ++$i) { 70 | $token = $tokens[$i]; 71 | 72 | if ($token->equals('}')) { 73 | return $elements; 74 | } 75 | 76 | if ($token->isGivenKind(T_STATIC)) { 77 | $element['static'] = true; 78 | 79 | continue; 80 | } 81 | 82 | if ($token->isGivenKind([T_PROTECTED, T_PRIVATE])) { 83 | $element['visibility'] = mb_strtolower($token->getContent()); 84 | 85 | continue; 86 | } 87 | 88 | if (!$token->isGivenKind($elementTokenKinds)) { 89 | continue; 90 | } 91 | 92 | $type = $this->detectElementType($tokens, $i); 93 | $element['type'] = $type; 94 | 95 | switch ($type) { 96 | case 'method': 97 | $element['methodName'] = $tokens[$tokens->getNextMeaningfulToken($i)]->getContent(); 98 | 99 | break; 100 | 101 | case 'property': 102 | $element['propertyName'] = $token->getContent(); 103 | 104 | break; 105 | } 106 | $element['end'] = $this->findElementEnd($tokens, $i); 107 | 108 | break; 109 | } 110 | 111 | $elements[] = $element; 112 | $startIndex = $element['end'] + 1; 113 | } 114 | } 115 | 116 | /** 117 | * @param int $index 118 | * 119 | * @return array|string type or array of type and name 120 | */ 121 | private function detectElementType(Tokens $tokens, $index) 122 | { 123 | $token = $tokens[$index]; 124 | 125 | if ($token->isGivenKind(CT::T_USE_TRAIT)) { 126 | return 'use_trait'; 127 | } 128 | 129 | if ($token->isGivenKind(T_CONST)) { 130 | return 'constant'; 131 | } 132 | 133 | if ($token->isGivenKind(T_VARIABLE)) { 134 | return 'property'; 135 | } 136 | 137 | $nameToken = $tokens[$tokens->getNextMeaningfulToken($index)]; 138 | 139 | if ($nameToken->equals([T_STRING, '__construct'], false)) { 140 | return 'construct'; 141 | } 142 | 143 | if ($nameToken->equals([T_STRING, '__destruct'], false)) { 144 | return 'destruct'; 145 | } 146 | 147 | if ( 148 | $nameToken->equalsAny([ 149 | [T_STRING, 'setUpBeforeClass'], 150 | [T_STRING, 'tearDownAfterClass'], 151 | [T_STRING, 'setUp'], 152 | [T_STRING, 'tearDown'], 153 | ], false) 154 | ) { 155 | return ['phpunit', mb_strtolower($nameToken->getContent())]; 156 | } 157 | 158 | if (mb_substr($nameToken->getContent(), 0, 2) === '__') { 159 | return 'magic'; 160 | } 161 | 162 | return 'method'; 163 | } 164 | 165 | /** 166 | * @param int $index 167 | * 168 | * @return int 169 | */ 170 | private function findElementEnd(Tokens $tokens, $index) 171 | { 172 | $index = $tokens->getNextTokenOfKind($index, ['{', ';']); 173 | 174 | if ($tokens[$index]->equals('{')) { 175 | $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); 176 | } 177 | 178 | for (++$index; $tokens[$index]->isWhitespace(" \t") || $tokens[$index]->isComment(); ++$index); 179 | 180 | --$index; 181 | 182 | return $tokens[$index]->isWhitespace() ? $index - 1 : $index; 183 | } 184 | 185 | /** 186 | * @param int $startIndex 187 | * @param int $endIndex 188 | * @param array[] $elements 189 | */ 190 | private function sortTokens( 191 | Tokens $tokens, 192 | $startIndex, 193 | $endIndex, 194 | array $elements 195 | ): void { 196 | $replaceTokens = []; 197 | 198 | foreach ($elements as $element) { 199 | for ($i = $element['start']; $i <= $element['end']; ++$i) { 200 | $replaceTokens[] = clone $tokens[$i]; 201 | } 202 | } 203 | 204 | $tokens->overrideRange($startIndex + 1, $endIndex, $replaceTokens); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Taptima/CS/Fixer/DoctrineMigrationsFixer.php: -------------------------------------------------------------------------------- 1 | ['Doctrine\Migrations\AbstractMigration']], 26 | ]; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function isCandidate(Tokens $tokens) 33 | { 34 | foreach ($this->configuration['instanceof'] as $parent) { 35 | if ($this->extendsClass($tokens, $parent)) { 36 | return true; 37 | } 38 | 39 | if ($this->implementsInterface($tokens, $parent)) { 40 | return true; 41 | } 42 | } 43 | 44 | return false; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getSampleCode() 51 | { 52 | return <<<'SPEC' 53 | abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 76 | 77 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 78 | } 79 | 80 | public function down(Schema $schema) 81 | { 82 | // this down() migration is auto-generated, please modify it to your needs 83 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 84 | 85 | $this->addSql('DROP TABLE admin'); 86 | } 87 | } 88 | SPEC; 89 | } 90 | 91 | public function getDocumentation() 92 | { 93 | return 'Remove useless getDescription(), up(), down() and comments from Doctrine\Migrations\AbstractMigration if needed.'; 94 | } 95 | 96 | public function getPriority() 97 | { 98 | return Priority::before(ClassAttributesSeparationFixer::class, NoEmptyPhpdocFixer::class, NoExtraBlankLinesFixer::class); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | protected function createConfigurationDefinition() 105 | { 106 | return new FixerConfigurationResolver([ 107 | (new FixerOptionBuilder('instanceof', 'Parent classes of your migration classes.')) 108 | ->setDefault(['Doctrine\Migrations\AbstractMigration']) 109 | ->getOption(), 110 | ]); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | protected function applyFix(SplFileInfo $file, Tokens $tokens): void 117 | { 118 | $this->removeUselessGetDocumentation($tokens); 119 | $this->removeUselessComments($tokens); 120 | } 121 | 122 | private function removeUselessGetDocumentation(Tokens $tokens): void 123 | { 124 | foreach ($this->analyze($tokens)->getElements() as $element) { 125 | if ($element['type'] !== 'method') { 126 | continue; 127 | } 128 | 129 | if ($element['methodName'] !== 'getDescription') { 130 | continue; 131 | } 132 | 133 | $sequences = $this->analyze($tokens)->findAllSequences( 134 | [ 135 | [ 136 | '{', 137 | [T_RETURN], 138 | [T_CONSTANT_ENCAPSED_STRING, "''"], 139 | ';', 140 | '}', 141 | ], 142 | [ 143 | '{', 144 | [T_RETURN], 145 | [T_CONSTANT_ENCAPSED_STRING, '""'], 146 | ';', 147 | '}', 148 | ], 149 | ], 150 | $element['start'], 151 | $element['end'] 152 | ); 153 | 154 | if (empty($sequences)) { 155 | return; 156 | } 157 | 158 | $tokens->clearRange($element['start'], $element['end']); 159 | } 160 | } 161 | 162 | private function removeUselessComments(Tokens $tokens): void 163 | { 164 | $comments = $this->getComments($tokens); 165 | 166 | $blackList = [ 167 | 'Auto-generated Migration: Please modify to your needs!', 168 | 'this up() migration is auto-generated, please modify it to your needs', 169 | 'this down() migration is auto-generated, please modify it to your needs', 170 | ]; 171 | 172 | foreach ($comments as $position => $comment) { 173 | $lines = explode("\n", $comment->getContent()); 174 | $changed = false; 175 | 176 | foreach ($lines as $index => $line) { 177 | if (\in_array(trim($line, '/* '), $blackList, true)) { 178 | unset($lines[$index]); 179 | $changed = true; 180 | } 181 | } 182 | 183 | if ($changed === false) { 184 | continue; 185 | } 186 | 187 | if (empty(trim(implode("\n", $lines), " /*\n"))) { 188 | $tokens->clearAt($position); 189 | $tokens->removeTrailingWhitespace($position); 190 | 191 | continue; 192 | } 193 | 194 | $tokens[$position] = new Token([T_COMMENT, implode("\n", $lines)]); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/Taptima/CS/Fixer/OrderedSettersAndGettersFixer.php: -------------------------------------------------------------------------------- 1 | inflector = $factory->build(); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function isCandidate(Tokens $tokens) 37 | { 38 | return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function getPriority() 45 | { 46 | return Priority::before(OrderedClassElementsFixer::class); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function getDocumentation() 53 | { 54 | return 'Class/interface/trait setters and getters MUST BE ordered (order is setter, isser, hasser, adder, remover, getter).'; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function getSampleCode() 61 | { 62 | return <<<'PHP' 63 | items = new ArrayCollection(); 97 | foreach ($data as $key => $value) { 98 | $this->$key = $value; 99 | } 100 | } 101 | 102 | public function setFirstName($firstName) 103 | { 104 | $this->firstName = $firstName; 105 | } 106 | 107 | public function setName($name) 108 | { 109 | $this->name = $name; 110 | } 111 | 112 | public function isEnabled() 113 | { 114 | return $this->enabled; 115 | } 116 | 117 | public function getName() 118 | { 119 | return $this->name; 120 | } 121 | 122 | public function getIdentifier() 123 | { 124 | return $this->identifier; 125 | } 126 | 127 | public function getFirstName() 128 | { 129 | return $this->firstName; 130 | } 131 | 132 | public function enable() 133 | { 134 | $this->enabled = true; 135 | } 136 | 137 | public function disable() 138 | { 139 | $this->enabled = false; 140 | } 141 | 142 | /** 143 | * @param Item $items 144 | * 145 | * @return User 146 | */ 147 | public function setItems(Item $items) 148 | { 149 | $this->items = $items; 150 | return $this; 151 | } 152 | 153 | /** 154 | * @return Item 155 | */ 156 | public function getItems() 157 | { 158 | return $this->items; 159 | } 160 | 161 | /** 162 | * @param Item $item 163 | * @return User 164 | */ 165 | public function addItem(Item $item) 166 | { 167 | $this->items[] = $item; 168 | 169 | return $this; 170 | } 171 | 172 | /** 173 | * @param Item $item 174 | * @return User 175 | */ 176 | public function removeItem(Item $item) 177 | { 178 | $this->items->removeElement($item); 179 | 180 | return $this; 181 | } 182 | } 183 | PHP; 184 | } 185 | 186 | /** 187 | * {@inheritdoc} 188 | */ 189 | protected function sortElements(array $elements) 190 | { 191 | $methods = $this->getMethodsNames($elements); 192 | $portions = []; 193 | 194 | foreach ($methods as $i => $method) { 195 | foreach ($elements as $index => $element) { 196 | if ($element['type'] !== 'method') { 197 | continue; 198 | } 199 | 200 | if ($element['methodName'] === $method['name']) { 201 | $portions[$method['name']] = [ 202 | 'element' => $element, 203 | 'before' => $method['before'], 204 | 'after' => $method['after'], 205 | ]; 206 | } 207 | } 208 | } 209 | 210 | $result = []; 211 | $usedMethodElements = []; 212 | 213 | $addToResult = static function ($element, $isMethod = true) use (&$result, &$usedMethodElements) { 214 | $result[] = $element; 215 | 216 | if ($isMethod) { 217 | $usedMethodElements[$element['methodName']] = true; 218 | } 219 | }; 220 | 221 | foreach ($elements as $element) { 222 | if ($element['type'] !== 'method') { 223 | $addToResult($element, false); 224 | 225 | continue; 226 | } 227 | 228 | if (array_key_exists($element['methodName'], $usedMethodElements)) { 229 | continue; 230 | } 231 | 232 | if (!array_key_exists($element['methodName'], $portions)) { 233 | $addToResult($element); 234 | 235 | continue; 236 | } 237 | 238 | foreach ($portions[$element['methodName']]['before'] as $method) { 239 | if (!array_key_exists($method, $portions) || array_key_exists($method, $usedMethodElements)) { 240 | continue; 241 | } 242 | 243 | $addToResult($portions[$method]['element']); 244 | } 245 | 246 | $addToResult($element); 247 | 248 | foreach ($portions[$element['methodName']]['after'] as $method) { 249 | if (!array_key_exists($method, $portions) || array_key_exists($method, $usedMethodElements)) { 250 | continue; 251 | } 252 | 253 | $addToResult($portions[$method]['element']); 254 | } 255 | } 256 | 257 | return $result; 258 | } 259 | 260 | /** 261 | * @param array $elements 262 | * 263 | * @return array 264 | */ 265 | private function getMethodsNames(array $elements) 266 | { 267 | $result = []; 268 | 269 | foreach ($this->getPropertiesNames($elements) as $name) { 270 | $methods = [sprintf('set%s', ucfirst($name))]; 271 | 272 | foreach ((array) $this->inflector->singularize($name) as $singular) { 273 | $methods[] = sprintf('add%s', ucfirst($singular)); 274 | $methods[] = sprintf('remove%s', ucfirst($singular)); 275 | } 276 | 277 | $methods[] = sprintf('is%s', ucfirst($name)); 278 | $methods[] = sprintf('has%s', ucfirst($name)); 279 | $methods[] = sprintf('get%s', ucfirst($name)); 280 | 281 | for ($i = 0, $max = count($methods); $i < $max; ++$i) { 282 | $result[] = $this->createMethodDescription($methods[$i], array_slice($methods, 0, $i), array_slice($methods, $i + 1, $max)); 283 | } 284 | } 285 | 286 | return $result; 287 | } 288 | 289 | /** 290 | * @param string $name 291 | * @param array $beforeMethods 292 | * @param array $afterMethods 293 | * 294 | * @return array 295 | */ 296 | private function createMethodDescription($name, array $beforeMethods = [], array $afterMethods = []) 297 | { 298 | return [ 299 | 'name' => $name, 300 | 'before' => $beforeMethods, 301 | 'after' => $afterMethods, 302 | ]; 303 | } 304 | 305 | /** 306 | * @param array $elements 307 | * 308 | * @return array 309 | */ 310 | private function getPropertiesNames(array $elements) 311 | { 312 | $properties = array_filter($elements, function ($element) { 313 | return $element['type'] === 'property'; 314 | }); 315 | 316 | return array_map(function ($element) { 317 | return ltrim($element['propertyName'], '$'); 318 | }, $properties); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/Taptima/CS/Fixers.php: -------------------------------------------------------------------------------- 1 | in($dir) 24 | ->name('*.php') 25 | ; 26 | 27 | $files = array_map( 28 | function ($file) { 29 | return $file->getPathname(); 30 | }, 31 | iterator_to_array($finder) 32 | ); 33 | 34 | sort($files); 35 | 36 | foreach ($files as $file) { 37 | $class = str_replace('/', '\\', mb_substr($file, mb_strlen($dir) - 16, -4)); 38 | 39 | if (class_exists($class) === false) { 40 | continue; 41 | } 42 | 43 | try { 44 | $rfl = new ReflectionClass($class); 45 | } catch (Throwable $e) { 46 | continue; 47 | } 48 | 49 | if ($rfl->implementsInterface(FixerInterface::class) === false) { 50 | continue; 51 | } 52 | 53 | if ($rfl->isAbstract()) { 54 | continue; 55 | } 56 | 57 | yield new $class(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Taptima/CS/Priority.php: -------------------------------------------------------------------------------- 1 | $classes 15 | * 16 | * @return int 17 | */ 18 | public static function before(...$classes) 19 | { 20 | $priorities = array_map( 21 | function ($class) { 22 | return (new $class())->getPriority(); 23 | }, 24 | $classes 25 | ); 26 | 27 | return max($priorities) + 1; 28 | } 29 | 30 | /** 31 | * @param array $classes 32 | * 33 | * @return int 34 | */ 35 | public static function after(...$classes) 36 | { 37 | $priorities = array_map( 38 | function ($class) { 39 | return (new $class())->getPriority(); 40 | }, 41 | $classes 42 | ); 43 | 44 | return min($priorities) - 1; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Taptima/CS/RuleSetFactory.php: -------------------------------------------------------------------------------- 1 | [ 16 | '@Symfony' => true, 17 | '@DoctrineAnnotation' => true, 18 | '@PhpCsFixer' => true, 19 | '@PHP71Migration' => true, 20 | 21 | 'blank_line_after_namespace' => true, 22 | 'single_import_per_statement' => true, 23 | 'single_line_after_imports' => true, 24 | 'no_unused_imports' => true, 25 | 'clean_namespace' => true, 26 | 'lambda_not_used_import' => true, 27 | 'switch_continue_to_break' => true, 28 | 'no_alias_language_construct_call' => true, 29 | 'single_space_after_construct' => true, 30 | 'operator_linebreak' => true, 31 | 32 | 'ordered_imports' => true, 33 | 'global_namespace_import' => true, 34 | 'ordered_class_elements' => [ 35 | 'order' => [ 36 | 'use_trait', 37 | 'constant', 38 | 'constant_public', 39 | 'constant_protected', 40 | 'constant_private', 41 | 'property_public_static', 42 | 'property_public', 43 | 'property_protected_static', 44 | 'property_protected', 45 | 'property_private_static', 46 | 'property_private', 47 | 'construct', 48 | 'destruct', 49 | 'magic', 50 | 'phpunit', 51 | 'method_public_abstract', 52 | 'method_public_abstract_static', 53 | 'method_public_static', 54 | 'method_public', 55 | 'method_protected_abstract', 56 | 'method_protected_abstract_static', 57 | 'method_protected_static', 58 | 'method_protected', 59 | 'method_private_static', 60 | 'method_private', 61 | ], 62 | ], 63 | 64 | 'method_chaining_indentation' => false, 65 | 66 | 'self_static_accessor' => true, 67 | 68 | 'ternary_to_null_coalescing' => true, 69 | 'binary_operator_spaces' => [ 70 | 'default' => 'single_space', 71 | 'operators' => [ 72 | '=' => 'align_single_space_minimal', 73 | '+=' => 'align_single_space_minimal', 74 | '-=' => 'align_single_space_minimal', 75 | '/=' => 'align_single_space_minimal', 76 | '*=' => 'align_single_space_minimal', 77 | '%=' => 'align_single_space_minimal', 78 | '**=' => 'align_single_space_minimal', 79 | '=>' => 'align_single_space_minimal', 80 | ], 81 | ], 82 | 'yoda_style' => [ 83 | 'equal' => false, 84 | 'identical' => false, 85 | 'less_and_greater' => null, 86 | ], 87 | 'concat_space' => [ 88 | 'spacing' => 'one', 89 | ], 90 | 'array_syntax' => [ 91 | 'syntax' => 'short', 92 | ], 93 | 'braces' => true, 94 | 'elseif' => true, 95 | 'trim_array_spaces' => true, 96 | 97 | 'function_declaration' => true, 98 | 'no_spaces_after_function_name' => true, 99 | 'no_spaces_inside_parenthesis' => true, 100 | 101 | 'cast_spaces' => true, 102 | 'encoding' => true, 103 | 'full_opening_tag' => true, 104 | 'linebreak_after_opening_tag' => true, 105 | 'no_closing_tag' => true, 106 | 'indentation_type' => true, 107 | 'line_ending' => true, 108 | 'single_blank_line_at_eof' => false, 109 | 'no_trailing_whitespace' => true, 110 | 'lowercase_keywords' => true, 111 | 'no_whitespace_in_blank_line' => true, 112 | 'blank_line_before_statement' => true, 113 | 'echo_tag_syntax' => true, 114 | 115 | 'doctrine_annotation_braces' => false, 116 | 'doctrine_annotation_array_assignment' => [ 117 | 'operator' => '=', 118 | ], 119 | 120 | 'align_multiline_comment' => [ 121 | 'comment_type' => 'all_multiline', 122 | ], 123 | 124 | 'no_superfluous_phpdoc_tags' => false, 125 | 'phpdoc_order' => true, 126 | 'phpdoc_separation' => true, 127 | 'phpdoc_var_without_name' => false, 128 | 'phpdoc_var_annotation_correct_order' => true, 129 | 'phpdoc_types_order' => [ 130 | 'sort_algorithm' => 'none', 131 | 'null_adjustment' => 'always_last', 132 | ], 133 | 'phpdoc_order_by_value' => [ 134 | 'annotations' => [ 135 | 'method', 136 | 'throws', 137 | 'author', 138 | 'property', 139 | 'internal', 140 | ], 141 | ], 142 | 143 | 'nullable_type_declaration_for_default_null_value' => true, 144 | 145 | 'phpdoc_add_missing_param_annotation' => [ 146 | 'only_untyped' => false, 147 | ], 148 | ], 149 | ]; 150 | 151 | /** 152 | * @const array 153 | */ 154 | private const RISKY_DEFINITION = [ 155 | 'phpdoc_to_param_type' => true, 156 | 'void_return' => true, 157 | ]; 158 | 159 | /** 160 | * @var array[] 161 | */ 162 | private $rules; 163 | 164 | /** 165 | * RuleSetFactory constructor. 166 | * 167 | * @param array $rules 168 | */ 169 | public function __construct(array $rules = []) 170 | { 171 | if (array_key_exists('@Taptima', $rules)) { 172 | unset($rules['@Taptima']); 173 | $rules = array_merge(self::SET_DEFINITIONS['@Taptima'], $rules); 174 | } 175 | 176 | $this->rules = $rules; 177 | } 178 | 179 | /** 180 | * @param array $rules 181 | * 182 | * @return RuleSetFactory 183 | */ 184 | public static function create(array $rules = []) 185 | { 186 | return new self($rules); 187 | } 188 | 189 | /** 190 | * @return array 191 | */ 192 | public function getRules() 193 | { 194 | $rules = $this->rules; 195 | 196 | ksort($rules); 197 | 198 | return $rules; 199 | } 200 | 201 | /** 202 | * @return RuleSetFactory 203 | */ 204 | public function psr0() 205 | { 206 | return self::create(array_merge( 207 | $this->rules, 208 | ['@psr0' => true] 209 | )); 210 | } 211 | 212 | /** 213 | * @return RuleSetFactory 214 | */ 215 | public function psr1() 216 | { 217 | return self::create(array_merge( 218 | $this->rules, 219 | ['@psr1' => true] 220 | )); 221 | } 222 | 223 | /** 224 | * @return RuleSetFactory 225 | */ 226 | public function psr2() 227 | { 228 | return self::create(array_merge( 229 | $this->rules, 230 | ['@psr2' => true] 231 | )); 232 | } 233 | 234 | /** 235 | * @return RuleSetFactory 236 | */ 237 | public function psr4() 238 | { 239 | return self::create(array_merge( 240 | $this->rules, 241 | ['@psr4' => true] 242 | )); 243 | } 244 | 245 | /** 246 | * @param bool $risky 247 | * 248 | * @return RuleSetFactory 249 | */ 250 | public function symfony($risky = false) 251 | { 252 | $rules = ['@Symfony' => true]; 253 | 254 | if ($risky) { 255 | $rules['@Symfony:risky'] = true; 256 | } 257 | 258 | return self::create(array_merge( 259 | $this->rules, 260 | $rules 261 | )); 262 | } 263 | 264 | /** 265 | * @param bool $risky 266 | * 267 | * @return RuleSetFactory 268 | */ 269 | public function phpCsFixer($risky = false) 270 | { 271 | $rules = ['@PhpCsFixer' => true]; 272 | 273 | if ($risky) { 274 | $rules['@PhpCsFixer:risky'] = true; 275 | } 276 | 277 | return self::create(array_merge( 278 | $this->rules, 279 | $rules 280 | )); 281 | } 282 | 283 | /** 284 | * @return RuleSetFactory 285 | */ 286 | public function doctrineAnnotation() 287 | { 288 | return self::create(array_merge( 289 | $this->rules, 290 | ['@DoctrineAnnotation' => true] 291 | )); 292 | } 293 | 294 | /** 295 | * @param float|string $version 296 | * @param bool $risky 297 | * 298 | * @return RuleSetFactory 299 | */ 300 | public function php($version, $risky = false) 301 | { 302 | $config = $this->migration('php', $version, $risky)->getRules(); 303 | 304 | switch (true) { 305 | case $version >= 7.1: 306 | $config = array_merge(['list_syntax' => ['syntax' => 'short']], $config); 307 | // no break 308 | case $version >= 5.4: 309 | $config = array_merge(['array_syntax' => ['syntax' => 'short']], $config); 310 | } 311 | 312 | $config = array_merge(['list_syntax' => ['syntax' => 'long']], $config); 313 | $config = array_merge(['array_syntax' => ['syntax' => 'long']], $config); 314 | 315 | return self::create(array_merge( 316 | $this->rules, 317 | $config 318 | )); 319 | } 320 | 321 | /** 322 | * @param float $version 323 | * @param bool $risky 324 | * 325 | * @return RuleSetFactory 326 | */ 327 | public function phpUnit($version, $risky = false) 328 | { 329 | return $this->migration('phpunit', $version, $risky); 330 | } 331 | 332 | /** 333 | * @param bool $risky 334 | * 335 | * @return RuleSetFactory 336 | */ 337 | public function taptima($risky = false) 338 | { 339 | $rules = []; 340 | 341 | foreach (new Fixers() as $fixer) { 342 | if ($fixer->isDeprecated()) { 343 | continue; 344 | } 345 | 346 | $rules[$fixer->getName()] = true; 347 | } 348 | 349 | ksort($rules); 350 | 351 | $this->rules = array_merge( 352 | $this->rules, 353 | $rules 354 | ); 355 | 356 | return $this; 357 | } 358 | 359 | /** 360 | * @return $this 361 | */ 362 | public function taptimaRisky() 363 | { 364 | $this->rules = array_merge( 365 | $this->rules, 366 | self::RISKY_DEFINITION 367 | ); 368 | 369 | return $this; 370 | } 371 | 372 | /** 373 | * @param string $name 374 | * 375 | * @return RuleSetFactory 376 | */ 377 | public function enable($name, array $config = null) 378 | { 379 | return self::create(array_merge( 380 | $this->rules, 381 | [$name => \is_array($config) ? $config : true] 382 | )); 383 | } 384 | 385 | /** 386 | * @param string $name 387 | * 388 | * @return RuleSetFactory 389 | */ 390 | public function disable($name) 391 | { 392 | return self::create(array_merge( 393 | $this->rules, 394 | [$name => false] 395 | )); 396 | } 397 | 398 | /** 399 | * @param string $package 400 | * @param float $version 401 | * @param bool $risky 402 | * 403 | * @return RuleSetFactory 404 | */ 405 | private function migration($package, $version, $risky) 406 | { 407 | $rules = (new RuleSet())->getSetDefinitionNames(); 408 | $rules = array_combine($rules, $rules); 409 | 410 | $rules = array_map(function ($name) { 411 | $matches = []; 412 | 413 | preg_match('/^@([A-Za-z]+)(\d+)Migration(:risky|)$/', $name, $matches); 414 | 415 | return $matches; 416 | }, $rules); 417 | 418 | $rules = array_filter($rules); 419 | 420 | $rules = array_filter($rules, function ($versionAndRisky) use ($package) { 421 | [$rule, $rulePackage, $ruleVersion, $ruleRisky] = $versionAndRisky; 422 | 423 | return strtoupper($package) === strtoupper($rulePackage); 424 | }); 425 | 426 | $rules = array_filter($rules, function ($versionAndRisky) use ($version) { 427 | [$rule, $rulePackage, $ruleVersion, $ruleRisky] = $versionAndRisky; 428 | 429 | return ((float) $ruleVersion / 10) <= $version; 430 | }); 431 | 432 | $rules = array_filter($rules, function ($versionAndRisky) use ($risky) { 433 | [$rule, $rulePackage, $ruleVersion, $ruleRisky] = $versionAndRisky; 434 | 435 | if ($risky) { 436 | return true; 437 | } 438 | 439 | return empty($ruleRisky); 440 | }); 441 | 442 | return self::create(array_merge( 443 | $this->rules, 444 | array_map(function () { 445 | return true; 446 | }, $rules) 447 | )); 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /src/Taptima/CS/TokenSignatures.php: -------------------------------------------------------------------------------- 1 | tokens = $tokens; 28 | $this->analyzer = new PhpCsFixerTokensAnalyzer($tokens); 29 | } 30 | 31 | public function __call($name, $arguments) 32 | { 33 | return \call_user_func_array([$this->analyzer, $name], $arguments); 34 | } 35 | 36 | /** 37 | * @param int $index 38 | * 39 | * @return array 40 | */ 41 | public function getMethodArguments($index) 42 | { 43 | $methodName = $this->tokens->getNextMeaningfulToken($index); 44 | $openParenthesis = $this->tokens->getNextMeaningfulToken($methodName); 45 | $closeParenthesis = $this->getClosingParenthesis($openParenthesis); 46 | 47 | $arguments = []; 48 | 49 | for ($position = $openParenthesis + 1; $position < $closeParenthesis; ++$position) { 50 | $token = $this->tokens[$position]; 51 | 52 | if ($token->isWhitespace()) { 53 | continue; 54 | } 55 | 56 | $argumentType = null; 57 | $argumentName = $position; 58 | $argumentAsDefault = false; 59 | $argumentNullable = false; 60 | 61 | if (!preg_match('/^\$.+/', $this->tokens[$argumentName]->getContent())) { 62 | do { 63 | if ($this->tokens[$argumentName]->isWhitespace() === false) { 64 | $argumentType .= $this->tokens[$argumentName]->getContent(); 65 | } 66 | 67 | ++$argumentName; 68 | } while (!preg_match('/^\$.+/', $this->tokens[$argumentName]->getContent())); 69 | } 70 | 71 | $next = $this->tokens->getNextMeaningfulToken($argumentName); 72 | 73 | if ($this->tokens[$next]->equals('=')) { 74 | $argumentAsDefault = true; 75 | $value = $this->tokens->getNextMeaningfulToken($next); 76 | $argumentNullable = $this->tokens[$value]->getContent() === 'null'; 77 | } 78 | 79 | $arguments[$position] = [ 80 | 'type' => $argumentType, 81 | 'name' => $this->tokens[$argumentName]->getContent(), 82 | 'nullable' => $argumentNullable, 83 | 'asDefault' => $argumentAsDefault, 84 | ]; 85 | 86 | $nextComma = $this->getNextComma($position); 87 | 88 | if ($nextComma === null) { 89 | return $arguments; 90 | } 91 | 92 | $position = $nextComma; 93 | } 94 | 95 | return $arguments; 96 | } 97 | 98 | /** 99 | * @param int $index 100 | * 101 | * @return int 102 | */ 103 | public function getNumberOfArguments($index) 104 | { 105 | return \count($this->getMethodArguments($index)); 106 | } 107 | 108 | /** 109 | * @param int $index 110 | * 111 | * @return int|null 112 | */ 113 | public function getNextComma($index) 114 | { 115 | do { 116 | $index = $this->tokens->getNextMeaningfulToken($index); 117 | 118 | if ($index === null) { 119 | return; 120 | } 121 | 122 | switch (true) { 123 | case $this->tokens[$index]->equals('('): 124 | $index = $this->getClosingParenthesis($index); 125 | 126 | break; 127 | 128 | case $this->tokens[$index]->equals('['): 129 | $index = $this->getClosingBracket($index); 130 | 131 | break; 132 | 133 | case $this->tokens[$index]->equals('{'): 134 | $index = $this->getClosingCurlyBracket($index); 135 | 136 | break; 137 | 138 | case $this->tokens[$index]->equals(';'): 139 | return; 140 | } 141 | } while ($this->tokens[$index]->equals(',') === false); 142 | 143 | return $index; 144 | } 145 | 146 | /** 147 | * @param int $index 148 | * 149 | * @return int|null 150 | */ 151 | public function getNextSemiColon($index) 152 | { 153 | do { 154 | $index = $this->tokens->getNextMeaningfulToken($index); 155 | 156 | if ($index === null) { 157 | return; 158 | } 159 | 160 | switch (true) { 161 | case $this->tokens[$index]->equals('('): 162 | $index = $this->getClosingParenthesis($index); 163 | 164 | break; 165 | 166 | case $this->tokens[$index]->equals('['): 167 | $index = $this->getClosingBracket($index); 168 | 169 | break; 170 | 171 | case $this->tokens[$index]->equals('{'): 172 | $index = $this->getClosingCurlyBracket($index); 173 | 174 | break; 175 | } 176 | } while ($this->tokens[$index]->equals(';') === false); 177 | 178 | return $index; 179 | } 180 | 181 | /** 182 | * @param int $index 183 | * 184 | * @return array|string|null 185 | */ 186 | public function getReturnedType($index) 187 | { 188 | if ($this->tokens[$index]->isGivenKind(T_FUNCTION) === false) { 189 | throw new Exception(sprintf('Expected token: T_FUNCTION Token %d id contains %s.', $index, $this->tokens[$index]->getContent())); 190 | } 191 | 192 | $methodName = $this->tokens->getNextMeaningfulToken($index); 193 | $openParenthesis = $this->tokens->getNextMeaningfulToken($methodName); 194 | $closeParenthesis = $this->getClosingParenthesis($openParenthesis); 195 | 196 | $next = $this->tokens->getNextMeaningfulToken($closeParenthesis); 197 | 198 | if ($next === null) { 199 | return; 200 | } 201 | 202 | if ($this->tokens[$next]->isGivenKind(TokenSignatures::TYPINT_DOUBLE_DOTS) === false) { 203 | return; 204 | } 205 | 206 | $next = $this->tokens->getNextMeaningfulToken($next); 207 | 208 | if ($next === null) { 209 | return; 210 | } 211 | 212 | $optionnal = $this->tokens[$next]->isGivenKind(TokenSignatures::TYPINT_OPTIONAL); 213 | 214 | $next = $optionnal 215 | ? $this->tokens->getNextMeaningfulToken($next) 216 | : $next 217 | ; 218 | 219 | do { 220 | $return = $this->tokens[$next]->getContent(); 221 | ++$next; 222 | 223 | if ($this->tokens[$next]->isWhitespace() || $this->tokens[$next]->equals(';')) { 224 | return $optionnal 225 | ? [$return, null] 226 | : $return; 227 | } 228 | } while ($this->tokens[$index]->equals(['{', ';']) === false); 229 | } 230 | 231 | /** 232 | * @param int $index 233 | * 234 | * @return int|null 235 | */ 236 | public function getBeginningOfTheLine($index) 237 | { 238 | for ($i = $index; $i >= 0; --$i) { 239 | if (mb_strpos($this->tokens[$i]->getContent(), "\n") !== false) { 240 | return $i; 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * @param int $index 247 | * 248 | * @return int|null 249 | */ 250 | public function getEndOfTheLine($index) 251 | { 252 | for ($i = $index; $i < $this->tokens->count(); ++$i) { 253 | if (mb_strpos($this->tokens[$i]->getContent(), "\n") !== false) { 254 | return $i; 255 | } 256 | } 257 | } 258 | 259 | /** 260 | * @param int $index 261 | * 262 | * @return int 263 | */ 264 | public function getSizeOfTheLine($index) 265 | { 266 | $start = $this->getBeginningOfTheLine($index); 267 | $end = $this->getEndOfTheLine($index); 268 | $size = 0; 269 | 270 | $parts = explode("\n", $this->tokens[$start]->getContent()); 271 | $size += mb_strlen(end($parts)); 272 | 273 | $parts = explode("\n", $this->tokens[$end]->getContent()); 274 | $size += mb_strlen(current($parts)); 275 | 276 | for ($i = $start + 1; $i < $end; ++$i) { 277 | $size += mb_strlen($this->tokens[$i]->getContent()); 278 | } 279 | 280 | return $size; 281 | } 282 | 283 | /** 284 | * @param int $index 285 | * 286 | * @return int|null 287 | */ 288 | public function endOfTheStatement($index) 289 | { 290 | do { 291 | $index = $this->tokens->getNextMeaningfulToken($index); 292 | 293 | if ($index === null) { 294 | return null; 295 | } 296 | 297 | switch (true) { 298 | case $this->tokens[$index]->equals('('): 299 | $index = $this->getClosingParenthesis($index); 300 | 301 | break; 302 | 303 | case $this->tokens[$index]->equals('['): 304 | $index = $this->getClosingBracket($index); 305 | 306 | break; 307 | 308 | case $this->tokens[$index]->equals('{'): 309 | $index = $this->getClosingCurlyBracket($index); 310 | 311 | break; 312 | } 313 | } while ($this->tokens[$index]->equals('}') === false); 314 | 315 | return $index; 316 | } 317 | 318 | /** 319 | * @param int $index 320 | * 321 | * @return int|null 322 | */ 323 | public function getClosingParenthesis($index) 324 | { 325 | if ($this->tokens[$index]->equals('(') === false) { 326 | throw new Exception(sprintf('Expected token: (. Token %d id contains %s.', $index, $this->tokens[$index]->getContent())); 327 | } 328 | 329 | for ($i = $index + 1; $i < $this->tokens->count(); ++$i) { 330 | if ($this->tokens[$i]->equals('(')) { 331 | $i = $this->getClosingParenthesis($i); 332 | 333 | if ($i === null) { 334 | return null; 335 | } 336 | 337 | continue; 338 | } 339 | 340 | if ($this->tokens[$i]->equals(')')) { 341 | return $i; 342 | } 343 | } 344 | } 345 | 346 | /** 347 | * @param int $index 348 | * 349 | * @return int|null 350 | */ 351 | public function getClosingBracket($index) 352 | { 353 | if ($this->tokens[$index]->equals('[') === false) { 354 | throw new Exception(sprintf('Expected token: [. Token %d id contains %s.', $index, $this->tokens[$index]->getContent())); 355 | } 356 | 357 | for ($i = $index + 1; $i < $this->tokens->count(); ++$i) { 358 | if ($this->tokens[$i]->equals('[')) { 359 | $i = $this->getClosingBracket($i); 360 | 361 | if ($i === null) { 362 | return null; 363 | } 364 | 365 | continue; 366 | } 367 | 368 | if ($this->tokens[$i]->equals(']')) { 369 | return $i; 370 | } 371 | } 372 | } 373 | 374 | /** 375 | * @param int $index 376 | * 377 | * @return int|null 378 | */ 379 | public function getClosingCurlyBracket($index) 380 | { 381 | if ($this->tokens[$index]->equals('{') === false) { 382 | throw new Exception(sprintf('Expected token: {. Token %d id contains %s.', $index, $this->tokens[$index]->getContent())); 383 | } 384 | 385 | for ($i = $index + 1; $i < $this->tokens->count(); ++$i) { 386 | if ($this->tokens[$i]->equals('{')) { 387 | $i = $this->getClosingCurlyBracket($i); 388 | 389 | if ($i === null) { 390 | return null; 391 | } 392 | 393 | continue; 394 | } 395 | 396 | if ($this->tokens[$i]->equals('}')) { 397 | return $i; 398 | } 399 | } 400 | } 401 | 402 | /** 403 | * @param int $index 404 | * 405 | * @return bool 406 | */ 407 | public function isInsideSwitchCase($index) 408 | { 409 | $switch = null; 410 | $ids = array_keys($this->tokens->toArray()); 411 | 412 | $switches = $this->findAllSequences([[[T_SWITCH]]]); 413 | $intervals = []; 414 | 415 | foreach ($switches as $i => $switch) { 416 | $start = $this->tokens->getNextTokenOfKind($i, ['{']); 417 | $end = $this->getClosingCurlyBracket($start); 418 | 419 | $intervals[] = [$start, $end]; 420 | } 421 | 422 | foreach ($intervals as $interval) { 423 | [$start, $end] = $interval; 424 | 425 | if ($index >= $start && $index <= $end) { 426 | return true; 427 | } 428 | } 429 | 430 | return false; 431 | } 432 | 433 | /** 434 | * @param int $index 435 | * 436 | * @return string 437 | */ 438 | public function getLineIndentation($index) 439 | { 440 | $start = $this->getBeginningOfTheLine($index); 441 | $token = $this->tokens[$start]; 442 | $parts = explode("\n", $token->getContent()); 443 | 444 | return end($parts); 445 | } 446 | 447 | /** 448 | * @param mixed|null $start 449 | * @param mixed|null $end 450 | * 451 | * @return array 452 | */ 453 | public function findAllSequences(array $seqs, $start = null, $end = null) 454 | { 455 | $sequences = []; 456 | 457 | foreach ($seqs as $seq) { 458 | $index = $start ?: 0; 459 | 460 | do { 461 | $extract = $this->tokens->findSequence($seq, (int) $index, $end); 462 | 463 | if ($extract !== null) { 464 | $keys = array_keys($extract); 465 | $index = end($keys) + 1; 466 | $sequences[reset($keys)] = $extract; 467 | } 468 | } while ($extract !== null); 469 | } 470 | 471 | ksort($sequences); 472 | 473 | return $sequences; 474 | } 475 | 476 | /** 477 | * @param int $startIndex 478 | * 479 | * @return array[] 480 | */ 481 | public function getElements($startIndex = null) 482 | { 483 | static $elementTokenKinds = [CT::T_USE_TRAIT, T_CONST, T_VARIABLE, T_FUNCTION]; 484 | 485 | if ($startIndex === null) { 486 | foreach ($this->tokens as $startIndex => $token) { 487 | if (!$token->isClassy()) { 488 | continue; 489 | } 490 | 491 | $startIndex = $this->tokens->getNextTokenOfKind($startIndex, ['{']); 492 | 493 | break; 494 | } 495 | } 496 | 497 | ++$startIndex; 498 | $elements = []; 499 | 500 | while (true) { 501 | $element = [ 502 | 'start' => $startIndex, 503 | 'visibility' => 'public', 504 | 'static' => false, 505 | ]; 506 | 507 | for ($i = $startIndex;; ++$i) { 508 | $token = $this->tokens[$i]; 509 | 510 | if ($token->equals('}')) { 511 | return $elements; 512 | } 513 | 514 | if ($token->isGivenKind(T_STATIC)) { 515 | $element['static'] = true; 516 | 517 | continue; 518 | } 519 | 520 | if ($token->isGivenKind([T_PROTECTED, T_PRIVATE])) { 521 | $element['visibility'] = mb_strtolower($token->getContent()); 522 | 523 | continue; 524 | } 525 | 526 | if (!$token->isGivenKind([CT::T_USE_TRAIT, T_CONST, T_VARIABLE, T_FUNCTION])) { 527 | continue; 528 | } 529 | 530 | $type = $this->detectElementType($this->tokens, $i); 531 | $element['type'] = $type; 532 | 533 | switch ($type) { 534 | case 'method': 535 | $element['methodName'] = $this->tokens[$this->tokens->getNextMeaningfulToken($i)]->getContent(); 536 | 537 | break; 538 | 539 | case 'property': 540 | $element['propertyName'] = $token->getContent(); 541 | 542 | break; 543 | } 544 | $element['end'] = $this->findElementEnd($this->tokens, $i); 545 | 546 | break; 547 | } 548 | 549 | $elements[] = $element; 550 | $startIndex = $element['end'] + 1; 551 | } 552 | } 553 | 554 | /** 555 | * @param int $index 556 | * 557 | * @return array|string type or array of type and name 558 | */ 559 | private function detectElementType(Tokens $tokens, $index) 560 | { 561 | $token = $tokens[$index]; 562 | 563 | if ($token->isGivenKind(CT::T_USE_TRAIT)) { 564 | return 'use_trait'; 565 | } 566 | 567 | if ($token->isGivenKind(T_CONST)) { 568 | return 'constant'; 569 | } 570 | 571 | if ($token->isGivenKind(T_VARIABLE)) { 572 | return 'property'; 573 | } 574 | 575 | $nameToken = $tokens[$tokens->getNextMeaningfulToken($index)]; 576 | 577 | if ($nameToken->equals([T_STRING, '__construct'], false)) { 578 | return 'construct'; 579 | } 580 | 581 | if ($nameToken->equals([T_STRING, '__destruct'], false)) { 582 | return 'destruct'; 583 | } 584 | 585 | if ( 586 | $nameToken->equalsAny([ 587 | [T_STRING, 'setUpBeforeClass'], 588 | [T_STRING, 'tearDownAfterClass'], 589 | [T_STRING, 'setUp'], 590 | [T_STRING, 'tearDown'], 591 | ], false) 592 | ) { 593 | return ['phpunit', mb_strtolower($nameToken->getContent())]; 594 | } 595 | 596 | if (mb_substr($nameToken->getContent(), 0, 2) === '__') { 597 | return 'magic'; 598 | } 599 | 600 | return 'method'; 601 | } 602 | 603 | /** 604 | * @param int $index 605 | * 606 | * @return int 607 | */ 608 | private function findElementEnd(Tokens $tokens, $index) 609 | { 610 | $index = $tokens->getNextTokenOfKind($index, ['{', ';']); 611 | 612 | if ($tokens[$index]->equals('{')) { 613 | $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); 614 | } 615 | 616 | for (++$index; $tokens[$index]->isWhitespace(" \t") || $tokens[$index]->isComment(); ++$index); 617 | 618 | --$index; 619 | 620 | return $tokens[$index]->isWhitespace() ? $index - 1 : $index; 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /tests/Orchestra.php: -------------------------------------------------------------------------------- 1 | fixer = $fixer; 28 | } 29 | 30 | public static function run(): void 31 | { 32 | self::assert(new OrderedSettersAndGettersFixer()) 33 | ->before(new OrderedClassElementsFixer()) 34 | ; 35 | 36 | self::assert(new DoctrineMigrationsFixer()) 37 | ->before(new ClassAttributesSeparationFixer()) 38 | ->before(new NoEmptyPhpdocFixer()) 39 | ->before(new NoExtraBlankLinesFixer()) 40 | ->before(new SingleLineAfterImportsFixer()) 41 | ->before(new NoWhitespaceInBlankLineFixer()) 42 | ; 43 | } 44 | 45 | /** 46 | * @return Orchestra 47 | */ 48 | public static function assert(FixerInterface $fixer) 49 | { 50 | return new self($fixer); 51 | } 52 | 53 | /** 54 | * @return Orchestra 55 | */ 56 | public function before(FixerInterface $other) 57 | { 58 | echo sprintf("Run %s before %s\n", $this->fixer->getName(), $other->getName()); 59 | 60 | Assert::greaterThan( 61 | $this->fixer->getPriority(), 62 | $other->getPriority() 63 | ); 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * @return Orchestra 70 | */ 71 | public function after(FixerInterface $other) 72 | { 73 | echo sprintf("Run %s after %s\n", $this->fixer->getName(), $other->getName()); 74 | 75 | Assert::lessThan( 76 | $this->fixer->getPriority(), 77 | $other->getPriority() 78 | ); 79 | 80 | return $this; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/Runner.php: -------------------------------------------------------------------------------- 1 | in($directory) 63 | ->name('*.php') 64 | ; 65 | 66 | foreach ($finder as $file) { 67 | $class = str_replace('/', '\\', mb_substr($file->getPathName(), mb_strlen(__DIR__) - 5, -4)); 68 | 69 | if (class_exists($class) === false) { 70 | continue; 71 | } 72 | 73 | if (is_subclass_of($class, UseCase::class) === false) { 74 | continue; 75 | } 76 | 77 | $usecase = new $class(); 78 | 79 | if ($usecase->getMinSupportedPhpVersion() > \PHP_VERSION_ID) { 80 | continue; 81 | } 82 | 83 | $fixer = $usecase->getFixer(); 84 | $tokens = Tokens::fromCode($usecase->getRawScript()); 85 | 86 | $differ = new Differ(); 87 | 88 | echo "#######################################################################################\n"; 89 | echo "{$class}\n"; 90 | echo "#######################################################################################\n"; 91 | echo "\n"; 92 | 93 | $fixer->fix(new SplFileInfo(__FILE__), $tokens); 94 | 95 | if ($usecase->getExpectation() !== $tokens->generateCode()) { 96 | throw new Exception($differ->diff($usecase->getExpectation(), $tokens->generateCode())); 97 | } 98 | } 99 | } 100 | 101 | private static function runAnalyzerIntegrations(): void 102 | { 103 | $directory = sprintf('%s/TokensAnalyzerIntegration', __DIR__); 104 | 105 | $finder = new Finder(); 106 | $finder 107 | ->in($directory) 108 | ->name('*.php') 109 | ; 110 | 111 | foreach ($finder as $file) { 112 | $class = str_replace('/', '\\', mb_substr($file->getPathName(), mb_strlen(__DIR__) - 5, -4)); 113 | 114 | if (class_exists($class) === false) { 115 | continue; 116 | } 117 | 118 | if (is_subclass_of($class, TokensAnalyzerIntegration::class) === false) { 119 | continue; 120 | } 121 | 122 | $integration = new $class(); 123 | 124 | if ($integration->getMinSupportedPhpVersion() > \PHP_VERSION_ID) { 125 | continue; 126 | } 127 | 128 | $tokens = Tokens::fromCode($integration->getCode()); 129 | 130 | echo "#######################################################################################\n"; 131 | echo "{$class}\n"; 132 | echo "#######################################################################################\n"; 133 | echo "\n"; 134 | 135 | $integration->assertions(new TokensAnalyzer($tokens), $tokens); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration.php: -------------------------------------------------------------------------------- 1 | tokensContaining($tokens, $content); 36 | 37 | return current($indexes); 38 | } 39 | 40 | /** 41 | * @param string $content 42 | * 43 | * @return int[] 44 | */ 45 | protected function tokensContaining(Tokens $tokens, $content) 46 | { 47 | $indexes = []; 48 | 49 | foreach ($tokens as $index => $token) { 50 | if ($content === $token->getContent()) { 51 | $indexes[] = $index; 52 | } 53 | } 54 | 55 | if (empty($indexes)) { 56 | throw new Exception(sprintf('There is no token containing %s.', $content)); 57 | } 58 | 59 | return $indexes; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/InSwitch.php: -------------------------------------------------------------------------------- 1 | isInsideSwitchCase( 46 | $this->tokenContaining($tokens, "'foo'") 47 | ) 48 | ); 49 | 50 | Assert::true( 51 | $analyzer->isInsideSwitchCase( 52 | $this->tokenContaining($tokens, "'bar'") 53 | ) 54 | ); 55 | 56 | Assert::true( 57 | $analyzer->isInsideSwitchCase( 58 | $this->tokenContaining($tokens, "'baz'") 59 | ) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/LineIndentation.php: -------------------------------------------------------------------------------- 1 | getLineIndentation( 46 | $this->tokenContaining($tokens, 'theFunction') 47 | ), 48 | ' ' 49 | ); 50 | 51 | Assert::eq( 52 | $analyzer->getLineIndentation( 53 | $this->tokenContaining($tokens, "'foo'") 54 | ), 55 | ' ' 56 | ); 57 | 58 | Assert::eq( 59 | $analyzer->getLineIndentation( 60 | $this->tokenContaining($tokens, "'baz'") 61 | ), 62 | ' ' 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/MethodArguments.php: -------------------------------------------------------------------------------- 1 | user = $user; 40 | } 41 | } 42 | PHP; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function assertions(TokensAnalyzer $analyzer, Tokens $tokens): void 49 | { 50 | $methods = array_filter( 51 | $analyzer->getClassyElements(), 52 | function ($element) { 53 | return $element['type'] === 'method'; 54 | } 55 | ); 56 | 57 | Assert::count($methods, 3); 58 | 59 | [$contructor, $theFunction, $setUser] = array_keys($methods); 60 | 61 | $arguments = $analyzer->getMethodArguments($contructor); 62 | Assert::eq($analyzer->getNumberOfArguments($contructor), 0); 63 | Assert::count($arguments, 0); 64 | 65 | $arguments = $analyzer->getMethodArguments($theFunction); 66 | Assert::eq($analyzer->getNumberOfArguments($theFunction), 2); 67 | Assert::count($arguments, 2); 68 | Assert::eq( 69 | $arguments, 70 | [ 71 | ($theFunction + 5) => [ 72 | 'type' => 'Domain\\Model\\User', 73 | 'name' => '$user', 74 | 'nullable' => false, 75 | 'asDefault' => false, 76 | ], 77 | ($theFunction + 14) => [ 78 | 'type' => null, 79 | 'name' => '$boolean', 80 | 'nullable' => false, 81 | 'asDefault' => true, 82 | ], 83 | ] 84 | ); 85 | 86 | $arguments = $analyzer->getMethodArguments($setUser); 87 | Assert::eq($analyzer->getNumberOfArguments($setUser), 1); 88 | Assert::count($arguments, 1); 89 | Assert::eq( 90 | $arguments, 91 | [ 92 | ($setUser + 4) => [ 93 | 'type' => null, 94 | 'name' => '$user', 95 | 'nullable' => false, 96 | 'asDefault' => false, 97 | ], 98 | ] 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/NextSemiColon.php: -------------------------------------------------------------------------------- 1 | then(function (array $states) { 30 | return array_map(function (array $state) { 31 | if ($state['state'] === PromiseInterface::FULFILLED) { 32 | return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $state['value']->getBody()->getContents()); 33 | } 34 | 35 | $uri = (string) $state['reason']->getRequest()->getUri(); 36 | 37 | $this->logger->error("Error while trying to fetch article whitelist from `{$uri}`", [ 38 | 'error' => $state['reason'], 39 | ]); 40 | }, $states); 41 | }) 42 | ; 43 | } 44 | } 45 | PHP; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function assertions(TokensAnalyzer $analyzer, Tokens $tokens): void 52 | { 53 | Assert::eq( 54 | $analyzer->getNextSemiColon( 55 | $this->tokenContaining($tokens, 'array_map') 56 | ), 57 | max($this->tokensContaining($tokens, '$states')) + 2 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/Parenthesis.php: -------------------------------------------------------------------------------- 1 | name; 38 | } 39 | 40 | public function setName(string $name): void 41 | { 42 | $this->name = $name; 43 | } 44 | } 45 | PHP; 46 | } 47 | 48 | public function assertions(TokensAnalyzer $analyzer, Tokens $tokens): void 49 | { 50 | Assert::eq( 51 | $analyzer->getClosingParenthesis($this->tokenContaining($tokens, 'getId') + 1), 52 | $this->tokenContaining($tokens, 'getId') + 2 53 | ); 54 | 55 | Assert::eq( 56 | $analyzer->getClosingParenthesis($this->tokenContaining($tokens, 'getType') + 1), 57 | $this->tokenContaining($tokens, 'getType') + 2 58 | ); 59 | 60 | Assert::eq( 61 | $analyzer->getClosingParenthesis($this->tokenContaining($tokens, 'getName') + 1), 62 | $this->tokenContaining($tokens, 'getName') + 2 63 | ); 64 | 65 | Assert::eq( 66 | $analyzer->getClosingParenthesis($this->tokenContaining($tokens, 'setName') + 1), 67 | $this->tokenContaining($tokens, 'setName') + 5 68 | ); 69 | } 70 | 71 | /** 72 | * @return int 73 | */ 74 | public function getMinSupportedPhpVersion() 75 | { 76 | return 70100; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/ReturnedType.php: -------------------------------------------------------------------------------- 1 | name; 41 | } 42 | 43 | public function setName(string $name): void 44 | { 45 | $this->name = $name; 46 | } 47 | } 48 | PHP; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function assertions(TokensAnalyzer $analyzer, Tokens $tokens): void 55 | { 56 | Assert::eq( 57 | $analyzer->getReturnedType( 58 | $this->tokenContaining($tokens, 'getId') - 2 59 | ), 60 | null 61 | ); 62 | 63 | Assert::eq( 64 | $analyzer->getReturnedType( 65 | $this->tokenContaining($tokens, 'getType') - 2 66 | ), 67 | 'string' 68 | ); 69 | 70 | Assert::eq( 71 | $analyzer->getReturnedType( 72 | $this->tokenContaining($tokens, 'getName') - 2 73 | ), 74 | ['string', null] 75 | ); 76 | 77 | Assert::eq( 78 | $analyzer->getReturnedType( 79 | $this->tokenContaining($tokens, 'setName') - 2 80 | ), 81 | 'void' 82 | ); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function getMinSupportedPhpVersion() 89 | { 90 | return 70100; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/TokensAnalyzerIntegration/SizeOfTheLine.php: -------------------------------------------------------------------------------- 1 | isAVeryLongMethodCall() 29 | ->andThisIsAnOtherMethod() 30 | ; 31 | } 32 | } 33 | PHP; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function assertions(TokensAnalyzer $analyzer, Tokens $tokens): void 40 | { 41 | Assert::eq( 42 | $analyzer->getSizeOfTheLine( 43 | $this->tokenContaining($tokens, 'TheClass') 44 | ), 45 | 14 46 | ); 47 | 48 | Assert::eq( 49 | $analyzer->getSizeOfTheLine( 50 | $this->tokenContaining($tokens, 'theFunction') 51 | ), 52 | 33 53 | ); 54 | 55 | Assert::eq( 56 | $analyzer->getSizeOfTheLine( 57 | $this->tokenContaining($tokens, 'isAVeryLongMethodCall') 58 | ), 59 | 38 60 | ); 61 | 62 | Assert::eq( 63 | $analyzer->getSizeOfTheLine( 64 | $this->tokenContaining($tokens, 'andThisIsAnOtherMethod') 65 | ), 66 | 38 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/UseCase.php: -------------------------------------------------------------------------------- 1 | abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 49 | 50 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 51 | } 52 | 53 | public function down(Schema $schema): void 54 | { 55 | // this down() migration is auto-generated, please modify it to your needs 56 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 57 | 58 | $this->addSql('DROP TABLE admin'); 59 | } 60 | } 61 | PHP; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function getExpectation() 68 | { 69 | return <<<'PHP' 70 | abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 89 | 90 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 91 | } 92 | 93 | public function down(Schema $schema): void 94 | { 95 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 96 | 97 | $this->addSql('DROP TABLE admin'); 98 | } 99 | } 100 | PHP; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function getMinSupportedPhpVersion() 107 | { 108 | return 70100; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/UseCase/DoctrineMigrations/UselessGetDescription.php: -------------------------------------------------------------------------------- 1 | abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 45 | 46 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 47 | } 48 | 49 | public function down(Schema $schema): void 50 | { 51 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 52 | 53 | $this->addSql('DROP TABLE admin'); 54 | } 55 | } 56 | PHP; 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function getExpectation() 63 | { 64 | return <<<'PHP' 65 | abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 80 | 81 | $this->addSql('CREATE TABLE admin (identifier CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\', PRIMARY KEY(identifier)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); 82 | } 83 | 84 | public function down(Schema $schema): void 85 | { 86 | $this->abortIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.'); 87 | 88 | $this->addSql('DROP TABLE admin'); 89 | } 90 | } 91 | PHP; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function getMinSupportedPhpVersion() 98 | { 99 | return 70100; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/UseCase/OrderedSettersAndGetters/OrderedSettersAndGettersFirst.php: -------------------------------------------------------------------------------- 1 | items = new ArrayCollection(); 60 | 61 | foreach ($data as $key => $value) { 62 | $this->$key = $value; 63 | } 64 | } 65 | 66 | public function setFirstName($firstName) 67 | { 68 | $this->firstName = $firstName; 69 | } 70 | 71 | public function setName($name) 72 | { 73 | $this->name = $name; 74 | } 75 | 76 | public function setItems($items) 77 | { 78 | $this->items = $items; 79 | } 80 | 81 | public function enable() 82 | { 83 | $this->enabled = true; 84 | } 85 | 86 | public function disable() 87 | { 88 | $this->enabled = false; 89 | } 90 | 91 | public function removeItem($item) 92 | { 93 | $this->items->removeElement($item); 94 | 95 | return $this; 96 | } 97 | 98 | public function isEnabled() 99 | { 100 | return $this->enabled; 101 | } 102 | 103 | public function getName() 104 | { 105 | return $this->name; 106 | } 107 | 108 | public function addItem($item) 109 | { 110 | $this->items[] = $item; 111 | 112 | return $this; 113 | } 114 | 115 | public function getIdentifier() 116 | { 117 | return $this->identifier; 118 | } 119 | 120 | public function getFirstName() 121 | { 122 | return $this->firstName; 123 | } 124 | 125 | public function hasIdentifier() 126 | { 127 | return null !== $this->identifier; 128 | } 129 | 130 | public function getItems($items) 131 | { 132 | return $this->items; 133 | } 134 | } 135 | PHP; 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function getExpectation() 142 | { 143 | return <<<'PHP' 144 | items = new ArrayCollection(); 178 | 179 | foreach ($data as $key => $value) { 180 | $this->$key = $value; 181 | } 182 | } 183 | 184 | public function setFirstName($firstName) 185 | { 186 | $this->firstName = $firstName; 187 | } 188 | 189 | public function getFirstName() 190 | { 191 | return $this->firstName; 192 | } 193 | 194 | public function setName($name) 195 | { 196 | $this->name = $name; 197 | } 198 | 199 | public function getName() 200 | { 201 | return $this->name; 202 | } 203 | 204 | public function setItems($items) 205 | { 206 | $this->items = $items; 207 | } 208 | 209 | public function addItem($item) 210 | { 211 | $this->items[] = $item; 212 | 213 | return $this; 214 | } 215 | 216 | public function removeItem($item) 217 | { 218 | $this->items->removeElement($item); 219 | 220 | return $this; 221 | } 222 | 223 | public function getItems($items) 224 | { 225 | return $this->items; 226 | } 227 | 228 | public function enable() 229 | { 230 | $this->enabled = true; 231 | } 232 | 233 | public function disable() 234 | { 235 | $this->enabled = false; 236 | } 237 | 238 | public function isEnabled() 239 | { 240 | return $this->enabled; 241 | } 242 | 243 | public function hasIdentifier() 244 | { 245 | return null !== $this->identifier; 246 | } 247 | 248 | public function getIdentifier() 249 | { 250 | return $this->identifier; 251 | } 252 | } 253 | PHP; 254 | } 255 | 256 | /** 257 | * {@inheritdoc} 258 | */ 259 | public function getMinSupportedPhpVersion() 260 | { 261 | return 0; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /tools/Utils.php: -------------------------------------------------------------------------------- 1 | ' . self::valueToString($value); 20 | }, $array, array_keys($array))); 21 | } 22 | 23 | $string .= ' ]'; 24 | 25 | return $string; 26 | } 27 | 28 | private static function valueToString($value = null) 29 | { 30 | if (is_string($value)) { 31 | return sprintf('\'%s\'', $value); 32 | } 33 | 34 | if (is_bool($value)) { 35 | return $value ? 'true' : 'false'; 36 | } 37 | 38 | if (is_array($value)) { 39 | return self::arrayToString($value); 40 | } 41 | 42 | if ($value === null) { 43 | return '~'; 44 | } 45 | 46 | return $value; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tools/doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getDefinition()->getCodeSamples(); 15 | 16 | return [ 17 | 'name' => $fixer->getName(), 18 | 'doc' => [ 19 | 'summary' => $fixer->getDefinition()->getSummary(), 20 | ], 21 | 'deprecated' => $fixer->isDeprecated(), 22 | 'replacement' => $fixer->getDeprecationReplacement(), 23 | 'samples' => array_map(function (CodeSample $sample) use ($fixer) { 24 | if ($fixer instanceof ConfigurableFixerInterface) { 25 | $fixer->configure($sample->getConfiguration()); 26 | } 27 | 28 | $tokens = Tokens::fromCode($fixer->getSampleCode()); 29 | $differ = new Differ(); 30 | 31 | if ($fixer->isCandidate($tokens)) { 32 | $fixer->fix(new SplFileInfo(__FILE__), $tokens); 33 | $diff = explode("\n", $differ->diff($fixer->getSampleCode(), $tokens->generateCode())); 34 | 35 | foreach ($diff as $num => $line) { 36 | if (strlen($line) > 80 + 1) { 37 | continue; 38 | } 39 | 40 | while (strlen($line) < 80 + 1) { 41 | $line = $line.' '; 42 | } 43 | 44 | if (0 === $num) { 45 | $line = $line.'// 80 chars'; 46 | } else { 47 | $line = $line.'//'; 48 | } 49 | 50 | $diff[$num] = $line; 51 | } 52 | } else { 53 | $diff = ['+ Fixing not supported by your PHP version.']; 54 | } 55 | 56 | return [ 57 | 'diff' => implode("\n", $diff), 58 | 'configuration' => $sample->getConfiguration() 59 | ? Utils::arrayToString($sample->getConfiguration()) 60 | : null, 61 | ]; 62 | }, $samples), 63 | ]; 64 | }, iterator_to_array(new Taptima\CS\Fixers())); 65 | 66 | $loader = new Twig_Loader_Filesystem(__DIR__); 67 | $twig = new Twig_Environment($loader); 68 | 69 | echo $twig->render('doc.twig', ['fixers' => json_decode(json_encode($fixers))]); 70 | -------------------------------------------------------------------------------- /tools/doc.twig: -------------------------------------------------------------------------------- 1 | # PHP-CS-Fixer 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/taptima/php-cs-fixer/v/stable)](https://packagist.org/packages/taptima/php-cs-fixer) 4 | [![License](https://poser.pugx.org/taptima/php-cs-fixer/license)](https://packagist.org/packages/taptima/php-cs-fixer) 5 | 6 | This repository appeared thanks to the [PedroTroller/PhpCSFixer-Custom-Fixers](https://github.com/PedroTroller/PhpCSFixer-Custom-Fixers) and the code from this repository is used here. 7 | 8 | # Installation 9 | 10 | ```bash 11 | composer require --dev taptima/php-cs-fixer dev-master 12 | ``` 13 | 14 | ### Configuration 15 | 16 | ```php 17 | // .php_cs 18 | registerCustomFixers(new Taptima\CS\Fixers()) 23 | // ... 24 | ; 25 | 26 | return $config; 27 | ``` 28 | 29 | You can also include the ruleset used by the Taptima company. It includes the @Symfony, @PSR and other rules to get the best codestyle result. 30 | @Taptima rule set can be viewed [here](https://github.com/taptima/php-cs-fixer/blob/master/src/Taptima/CS/RuleSetFactory.php#L15). 31 | ```php 32 | // .php_cs 33 | setRules( 37 | Taptima\CS\RuleSetFactory::create([ 38 | '@Taptima' => true, 39 | // other rules 40 | ]) 41 | ->taptima() 42 | ->getRules() 43 | ) 44 | ->registerCustomFixers( 45 | new Taptima\CS\Fixers() 46 | ) 47 | 48 | return $config; 49 | ``` 50 | 51 | # Fixers 52 | 53 | {% for fixer in fixers %} 54 | 55 | ## {{ fixer.name }} 56 | 57 | {{ fixer.doc.summary|raw }} 58 | 59 | {% if fixer.deprecated %} 60 | **DEPRECATED** 61 | {% if fixer.replacement is not empty %} 62 | replaced by `{{ fixer.replacement }}`. 63 | 64 | {% endif %} 65 | {% endif %} 66 | {% for sample in fixer.samples -%} 67 | ### Configuration 68 | 69 | ```php 70 | // .php_cs 71 | setRules([ 76 | // ... 77 | {% if sample.configuration -%} 78 | '{{ fixer.name }}' => {{ sample.configuration|raw }}, 79 | {% else -%} 80 | '{{ fixer.name }}' => true, 81 | {% endif -%} 82 | // ... 83 | ]) 84 | // ... 85 | ->registerCustomFixers(new Taptima\CS\Fixers()) 86 | ; 87 | 88 | return $config; 89 | ``` 90 | 91 | **OR** using my [rule list builder](doc/rule-set-factory.md). 92 | 93 | ```php 94 | // .php_cs.dist 95 | setRules(Taptima\CS\RuleSetFactory::create() 100 | {% if sample.configuration -%} 101 | ->enable('{{ fixer.name }}', {{ sample.configuration|raw }}) 102 | {% else -%} 103 | ->enable('{{ fixer.name }}') 104 | {% endif -%} 105 | ->getRules() 106 | ]) 107 | // ... 108 | ->registerCustomFixers(new Taptima\CS\Fixers()) 109 | ; 110 | 111 | return $config; 112 | ``` 113 | 114 | {% if false == fixer.deprecated %} 115 | ### Fixes 116 | 117 | ```diff 118 | {{ sample.diff|raw }} 119 | ``` 120 | {% endif %} 121 | {% endfor %} 122 | {% endfor %} 123 | 124 | # Contributions 125 | 126 | Before to create a pull request to submit your contributon, you must: 127 | - run tests and be sure nothing is broken 128 | - rebuilt the documentation 129 | 130 | ## How to run tests 131 | 132 | ```bash 133 | composer tests 134 | ``` 135 | 136 | ## How to rebuild the documentation 137 | 138 | ```bash 139 | tools/doc > README.md 140 | ``` 141 | --------------------------------------------------------------------------------