├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── composer.json ├── config ├── contao.php └── set │ ├── psr1.php │ ├── psr12.php │ ├── psr2.php │ └── symfony.php ├── ecs.php ├── phpunit.xml.dist ├── rector.php ├── src ├── Fixer │ ├── AssertEqualsFixer.php │ ├── CaseCommentIndentationFixer.php │ ├── ChainedMethodBlockFixer.php │ ├── CommentLengthFixer.php │ ├── ExpectsWithCallbackFixer.php │ ├── FindByPkFixer.php │ ├── FunctionCallWithMultilineArrayFixer.php │ ├── IndentationFixerTrait.php │ ├── InlinePhpdocCommentFixer.php │ ├── IsArrayNotEmptyFixer.php │ ├── MockMethodChainingIndentationFixer.php │ ├── MultiLineIfIndentationFixer.php │ ├── MultiLineLambdaFunctionArgumentsFixer.php │ ├── NoExpectsThisAnyFixer.php │ ├── NoLineBreakBetweenMethodArgumentsFixer.php │ ├── NoSemicolonAfterShortEchoTagFixer.php │ ├── SingleLineConfigureCommandFixer.php │ └── TypeHintOrderFixer.php ├── Set │ └── SetList.php └── Sniffs │ ├── ContaoFrameworkClassAliasSniff.php │ ├── SetDefinitionCommandSniff.php │ └── UseSprintfInExceptionsSniff.php └── tests └── Fixer ├── AssertEqualsFixerTest.php ├── CaseCommentIndentationFixerTest.php ├── ChainedMethodBlockFixerTest.php ├── CommentLengthFixerTest.php ├── ExpectsWithCallbackFixerTest.php ├── FindByPkFixerTest.php ├── FunctionCallWithMultilineArrayFixerTest.php ├── InlinePhpdocCommentFixerTest.php ├── IsArrayNotEmptyFixerTest.php ├── MockMethodChainingIndentationFixerTest.php ├── MultiLineLambdaFunctionArgumentsFixerTest.php ├── MultilineIfIndentationFixerTest.php ├── NoExpectsThisAnyFixerTest.php ├── NoLineBreaksBetweenMethodArgumentsFixerTest.php ├── NoSemicolonAfterShortEchoTagFixerTest.php ├── SingleLineConfigureCommandFixerTest.php └── TypeHintOrderFixerTest.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: ~ 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | tests: 13 | name: PHP ${{ matrix.php }} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | php: [8.2, 8.3, 8.4] 19 | steps: 20 | - name: Setup PHP 21 | uses: shivammathur/setup-php@v2 22 | with: 23 | php-version: ${{ matrix.php }} 24 | extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, pcre, pdo_mysql, zlib 25 | coverage: none 26 | 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | with: 30 | show-progress: false 31 | 32 | - name: Install the dependencies 33 | uses: ramsey/composer-install@v3 34 | 35 | - name: Run the unit tests 36 | run: vendor/bin/phpunit 37 | 38 | nightly: 39 | name: PHP 8.5 40 | runs-on: ubuntu-latest 41 | continue-on-error: true 42 | steps: 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: 8.5 47 | extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, pcre, pdo_mysql, zlib 48 | coverage: none 49 | 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | show-progress: false 54 | 55 | - name: Install the dependencies 56 | uses: ramsey/composer-install@v3 57 | with: 58 | composer-options: --ignore-platform-req=php+ 59 | 60 | - name: Run the unit tests 61 | run: vendor/bin/phpunit 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | /composer.lock 3 | /vendor/ 4 | 5 | # PhpUnit 6 | /.phpunit.cache/ 7 | /phpunit.xml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyCodingStandard configurations for Contao 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | This package includes the [EasyCodingStandard][1] configuration for [Contao][2]. 10 | 11 | ## Installation 12 | 13 | Add the package to your Contao installation via Composer: 14 | 15 | ```bash 16 | composer require contao/easy-coding-standard --dev 17 | ``` 18 | 19 | ## Usage 20 | 21 | Create a file named `ecs.php` in the root directory of your project. 22 | 23 | ```php 24 | withSets([SetList::CONTAO]) 33 | // Adjust the configuration according to your needs. 34 | ; 35 | ``` 36 | 37 | Then run the script like this: 38 | 39 | ```bash 40 | vendor/bin/ecs check 41 | ``` 42 | 43 | ## What's inside? 44 | 45 | The package contains the following custom fixers: 46 | 47 | | Class | Description | 48 | | --- | --- | 49 | | [`AssertEqualsFixer`](src/Fixer/AssertEqualsFixer.php) | Replaces `asserEquals()` with `assertSame()` in unit tests unless the method is used to compare two objects. | 50 | | [`CaseCommentIndentationFixer`](src/Fixer/CaseCommentIndentationFixer.php) | Fixes the comment indentation before a `case` statement. | 51 | | [`ChainedMethodBlockFixer`](src/Fixer/ChainedMethodBlockFixer.php) | Adds an empty line after a block of chained method calls. | 52 | | [`CommentLengthFixer`](src/Fixer/CommentLengthFixer.php) | Adjusts the length of comments regardless of their indentation so that each line is about 80 characters long. | 53 | | [`ExpectsWithCallbackFixer`](src/Fixer/ExpectsWithCallbackFixer.php) | Adjusts the indentation of `$this->callback()` calls inside the `with()` method of a unit test. | 54 | | [`FindByPkFixer`](src/Fixer/FindByPkFixer.php) | Replaces `findByPk()` calls with `findById()`. | 55 | | [`FunctionCallWithMultilineArrayFixer`](src/Fixer/FunctionCallWithMultilineArrayFixer.php) | Fixes the indentation of function calls with multi-line array arguments. | 56 | | [`InlinePhpdocCommentFixer`](src/Fixer/InlinePhpdocCommentFixer.php) | Ensures that inline phpDoc comments are not converted to regular comments. | 57 | | [`IsArrayNotEmptyFixer`](src/Fixer/IsArrayNotEmptyFixer.php) | Fixes the order of `isset()` and `empty()` calls in conjunction with `is_array()` checks. | 58 | | [`MockMethodChainingIndentationFixer`](src/Fixer/MockMethodChainingIndentationFixer.php) | Fixes the indentation of chained mock methods. | 59 | | [`MultiLineIfIndentationFixer`](src/Fixer/MultiLineIfIndentationFixer.php) | Fixes the indentation of multi-line if statements. | 60 | | [`MultiLineLambdaFunctionArgumentsFixer`](src/Fixer/MultiLineLambdaFunctionArgumentsFixer.php) | Fixes the indentation of multi-line lambda function arguments. | 61 | | [`NoExpectsThisAnyFixer`](src/Fixer/NoExpectsThisAnyFixer.php) | Removes the explicit `any()` assertion in unit tests. | 62 | | [`NoLineBreakBetweenMethodArgumentsFixer`](src/Fixer/NoLineBreakBetweenMethodArgumentsFixer.php) | Fixes the indentation of method declarations. | 63 | | [`NoSemicolonAfterShortEchoTagFixer`](src/Fixer/NoSemicolonAfterShortEchoTagFixer.php) | Removes the semicolon after short echo tag instructions. | 64 | | [`SingleLineConfigureCommandFixer`](src/Fixer/SingleLineConfigureCommandFixer.php) | Fixes the indentation of Symfony command arguments and options. | 65 | | [`TypeHintOrderFixer`](src/Fixer/TypeHintOrderFixer.php) | Fixes the type hint order in method declarations. | 66 | 67 | The package contains the following custom sniffs: 68 | 69 | | Class | Description | 70 | | --- | --- | 71 | | [`ContaoFrameworkClassAliasSniff`](src/Sniffs/ContaoFrameworkClassAliasSniff.php) | Prevents using aliased Contao classes instead of their originals. | 72 | | [`SetDefinitionCommandSniff`](src/Sniffs/SetDefinitionCommandSniff.php) | Prevents using the `setDefinition()` method in Symfony commands. | 73 | | [`UseSprintfInExceptionsSniff`](src/Sniffs/UseSprintfInExceptionsSniff.php) | Prevents using string interpolation in exception messages. | 74 | 75 | ## License 76 | 77 | Contao is licensed under the terms of the LGPLv3. 78 | 79 | ## Getting support 80 | 81 | Visit the [support page][3] to learn about the available support options. 82 | 83 | [1]: https://github.com/Symplify/EasyCodingStandard 84 | [2]: https://contao.org 85 | [3]: https://to.contao.org/support 86 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contao/easy-coding-standard", 3 | "description": "EasyCodingStandard configurations for Contao", 4 | "license": "LGPL-3.0-or-later", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Leo Feyer", 9 | "homepage": "https://github.com/leofeyer" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.1", 14 | "kubawerlos/php-cs-fixer-custom-fixers": "^3.14", 15 | "slevomat/coding-standard": "^8.0", 16 | "symplify/easy-coding-standard": "^12.1" 17 | }, 18 | "require-dev": { 19 | "contao/rector": "^1.2", 20 | "phpunit/phpunit": "^11.5" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Contao\\EasyCodingStandard\\": "src/" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Contao\\EasyCodingStandard\\Tests\\": "tests/" 30 | } 31 | }, 32 | "config": { 33 | "allow-plugins": { 34 | "dealerdirect/phpcodesniffer-composer-installer": true 35 | } 36 | }, 37 | "scripts": { 38 | "all": [ 39 | "@rector", 40 | "@ecs", 41 | "@unit-tests" 42 | ], 43 | "ecs": "vendor/bin/ecs check --fix", 44 | "rector": "vendor/bin/rector", 45 | "unit-tests": "vendor/bin/phpunit" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /config/contao.php: -------------------------------------------------------------------------------- 1 | withSets([ 103 | __DIR__.'/set/psr12.php', 104 | __DIR__.'/set/symfony.php', 105 | ]) 106 | ->withRules([ 107 | ArrayIndentationFixer::class, 108 | AssertEqualsFixer::class, 109 | BlankLineAfterStrictTypesFixer::class, 110 | CaseCommentIndentationFixer::class, 111 | ChainedMethodBlockFixer::class, 112 | ClassAttributesSeparationFixer::class, 113 | CombineConsecutiveIssetsFixer::class, 114 | CombineConsecutiveUnsetsFixer::class, 115 | CommentLengthFixer::class, 116 | ContaoFrameworkClassAliasSniff::class, 117 | DeclareStrictTypesFixer::class, 118 | DisallowArrayTypeHintSyntaxSniff::class, 119 | DisallowDirectMagicInvokeCallSniff::class, 120 | ExpectsWithCallbackFixer::class, 121 | FindByPkFixer::class, 122 | FunctionCallWithMultilineArrayFixer::class, 123 | HeredocIndentationFixer::class, 124 | HeredocToNowdocFixer::class, 125 | InlinePhpdocCommentFixer::class, 126 | IsArrayNotEmptyFixer::class, 127 | MethodChainingIndentationFixer::class, 128 | MockMethodChainingIndentationFixer::class, 129 | MultilineCommentOpeningClosingFixer::class, 130 | MultiLineIfIndentationFixer::class, 131 | MultiLineLambdaFunctionArgumentsFixer::class, 132 | NoExpectsThisAnyFixer::class, 133 | NoLineBreakBetweenMethodArgumentsFixer::class, 134 | NoSuperfluousElseifFixer::class, 135 | NoUnsetOnPropertyFixer::class, 136 | NoUselessElseFixer::class, 137 | NoUselessReturnFixer::class, 138 | NullTypeHintOnLastPositionSniff::class, 139 | OrderedClassElementsFixer::class, 140 | ParamReturnAndVarTagMalformsFixer::class, 141 | PhpdocLineSpanFixer::class, 142 | PhpdocNoEmptyReturnFixer::class, 143 | PhpdocOrderByValueFixer::class, 144 | PhpdocTypesCommaSpacesFixer::class, 145 | PhpdocVarAnnotationCorrectOrderFixer::class, 146 | PhpUnitAssertArgumentsOrderFixer::class, 147 | PhpUnitDataProviderReturnTypeFixer::class, 148 | PhpUnitDataProviderStaticFixer::class, 149 | PhpUnitDedicateAssertInternalTypeFixer::class, 150 | PhpUnitExpectationFixer::class, 151 | PhpUnitMockFixer::class, 152 | PhpUnitNamespacedFixer::class, 153 | PhpUnitNoExpectationAnnotationFixer::class, 154 | RegularCallableCallFixer::class, 155 | ReturnAssignmentFixer::class, 156 | RequireCombinedAssignmentOperatorSniff::class, 157 | SelfStaticAccessorFixer::class, 158 | SetDefinitionCommandSniff::class, 159 | SingleLineConfigureCommandFixer::class, 160 | StaticLambdaFixer::class, 161 | StrictComparisonFixer::class, 162 | StrictParamFixer::class, 163 | TernaryToNullCoalescingFixer::class, 164 | TypeHintOrderFixer::class, 165 | UnusedInheritedVariablePassedToClosureSniff::class, 166 | UseArrowFunctionsFixer::class, 167 | UselessAliasSniff::class, 168 | UselessConstantTypeHintSniff::class, 169 | UselessParenthesesSniff::class, 170 | UselessVariableSniff::class, 171 | UseSprintfInExceptionsSniff::class, 172 | VoidReturnFixer::class, 173 | ]) 174 | ->withConfiguredRule(BlankLineBeforeStatementFixer::class, ['statements' => ['do', 'for', 'foreach', 'return', 'switch', 'throw', 'try', 'while']]) 175 | ->withConfiguredRule(DuplicateSpacesSniff::class, ['ignoreSpacesInAnnotation' => true]) 176 | ->withConfiguredRule(EchoTagSyntaxFixer::class, ['format' => 'short']) 177 | ->withConfiguredRule(GeneralPhpdocAnnotationRemoveFixer::class, ['annotations' => ['author', 'inheritdoc']]) 178 | ->withConfiguredRule(ListSyntaxFixer::class, ['syntax' => 'short']) 179 | ->withConfiguredRule(MultilinePromotedPropertiesFixer::class, ['minimum_number_of_parameters' => 2]) 180 | ->withConfiguredRule(MultilineWhitespaceBeforeSemicolonsFixer::class, ['strategy' => 'new_line_for_chained_calls']) 181 | ->withConfiguredRule(NativeConstantInvocationFixer::class, ['fix_built_in' => false, 'include' => ['DIRECTORY_SEPARATOR', 'PHP_SAPI', 'PHP_VERSION_ID'], 'scope' => 'namespaced']) 182 | ->withConfiguredRule(NoWhitespaceBeforeCommaInArrayFixer::class, ['after_heredoc' => true]) 183 | ->withConfiguredRule(NullableTypeDeclarationForDefaultNullValueFixer::class, ['use_nullable_type_declaration' => true]) 184 | ->withConfiguredRule(PhpdocSeparationFixer::class, ['groups' => [['template', 'mixin'], ['preserveGlobalState', 'runInSeparateProcess'], ['copyright', 'license'], ['Attributes', 'Attribute'], ['ORM\\*'], ['Assert\\*']]]) 185 | ->withConfiguredRule(PhpdocToCommentFixer::class, ['ignored_tags' => ['todo', 'see']]) 186 | ->withConfiguredRule(PhpdocTypesFixer::class, ['groups' => ['simple', 'meta']]) 187 | ->withConfiguredRule(PhpUnitTestCaseStaticMethodCallsFixer::class, ['call_type' => 'this']) 188 | ->withConfiguredRule(RandomApiMigrationFixer::class, ['replacements' => ['mt_rand' => 'random_int', 'rand' => 'random_int']]) 189 | ->withConfiguredRule(ReferenceUsedNamesOnlySniff::class, ['searchAnnotations' => true, 'allowFullyQualifiedNameForCollidingClasses' => true, 'allowFullyQualifiedGlobalClasses' => true, 'allowFullyQualifiedGlobalFunctions' => true, 'allowFullyQualifiedGlobalConstants' => true, 'allowPartialUses' => false]) 190 | ->withConfiguredRule(StringImplicitBackslashesFixer::class, ['single_quoted' => 'ignore', 'double_quoted' => 'escape', 'heredoc' => 'escape']) 191 | ->withConfiguredRule(TrailingCommaInMultilineFixer::class, ['elements' => ['arrays', 'arguments', 'match', 'parameters'], 'after_heredoc' => true]) 192 | ->withConfiguredRule(UnusedUsesSniff::class, ['searchAnnotations' => true]) 193 | ->withConfiguredRule(UnusedVariableSniff::class, ['ignoreUnusedValuesWhenOnlyKeysAreUsedInForeach' => true]) 194 | ; 195 | -------------------------------------------------------------------------------- /config/set/psr1.php: -------------------------------------------------------------------------------- 1 | withRules([ 19 | EncodingFixer::class, 20 | FullOpeningTagFixer::class, 21 | ]) 22 | ; 23 | -------------------------------------------------------------------------------- /config/set/psr12.php: -------------------------------------------------------------------------------- 1 | withSets([ 40 | __DIR__.'/psr1.php', 41 | __DIR__.'/psr2.php', 42 | ]) 43 | ->withRules([ 44 | BinaryOperatorSpacesFixer::class, 45 | BlankLineAfterOpeningTagFixer::class, 46 | BlankLineBetweenImportGroupsFixer::class, 47 | BlankLinesBeforeNamespaceFixer::class, 48 | CompactNullableTypeDeclarationFixer::class, 49 | DeclareEqualNormalizeFixer::class, 50 | LowercaseCastFixer::class, 51 | LowercaseStaticReferenceFixer::class, 52 | NewWithParenthesesFixer::class, 53 | NoBlankLinesAfterClassOpeningFixer::class, 54 | NoLeadingImportSlashFixer::class, 55 | NoTrailingWhitespaceInStringFixer::class, 56 | NoUnreachableDefaultArgumentValueFixer::class, 57 | NoWhitespaceInBlankLineFixer::class, 58 | ReturnTypeDeclarationFixer::class, 59 | ShortScalarCastFixer::class, 60 | SingleTraitInsertPerStatementFixer::class, 61 | TernaryOperatorSpacesFixer::class, 62 | VisibilityRequiredFixer::class, 63 | ]) 64 | ->withConfiguredRule(BracesPositionFixer::class, ['allow_single_line_empty_anonymous_classes' => true]) 65 | ->withConfiguredRule(ClassDefinitionFixer::class, ['inline_constructor_arguments' => false, 'space_before_parenthesis' => true]) 66 | ->withConfiguredRule(OrderedImportsFixer::class, ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'none']) 67 | ->withConfiguredRule(SingleImportPerStatementFixer::class, ['group_to_single_imports' => false]) 68 | ; 69 | -------------------------------------------------------------------------------- /config/set/psr2.php: -------------------------------------------------------------------------------- 1 | withRules([ 45 | BlankLineAfterNamespaceFixer::class, 46 | BracesPositionFixer::class, 47 | ClassDefinitionFixer::class, 48 | ConstantCaseFixer::class, 49 | ControlStructureBracesFixer::class, 50 | ControlStructureContinuationPositionFixer::class, 51 | ElseifFixer::class, 52 | FunctionDeclarationFixer::class, 53 | IndentationTypeFixer::class, 54 | LineEndingFixer::class, 55 | LowercaseKeywordsFixer::class, 56 | NoBreakCommentFixer::class, 57 | NoClosingTagFixer::class, 58 | NoMultipleStatementsPerLineFixer::class, 59 | NoSpaceAroundDoubleColonFixer::class, 60 | NoSpacesAfterFunctionNameFixer::class, 61 | NoTrailingWhitespaceFixer::class, 62 | NoTrailingWhitespaceInCommentFixer::class, 63 | SingleBlankLineAtEofFixer::class, 64 | SingleImportPerStatementFixer::class, 65 | SingleLineAfterImportsFixer::class, 66 | SpacesInsideParenthesesFixer::class, 67 | StatementIndentationFixer::class, 68 | SwitchCaseSemicolonToColonFixer::class, 69 | SwitchCaseSpaceFixer::class, 70 | ]) 71 | ->withConfiguredRule(MethodArgumentSpaceFixer::class, ['on_multiline' => 'ensure_fully_multiline']) 72 | ->withConfiguredRule(SingleClassElementPerStatementFixer::class, ['elements' => ['property']]) 73 | ->withConfiguredRule(VisibilityRequiredFixer::class, ['elements' => ['method', 'property']]) 74 | ; 75 | -------------------------------------------------------------------------------- /config/set/symfony.php: -------------------------------------------------------------------------------- 1 | withRules([ 149 | AlignMultilineCommentFixer::class, 150 | ArrayPushFixer::class, 151 | ArraySyntaxFixer::class, 152 | BacktickToShellExecFixer::class, 153 | CastSpacesFixer::class, 154 | ClassReferenceNameCasingFixer::class, 155 | CleanNamespaceFixer::class, 156 | CombineNestedDirnameFixer::class, 157 | ConcatSpaceFixer::class, 158 | DeclareParenthesesFixer::class, 159 | DirConstantFixer::class, 160 | EchoTagSyntaxFixer::class, 161 | EmptyLoopConditionFixer::class, 162 | EregToPregFixer::class, 163 | ErrorSuppressionFixer::class, 164 | FopenFlagOrderFixer::class, 165 | FullyQualifiedStrictTypesFixer::class, 166 | FunctionToConstantFixer::class, 167 | GetClassToClassKeywordFixer::class, 168 | ImplodeCallFixer::class, 169 | IncludeFixer::class, 170 | IncrementStyleFixer::class, 171 | IntegerLiteralCaseFixer::class, 172 | IsNullFixer::class, 173 | LambdaNotUsedImportFixer::class, 174 | LinebreakAfterOpeningTagFixer::class, 175 | LogicalOperatorsFixer::class, 176 | MagicConstantCasingFixer::class, 177 | MagicMethodCasingFixer::class, 178 | ModernizeStrposFixer::class, 179 | ModernizeTypesCastingFixer::class, 180 | NativeFunctionCasingFixer::class, 181 | NativeTypeDeclarationCasingFixer::class, 182 | NoAliasFunctionsFixer::class, 183 | NoAliasLanguageConstructCallFixer::class, 184 | NoAlternativeSyntaxFixer::class, 185 | NoBinaryStringFixer::class, 186 | NoBlankLinesAfterPhpdocFixer::class, 187 | NoEmptyCommentFixer::class, 188 | NoEmptyPhpdocFixer::class, 189 | NoEmptyStatementFixer::class, 190 | NoHomoglyphNamesFixer::class, 191 | NoLeadingNamespaceWhitespaceFixer::class, 192 | NoMixedEchoPrintFixer::class, 193 | NoMultilineWhitespaceAroundDoubleArrowFixer::class, 194 | NonPrintableCharacterFixer::class, 195 | NoNullPropertyInitializationFixer::class, 196 | NoPhp4ConstructorFixer::class, 197 | NoShortBoolCastFixer::class, 198 | NoSinglelineWhitespaceBeforeSemicolonsFixer::class, 199 | NoSpacesAroundOffsetFixer::class, 200 | NoTrailingCommaInSinglelineFixer::class, 201 | NoUnneededFinalMethodFixer::class, 202 | NoUnneededImportAliasFixer::class, 203 | NoUnsetCastFixer::class, 204 | NoUnusedImportsFixer::class, 205 | NoUselessConcatOperatorFixer::class, 206 | NoUselessNullsafeOperatorFixer::class, 207 | NoUselessSprintfFixer::class, 208 | NoWhitespaceBeforeCommaInArrayFixer::class, 209 | NormalizeIndexBraceFixer::class, 210 | ObjectOperatorWithoutWhitespaceFixer::class, 211 | OrderedTraitsFixer::class, 212 | PhpUnitConstructFixer::class, 213 | PhpUnitFqcnAnnotationFixer::class, 214 | PhpUnitMethodCasingFixer::class, 215 | PhpUnitMockShortWillReturnFixer::class, 216 | PhpUnitSetUpTearDownVisibilityFixer::class, 217 | PhpUnitTestAnnotationFixer::class, 218 | PhpdocAlignFixer::class, 219 | PhpdocAnnotationWithoutDotFixer::class, 220 | PhpdocIndentFixer::class, 221 | PhpdocInlineTagNormalizerFixer::class, 222 | PhpdocNoAccessFixer::class, 223 | PhpdocNoAliasTagFixer::class, 224 | PhpdocNoPackageFixer::class, 225 | PhpdocNoUselessInheritdocFixer::class, 226 | PhpdocReturnSelfReferenceFixer::class, 227 | PhpdocScalarFixer::class, 228 | PhpdocSeparationFixer::class, 229 | PhpdocSingleLineVarSpacingFixer::class, 230 | PhpdocSummaryFixer::class, 231 | PhpdocToCommentFixer::class, 232 | PhpdocTrimFixer::class, 233 | PhpdocTrimConsecutiveBlankLineSeparationFixer::class, 234 | PhpdocTypesFixer::class, 235 | PhpdocVarWithoutNameFixer::class, 236 | PsrAutoloadingFixer::class, 237 | SelfAccessorFixer::class, 238 | SemicolonAfterInstructionFixer::class, 239 | SetTypeToCastFixer::class, 240 | SimpleToComplexStringVariableFixer::class, 241 | SingleClassElementPerStatementFixer::class, 242 | SingleLineCommentSpacingFixer::class, 243 | SingleLineThrowFixer::class, 244 | SingleQuoteFixer::class, 245 | SingleSpaceAroundConstructFixer::class, 246 | StandardizeIncrementFixer::class, 247 | StandardizeNotEqualsFixer::class, 248 | StringLengthToEmptyFixer::class, 249 | StringLineEndingFixer::class, 250 | SwitchContinueToBreakFixer::class, 251 | TernaryToElvisOperatorFixer::class, 252 | TrailingCommaInMultilineFixer::class, 253 | TrimArraySpacesFixer::class, 254 | TypeDeclarationSpacesFixer::class, 255 | TypesSpacesFixer::class, 256 | UnaryOperatorSpacesFixer::class, 257 | WhitespaceAfterCommaInArrayFixer::class, 258 | YodaStyleFixer::class, 259 | ]) 260 | ->withConfiguredRule(BlankLineBeforeStatementFixer::class, ['statements' => ['return']]) 261 | ->withConfiguredRule(BracesPositionFixer::class, ['allow_single_line_anonymous_functions' => true, 'allow_single_line_empty_anonymous_classes' => true]) 262 | ->withConfiguredRule(ClassDefinitionFixer::class, ['single_line' => true]) 263 | ->withConfiguredRule(EmptyLoopBodyFixer::class, ['style' => 'braces']) 264 | ->withConfiguredRule(FopenFlagsFixer::class, ['b_mode' => false]) 265 | ->withConfiguredRule(GeneralPhpdocTagRenameFixer::class, ['replacements' => ['inheritDocs' => 'inheritDoc']]) 266 | ->withConfiguredRule(GlobalNamespaceImportFixer::class, ['import_classes' => false, 'import_constants' => false, 'import_functions' => false]) 267 | ->withConfiguredRule(MethodArgumentSpaceFixer::class, ['on_multiline' => 'ignore']) 268 | ->withConfiguredRule(NativeConstantInvocationFixer::class, ['strict' => false]) 269 | ->withConfiguredRule(NativeFunctionInvocationFixer::class, ['include' => ['@compiler_optimized'], 'scope' => 'namespaced', 'strict' => true]) 270 | ->withConfiguredRule(NoExtraBlankLinesFixer::class, ['tokens' => ['attribute', 'case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'switch', 'throw', 'use']]) 271 | ->withConfiguredRule(NoSuperfluousPhpdocTagsFixer::class, ['remove_inheritdoc' => true]) 272 | ->withConfiguredRule(NoUnneededBracesFixer::class, ['namespaces' => true]) 273 | ->withConfiguredRule(NoUnneededControlParenthesesFixer::class, ['statements' => ['break', 'clone', 'continue', 'echo_print', 'others', 'return', 'switch_case', 'yield', 'yield_from']]) 274 | ->withConfiguredRule(NullableTypeDeclarationForDefaultNullValueFixer::class, ['use_nullable_type_declaration' => false]) 275 | ->withConfiguredRule(OperatorLinebreakFixer::class, ['only_booleans' => true]) 276 | ->withConfiguredRule(OrderedImportsFixer::class, ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha']) 277 | ->withConfiguredRule(PhpdocOrderFixer::class, ['order' => ['param', 'return', 'throws']]) 278 | ->withConfiguredRule(PhpdocTagTypeFixer::class, ['tags' => ['inheritdoc' => 'inline']]) 279 | ->withConfiguredRule(PhpdocTypesOrderFixer::class, ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none']) 280 | ->withConfiguredRule(SingleLineCommentStyleFixer::class, ['comment_types' => ['hash']]) 281 | ->withConfiguredRule(SpaceAfterSemicolonFixer::class, ['remove_in_empty_for_expressions' => true]) 282 | ; 283 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | withSets([SetList::CONTAO]) 22 | ->withPaths([ 23 | __DIR__.'/config', 24 | __DIR__.'/src', 25 | __DIR__.'/tests', 26 | __DIR__.'/ecs.php', 27 | __DIR__.'/rector.php', 28 | ]) 29 | ->withSkip([ 30 | CommentLengthFixer::class => [ 31 | 'config/contao.php', 32 | ], 33 | FindByPkFixer::class => [ 34 | 'src/Fixer/FindByPkFixer.php', 35 | ], 36 | ReferenceUsedNamesOnlySniff::class => [ 37 | 'config/contao.php', 38 | ], 39 | ]) 40 | ->withConfiguredRule(HeaderCommentFixer::class, [ 41 | 'header' => "This file is part of Contao.\n\n(c) Leo Feyer\n\n@license LGPL-3.0-or-later", 42 | ]) 43 | ->withParallel() 44 | ->withCache(sys_get_temp_dir().'/ecs/ecs') 45 | ; 46 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | ./tests 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withSets([SetList::CONTAO]) 18 | ->withPaths([ 19 | __DIR__.'/config', 20 | __DIR__.'/src', 21 | __DIR__.'/tests', 22 | __DIR__.'/ecs.php', 23 | __DIR__.'/rector.php', 24 | ]) 25 | ->withParallel() 26 | ->withCache(sys_get_temp_dir().'/ecs/rector') 27 | ; 28 | -------------------------------------------------------------------------------- /src/Fixer/AssertEqualsFixer.php: -------------------------------------------------------------------------------- 1 | 'assertSame', 27 | 'assertNotEquals' => 'assertNotSame', 28 | ]; 29 | 30 | public function getDefinition(): FixerDefinitionInterface 31 | { 32 | return new FixerDefinition( 33 | 'Unless comparing objects, assertSame() should be used instead of asserEquals().', 34 | [ 35 | new CodeSample( 36 | <<<'EOT' 37 | assertSame("foo", $obj->getName()); 44 | $this->assertEquals(new Foo(), $obj); 45 | } 46 | EOT, 47 | ), 48 | ], 49 | ); 50 | } 51 | 52 | public function isCandidate(Tokens $tokens): bool 53 | { 54 | return $tokens->isTokenKindFound(T_STRING); 55 | } 56 | 57 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 58 | { 59 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 60 | if (!$tokens[$index]->isGivenKind(T_STRING)) { 61 | continue; 62 | } 63 | 64 | $name = $tokens[$index]->getContent(); 65 | 66 | if (!isset($this->mapper[$name]) || !$this->isMethodCall($index, $tokens)) { 67 | continue; 68 | } 69 | 70 | $end = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index + 1); 71 | 72 | $argumentsAnalyzer = new ArgumentsAnalyzer(); 73 | $argumentsIndexes = $argumentsAnalyzer->getArguments($tokens, $index + 1, $end); 74 | 75 | $argStart = array_key_first($argumentsIndexes); 76 | $argEnd = $argumentsIndexes[$argStart]; 77 | 78 | // Continue if the expected value is a or contains an object 79 | if ($tokens->findGivenKind(T_NEW, $argStart, $argEnd)) { 80 | continue; 81 | } 82 | 83 | $tokens->offsetSet($index, new Token([T_STRING, $this->mapper[$name]])); 84 | } 85 | } 86 | 87 | private function isMethodCall(int $index, Tokens $tokens): bool 88 | { 89 | return $tokens[$index - 1]->isGivenKind([T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM]) && $tokens[$index + 1]->equals('('); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Fixer/CaseCommentIndentationFixer.php: -------------------------------------------------------------------------------- 1 | isAnyTokenKindsFound([T_CASE, T_DEFAULT]); 56 | } 57 | 58 | public function getPriority(): int 59 | { 60 | // must be run after StatementIndentationFixer 61 | return -10; 62 | } 63 | 64 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 65 | { 66 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 67 | if (!$tokens[$index]->isGivenKind([T_CASE, T_DEFAULT])) { 68 | continue; 69 | } 70 | 71 | $prevMeaningful = $tokens->getPrevNonWhitespace($index); 72 | 73 | if (!$tokens[$prevMeaningful]->isGivenKind(T_COMMENT)) { 74 | continue; 75 | } 76 | 77 | // If there is more than one line break between the comment and the "case" 78 | // statement, the two do not belong to each other. 79 | if (substr_count($tokens->generatePartialCode($prevMeaningful, $index), "\n") > 1) { 80 | continue; 81 | } 82 | 83 | $indentCase = $this->getIndent($tokens, $index); 84 | $indentComment = $this->getIndent($tokens, $prevMeaningful, false); 85 | 86 | if ($indentCase === $indentComment) { 87 | continue; 88 | } 89 | 90 | $indent = ltrim($indentCase, "\n"); 91 | $replacement = rtrim($indentComment, "\t ").$indent; 92 | $i = $prevMeaningful - 1; 93 | 94 | $tokens->offsetSet($prevMeaningful - 1, new Token([T_WHITESPACE, $replacement])); 95 | 96 | // Handle multi-line comments 97 | while (true) { 98 | $prevComment = $tokens->getPrevNonWhitespace($i); 99 | 100 | if (!$tokens[$prevComment]->isGivenKind(T_COMMENT)) { 101 | break; 102 | } 103 | 104 | $indentComment = $this->getIndent($tokens, $prevComment, false); 105 | $replacement = rtrim($indentComment, "\t ").$indent; 106 | $i = $prevComment - 1; 107 | 108 | $tokens->offsetSet($prevComment - 1, new Token([T_WHITESPACE, $replacement])); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Fixer/ChainedMethodBlockFixer.php: -------------------------------------------------------------------------------- 1 | createMock(Foo::class); 40 | $mock 41 | ->method('isFoo') 42 | ->willReturn(true) 43 | ; 44 | 45 | $mock 46 | ->method('isBar') 47 | ->willReturn(false) 48 | ; 49 | 50 | $mock->isFoo(); 51 | } 52 | EOT, 53 | ), 54 | ], 55 | ); 56 | } 57 | 58 | public function isCandidate(Tokens $tokens): bool 59 | { 60 | return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); 61 | } 62 | 63 | /** 64 | * Must run before StatementIndentationFixer. 65 | */ 66 | public function getPriority(): int 67 | { 68 | return -4; 69 | } 70 | 71 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 72 | { 73 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 74 | if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { 75 | continue; 76 | } 77 | 78 | // Not a chained call 79 | if (!$tokens[$index - 1]->isWhitespace()) { 80 | continue; 81 | } 82 | 83 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 84 | 85 | // Not a method call 86 | if (!$tokens[$nextMeaningful + 1]->equals('(')) { 87 | continue; 88 | } 89 | 90 | $end = $tokens->getNextTokenOfKind($index, [';']); 91 | 92 | if (!$tokens[$end - 1]->isWhitespace()) { 93 | $index = $end; 94 | continue; 95 | } 96 | 97 | $start = $tokens->getPrevTokenOfKind($index, [';', '{']); 98 | $nextMeaningful = $tokens->getNextMeaningfulToken($start); 99 | 100 | if ($tokens[$nextMeaningful]->equals('}')) { 101 | $index = $end; 102 | continue; 103 | } 104 | 105 | $chainedCalls = $this->getChainedCalls($tokens, $start, $end); 106 | 107 | if ($chainedCalls < 1) { 108 | $index = $end; 109 | continue; 110 | } 111 | 112 | $this->fixLeadingWhitespace($tokens, $start); 113 | 114 | $index = $end; 115 | } 116 | } 117 | 118 | private function getChainedCalls(Tokens $tokens, int $start, int $end): int 119 | { 120 | $chainedCalls = 0; 121 | $operators = $tokens->findGivenKind(T_OBJECT_OPERATOR, $start, $end); 122 | 123 | foreach (array_keys($operators) as $pos) { 124 | if ($tokens[$pos - 1]->isWhitespace()) { 125 | ++$chainedCalls; 126 | } 127 | } 128 | 129 | return $chainedCalls; 130 | } 131 | 132 | private function fixLeadingWhitespace(Tokens $tokens, int $start): void 133 | { 134 | $prevStart = $tokens->getPrevTokenOfKind($start, [';', '{']); 135 | 136 | if (null === $prevStart) { 137 | return; 138 | } 139 | 140 | $prevIndex = $prevStart; 141 | $prevVar = $this->getBlockVariable($tokens, $prevStart, $prevIndex); 142 | 143 | if (null === $prevVar) { 144 | return; 145 | } 146 | 147 | $addNewLine = false; 148 | $removeNewLine = false; 149 | 150 | if ($this->isMultiline($tokens, $prevIndex, $start)) { 151 | $addNewLine = true; 152 | } else { 153 | $var = $this->getBlockVariable($tokens, $start); 154 | $prevVar = $this->getBlockVariable($tokens, $prevStart); 155 | 156 | if ($var === $prevVar) { 157 | $removeNewLine = true; 158 | } elseif ($prevVar && str_starts_with($prevVar, "$var->")) { 159 | $addNewLine = true; 160 | } 161 | } 162 | 163 | $content = $tokens[$start + 1]->getContent(); 164 | 165 | if ($addNewLine && !$tokens[$start + 2]->isGivenKind(T_WHITESPACE)) { 166 | if (substr_count($content, "\n") < 2) { 167 | $tokens->offsetSet($start + 1, new Token([T_WHITESPACE, str_replace("\n", "\n\n", $content)])); 168 | } 169 | } elseif ($removeNewLine && substr_count($content, "\n") > 1) { 170 | $tokens->offsetSet($start + 1, new Token([T_WHITESPACE, str_replace("\n\n", "\n", $content)])); 171 | } 172 | } 173 | 174 | private function isMultiline(Tokens $tokens, int $start, int $end): bool 175 | { 176 | $lineBreaks = 0; 177 | $operators = $tokens->findGivenKind(T_WHITESPACE, $start, $end); 178 | 179 | foreach (array_keys($operators) as $pos) { 180 | if ($tokens[$pos]->isWhitespace() && str_contains($tokens[$pos]->getContent(), "\n")) { 181 | ++$lineBreaks; 182 | } 183 | } 184 | 185 | return $lineBreaks > 1; 186 | } 187 | 188 | private function getBlockVariable(Tokens $tokens, int $start, int &$prevIndex = 0): string|null 189 | { 190 | $index = $tokens->getNextMeaningfulToken($start); 191 | 192 | if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { 193 | return null; 194 | } 195 | 196 | $prevIndex = $index; 197 | $var = $tokens[$index]->getContent(); 198 | 199 | if ($tokens[$index + 1]->isObjectOperator()) { 200 | $var = $tokens->generatePartialCode($index, $index + 2); 201 | } 202 | 203 | return $var; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/Fixer/CommentLengthFixer.php: -------------------------------------------------------------------------------- 1 | isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); 49 | } 50 | 51 | /** 52 | * Must run after InlinePhpdocCommentFixer. 53 | */ 54 | public function getPriority(): int 55 | { 56 | return 25; 57 | } 58 | 59 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 60 | { 61 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 62 | if ($tokens[$index]->isGivenKind(T_COMMENT)) { 63 | $index = $this->handleComment($tokens, $index); 64 | } elseif ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { 65 | $index = $this->handleDocComment($tokens, $index); 66 | } 67 | } 68 | } 69 | 70 | private function handleComment(Tokens $tokens, int $index): int 71 | { 72 | $content = $tokens[$index]->getContent(); 73 | 74 | if (!str_starts_with($content, '// ')) { 75 | return $index + 1; 76 | } 77 | 78 | // Ignore comments that are on the same line as the code 79 | if (!str_contains($tokens[$index - 1]->getContent(), "\n")) { 80 | return $index + 1; 81 | } 82 | 83 | $end = $index; 84 | $comment = substr($content, 3); 85 | 86 | while (true) { 87 | $next = $tokens->getNextNonWhitespace($end); 88 | 89 | if (null === $next || !$tokens[$next]->isGivenKind(T_COMMENT)) { 90 | break; 91 | } 92 | 93 | $content = $tokens[$next]->getContent(); 94 | 95 | // Preserve lines that contain URLs, TODOs or lists 96 | if (preg_match('#^// (https:|TODO:|- |\d+\. )#', $content)) { 97 | return $next + 1; 98 | } 99 | 100 | $comment .= ' '.substr($content, 3); 101 | $end = $next; 102 | } 103 | 104 | $lines = $this->getLines($comment, 80, '//'); 105 | 106 | if (substr_count((string) end($lines), ' ') < 2) { 107 | $lines = $this->getLines($comment, 86, '//'); 108 | 109 | if (substr_count((string) end($lines), ' ') < 2) { 110 | $lines = $this->getLines($comment, 74, '//'); 111 | } 112 | } 113 | 114 | $new = []; 115 | $indent = $this->getIndent($tokens, $index); 116 | 117 | for ($i = 0, $c = \count($lines); $i < $c; ++$i) { 118 | if ($i > 0) { 119 | $new[] = new Token([T_WHITESPACE, $indent]); 120 | } 121 | 122 | $new[] = new Token([T_COMMENT, $lines[$i]]); 123 | } 124 | 125 | $tokens->clearRange($index, $end); 126 | $tokens->insertAt($index, $new); 127 | 128 | return $end + 1; 129 | } 130 | 131 | private function handleDocComment(Tokens $tokens, int $index): int 132 | { 133 | $text = null; 134 | $newLines = []; 135 | 136 | $docBlock = new DocBlock($tokens[$index]->getContent()); 137 | $lines = $docBlock->getLines(); 138 | $content = end($lines)->getContent(); 139 | $indent = substr($content, 0, strpos($content, '*')); 140 | 141 | foreach ($lines as $line) { 142 | $content = $line->getContent(); 143 | 144 | // Preserve lines that contain URLs, lists or indented content 145 | if ($line->containsATag() || !$line->containsUsefulContent() || preg_match('#^ *\* ( |https:|- |\d+\. )#', $content)) { 146 | if ($text) { 147 | $comment = rtrim($text); 148 | $lns = $this->getLines($comment, 80, '*'); 149 | 150 | if (substr_count((string) end($lns), ' ') < 2) { 151 | $lns = $this->getLines($comment, 86, '*'); 152 | 153 | if (substr_count((string) end($lns), ' ') < 2) { 154 | $lns = $this->getLines($comment, 74, '*'); 155 | } 156 | } 157 | 158 | foreach ($lns as $ln) { 159 | $newLines[] = "$indent$ln\n"; 160 | } 161 | 162 | $text = null; 163 | } 164 | 165 | $newLines[] = $content; 166 | continue; 167 | } 168 | 169 | $text .= rtrim(substr($content, \strlen($indent) + 2)).' '; 170 | } 171 | 172 | $tokens->offsetSet($index, new Token([T_DOC_COMMENT, implode('', $newLines)])); 173 | 174 | return $index + 1; 175 | } 176 | 177 | private function getLines(string $comment, int $length, string $prefix = ''): array 178 | { 179 | $lines = []; 180 | $i = 0; 181 | $chunks = explode(' ', $comment); 182 | 183 | while ([] !== $chunks) { 184 | $word = array_shift($chunks); 185 | 186 | if (!isset($lines[$i])) { 187 | $lines[$i] = $prefix; 188 | } 189 | 190 | if ($prefix !== $lines[$i] && \strlen($lines[$i]) + \strlen($word) > $length) { 191 | $lines[++$i] = $prefix; 192 | } 193 | 194 | $lines[$i] .= " $word"; 195 | } 196 | 197 | return $lines; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Fixer/ExpectsWithCallbackFixer.php: -------------------------------------------------------------------------------- 1 | callback() calls, there must not be a line break before the call.', 31 | [ 32 | new CodeSample( 33 | <<<'EOT' 34 | createMock(Foo::class); 39 | $foo 40 | ->method("bar") 41 | ->with($this->callback( 42 | function () { 43 | } 44 | )); 45 | } 46 | EOT, 47 | ), 48 | new CodeSample( 49 | <<<'EOT' 50 | createMock(Foo::class); 55 | $foo 56 | ->method("bar") 57 | ->with( 58 | $this->callback( 59 | function () { 60 | } 61 | ), 62 | $this->callback( 63 | function () { 64 | } 65 | ) 66 | ); 67 | } 68 | EOT, 69 | ), 70 | ], 71 | ); 72 | } 73 | 74 | public function isCandidate(Tokens $tokens): bool 75 | { 76 | return $tokens->isTokenKindFound(T_STRING); 77 | } 78 | 79 | /** 80 | * Must run after MultiLineLambdaFunctionArgumentsFixer, 81 | * MultilineWhitespaceBeforeSemicolonsFixer. 82 | */ 83 | public function getPriority(): int 84 | { 85 | return 1; 86 | } 87 | 88 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 89 | { 90 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 91 | if (!$tokens[$index]->isGivenKind(T_STRING)) { 92 | continue; 93 | } 94 | 95 | if ( 96 | 'callback' !== $tokens[$index]->getContent() 97 | || !$tokens[$index - 1]->isGivenKind(T_OBJECT_OPERATOR) 98 | || !$tokens[$index + 1]->equals('(') 99 | ) { 100 | continue; 101 | } 102 | 103 | if (!$start = $this->findParentMethodStart($tokens, $index - 1)) { 104 | continue; 105 | } 106 | 107 | $end = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $start); 108 | 109 | $argumentsAnalyzer = new ArgumentsAnalyzer(); 110 | $argumentsIndexes = $argumentsAnalyzer->getArguments($tokens, $start, $end); 111 | 112 | $needsLineBreak = \count($argumentsIndexes) > 1; 113 | $hasLineBreak = $tokens[$start + 1]->isGivenKind(T_WHITESPACE); 114 | 115 | if ($needsLineBreak && !$hasLineBreak) { 116 | $this->addLineBreak($tokens, $argumentsIndexes, $start, $end); 117 | } elseif (!$needsLineBreak && $hasLineBreak) { 118 | $this->removeLineBreak($tokens, $start, $end); 119 | } 120 | } 121 | } 122 | 123 | private function findParentMethodStart(Tokens $tokens, int $start): int 124 | { 125 | do { 126 | if (!$start = (int) $tokens->getPrevTokenOfKind($start, ['('])) { 127 | return 0; 128 | } 129 | } while ('with' !== $tokens[$start - 1]->getContent()); 130 | 131 | return $start; 132 | } 133 | 134 | private function addLineBreak(Tokens $tokens, array $argumentsIndexes, int $start, int $end): void 135 | { 136 | $indent = $this->getIndent($tokens, $start); 137 | 138 | foreach ($argumentsIndexes as $argEnd) { 139 | if ($tokens[$argEnd + 1]->equals(',') && !str_contains($tokens[$argEnd + 2]->getContent(), "\n")) { 140 | $tokens->offsetSet($argEnd + 2, new Token([T_WHITESPACE, $indent])); 141 | } 142 | } 143 | 144 | $whitespaces = $tokens->findGivenKind(T_WHITESPACE, $start + 2, $end); 145 | 146 | foreach ($whitespaces as $pos => $whitespace) { 147 | $content = $whitespace->getContent(); 148 | 149 | if (str_contains($content, "\n")) { 150 | $tokens->offsetSet($pos, new Token([T_WHITESPACE, $content.' '])); 151 | } 152 | } 153 | 154 | $tokens->insertAt($start + 1, new Token([T_WHITESPACE, $indent.' '])); 155 | 156 | if ($tokens[$end + 1]->equals(')')) { 157 | $tokens->insertAt($end + 1, new Token([T_WHITESPACE, $indent])); 158 | } 159 | } 160 | 161 | private function removeLineBreak(Tokens $tokens, int $start, int $end): void 162 | { 163 | $whitespaces = $tokens->findGivenKind(T_WHITESPACE, $start, $end); 164 | 165 | foreach ($whitespaces as $pos => $whitespace) { 166 | $content = $whitespace->getContent(); 167 | 168 | if (str_contains($content, "\n")) { 169 | $tokens->offsetSet($pos, new Token([T_WHITESPACE, substr($content, 0, -4)])); 170 | } 171 | } 172 | 173 | $tokens->clearAt($start + 1); 174 | $tokens->clearAt($end - 1); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Fixer/FindByPkFixer.php: -------------------------------------------------------------------------------- 1 | isAnyTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); 46 | } 47 | 48 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 49 | { 50 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 51 | if (!$tokens[$index]->isGivenKind([T_STRING, T_CONSTANT_ENCAPSED_STRING])) { 52 | continue; 53 | } 54 | 55 | $content = $tokens[$index]->getContent(); 56 | 57 | if (!\in_array($content, ['findByPk', "'findByPk'", '"findByPk"'], true)) { 58 | continue; 59 | } 60 | 61 | if ($tokens[$index]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { 62 | $tokens->offsetSet($index, new Token([T_CONSTANT_ENCAPSED_STRING, "'findById'"])); 63 | } elseif ($this->isMethodCall($index, $tokens)) { 64 | $tokens->offsetSet($index, new Token([T_STRING, 'findById'])); 65 | } 66 | } 67 | } 68 | 69 | private function isMethodCall(int $index, Tokens $tokens): bool 70 | { 71 | if (!$tokens[$index + 1]->equals('(')) { 72 | return false; 73 | } 74 | 75 | if (!$tokens[$index - 1]->isGivenKind([T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM])) { 76 | return false; 77 | } 78 | 79 | return !$tokens[$index - 2]->equals([T_STRING, 'parent']); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Fixer/FunctionCallWithMultilineArrayFixer.php: -------------------------------------------------------------------------------- 1 | "Foo", 49 | "bar" => "Bar", 50 | ]); 51 | EOT, 52 | ), 53 | new CodeSample( 54 | <<<'EOT' 55 | "Foo", 59 | "bar" => "Bar", 60 | ]); 61 | EOT, 62 | ), 63 | new CodeSample( 64 | <<<'EOT' 65 | "Foo", 71 | "bar" => "Bar", 72 | ], 73 | $bar 74 | ); 75 | EOT, 76 | ), 77 | ], 78 | ); 79 | } 80 | 81 | public function isCandidate(Tokens $tokens): bool 82 | { 83 | return $tokens->isTokenKindFound(T_STRING); 84 | } 85 | 86 | /** 87 | * Must run before StatementIndentationFixer. 88 | */ 89 | public function getPriority(): int 90 | { 91 | return -4; 92 | } 93 | 94 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 95 | { 96 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 97 | if (!$tokens[$index]->isGivenKind(T_STRING)) { 98 | continue; 99 | } 100 | 101 | if (!$tokens[$index + 1]->equals('(')) { 102 | continue; 103 | } 104 | 105 | $start = $index + 1; 106 | $end = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $start); 107 | 108 | if (!$this->isMultiLineStatement($tokens, $start, $end)) { 109 | $index = $end + 1; 110 | continue; 111 | } 112 | 113 | $argumentsAnalyzer = new ArgumentsAnalyzer(); 114 | $argumentsIndexes = $argumentsAnalyzer->getArguments($tokens, $start, $end); 115 | 116 | if (1 === \count($argumentsIndexes)) { 117 | $index = $end + 1; 118 | continue; 119 | } 120 | 121 | if (!$this->hasMultilineArrayArgument($tokens, $argumentsIndexes)) { 122 | $index = $end + 1; 123 | continue; 124 | } 125 | 126 | $method = $tokens[$index]->getContent(); 127 | 128 | if ($tokens[$index - 1]->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { 129 | $method = $tokens->generatePartialCode($index - 2, $index); 130 | } 131 | 132 | if (\in_array($method, self::$ignoredMethods, true)) { 133 | $index = $end + 1; 134 | continue; 135 | } 136 | 137 | if ($this->shouldRemoveLineBreaks($tokens, $argumentsIndexes, $method)) { 138 | $this->removeLineBreaks($tokens, $argumentsIndexes, $end); 139 | } else { 140 | $this->addLineBreaks($tokens, $argumentsIndexes, $start, $end); 141 | } 142 | } 143 | } 144 | 145 | private function hasMultilineArrayArgument(Tokens $tokens, array $argumentsIndexes): bool 146 | { 147 | foreach (array_keys($argumentsIndexes) as $start) { 148 | if ($this->isMultilineArrayArgument($tokens, $start)) { 149 | return true; 150 | } 151 | } 152 | 153 | return false; 154 | } 155 | 156 | private function isMultilineArrayArgument(Tokens $tokens, int $start): bool 157 | { 158 | if ($tokens[$start]->isGivenKind([T_ELLIPSIS, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { 159 | $index = $start; 160 | } elseif (!$index = $tokens->getNextMeaningfulToken($start)) { 161 | return false; 162 | } 163 | 164 | if ($tokens[$index]->isGivenKind(T_ELLIPSIS)) { 165 | ++$index; 166 | } 167 | 168 | if (!$tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { 169 | return false; 170 | } 171 | 172 | return str_starts_with($tokens[$index + 1]->getContent(), "\n"); 173 | } 174 | 175 | private function shouldRemoveLineBreaks(Tokens $tokens, array $argumentsIndexes, string $method): bool 176 | { 177 | if (\in_array($method, self::$methods, true)) { 178 | return false; 179 | } 180 | 181 | if (2 !== \count($argumentsIndexes)) { 182 | return false; 183 | } 184 | 185 | [$arg1, $arg2] = array_keys($argumentsIndexes); 186 | 187 | if ($this->isMultilineArrayArgument($tokens, $arg1)) { 188 | return false; 189 | } 190 | 191 | $firstArg = $tokens->generatePartialCode($arg1, $argumentsIndexes[$arg1]); 192 | 193 | return 0 === substr_count(trim($firstArg), ' ') && $this->isMultilineArrayArgument($tokens, $arg2); 194 | } 195 | 196 | private function addLineBreaks(Tokens $tokens, array $argumentsIndexes, int $start, int $end): void 197 | { 198 | $indent = $this->getIndent($tokens, $start); 199 | 200 | $firstArg = null; 201 | $lastArg = $end; 202 | 203 | foreach ($argumentsIndexes as $argStart => $argEnd) { 204 | if ($tokens[$argStart - 1]->equals('(') && !str_starts_with($tokens[$argStart]->getContent(), "\n")) { 205 | $firstArg = $argStart; 206 | } 207 | 208 | if ($this->isMultilineArrayArgument($tokens, $argStart)) { 209 | if (' ' === $tokens[$argStart]->getContent()) { 210 | $tokens->offsetSet($argStart, new Token([T_WHITESPACE, $indent])); 211 | } elseif (str_starts_with($tokens[$argStart]->getContent(), "\n")) { 212 | continue; 213 | } 214 | 215 | $whitespaces = $tokens->findGivenKind(T_WHITESPACE, $argStart, $argEnd); 216 | 217 | foreach ($whitespaces as $pos => $whitespace) { 218 | $content = $whitespace->getContent(); 219 | 220 | if (str_starts_with($content, "\n")) { 221 | $tokens->offsetSet($pos, new Token([T_WHITESPACE, $content.' '])); 222 | } 223 | } 224 | } elseif (' ' === $tokens[$argStart]->getContent()) { 225 | $tokens->insertAt($argStart, new Token([T_WHITESPACE, $indent.' '])); 226 | ++$lastArg; 227 | } 228 | } 229 | 230 | if ($firstArg) { 231 | $tokens->insertAt($firstArg, new Token([T_WHITESPACE, $indent.' '])); 232 | ++$lastArg; 233 | } 234 | 235 | if ($tokens[$lastArg]->equals(')') && !str_starts_with($tokens[$lastArg - 1]->getContent(), "\n")) { 236 | $tokens->insertAt($lastArg, new Token([T_WHITESPACE, $indent])); 237 | } 238 | 239 | if (!$tokens[$lastArg]->equals(')')) { 240 | $tokens->insertAt($lastArg, new Token([T_STRING, ','])); 241 | } 242 | } 243 | 244 | private function removeLineBreaks(Tokens $tokens, array $argumentsIndexes, int $end): void 245 | { 246 | $firstArg = null; 247 | $lastArg = $end; 248 | 249 | foreach ($argumentsIndexes as $argStart => $argEnd) { 250 | if ($tokens[$argStart - 1]->equals('(') && str_starts_with($tokens[$argStart]->getContent(), "\n")) { 251 | $firstArg = $argStart; 252 | } 253 | 254 | if ($this->isMultilineArrayArgument($tokens, $argStart)) { 255 | if (' ' === $tokens[$argStart]->getContent()) { 256 | continue; 257 | } 258 | 259 | if (str_starts_with($tokens[$argStart]->getContent(), "\n")) { 260 | $tokens->offsetSet($argStart, new Token([T_WHITESPACE, ' '])); 261 | } 262 | 263 | $whitespaces = $tokens->findGivenKind(T_WHITESPACE, $argStart, $argEnd); 264 | 265 | foreach ($whitespaces as $pos => $whitespace) { 266 | $content = $whitespace->getContent(); 267 | 268 | if (str_starts_with($content, "\n") && \strlen($content) > 4) { 269 | $tokens->offsetSet($pos, new Token([T_WHITESPACE, substr($content, 0, -4)])); 270 | } 271 | } 272 | } 273 | } 274 | 275 | if ($firstArg) { 276 | $tokens->clearAt($firstArg); 277 | } 278 | 279 | if ($tokens[$lastArg]->equals(')') && str_starts_with($tokens[$lastArg - 1]->getContent(), "\n")) { 280 | $tokens->clearAt($lastArg - 1); 281 | } 282 | 283 | if ($tokens[$lastArg - 2]->equals(',')) { 284 | $tokens->clearAt($lastArg - 2); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/Fixer/IndentationFixerTrait.php: -------------------------------------------------------------------------------- 1 | getPrevTokenOfKind($index, [[T_WHITESPACE]])) { 23 | return ''; 24 | } 25 | } while (!str_contains($tokens[$index]->getContent(), "\n")); 26 | 27 | $content = $tokens[$index]->getContent(); 28 | 29 | if ($normalize) { 30 | $content = "\n".ltrim($content, "\n"); 31 | } 32 | 33 | return $content; 34 | } 35 | 36 | protected function isMultiLineStatement(Tokens $tokens, int $start, int $end): bool 37 | { 38 | $whitespaces = $tokens->findGivenKind(T_WHITESPACE, $start, $end); 39 | 40 | foreach ($whitespaces as $whitespace) { 41 | if (str_contains($whitespace->getContent(), "\n")) { 42 | return true; 43 | } 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Fixer/InlinePhpdocCommentFixer.php: -------------------------------------------------------------------------------- 1 | get(); 37 | } 38 | EOT, 39 | ), 40 | ], 41 | ); 42 | } 43 | 44 | public function isCandidate(Tokens $tokens): bool 45 | { 46 | return $tokens->isTokenKindFound(T_COMMENT); 47 | } 48 | 49 | /** 50 | * Must run after PhpdocToCommentFixer. 51 | */ 52 | public function getPriority(): int 53 | { 54 | return 24; 55 | } 56 | 57 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 58 | { 59 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 60 | if (!$tokens[$index]->isGivenKind(T_COMMENT)) { 61 | continue; 62 | } 63 | 64 | $content = $tokens[$index]->getContent(); 65 | 66 | if (!str_starts_with($content, '/* @') || str_contains($content, "\n")) { 67 | continue; 68 | } 69 | 70 | $tokens->offsetSet($index, new Token([T_DOC_COMMENT, '/** @'.substr($content, 4)])); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Fixer/IsArrayNotEmptyFixer.php: -------------------------------------------------------------------------------- 1 | isTokenKindFound(T_STRING); 51 | } 52 | 53 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 54 | { 55 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 56 | if (!$tokens[$index]->isGivenKind(T_STRING)) { 57 | continue; 58 | } 59 | 60 | if ('is_array' !== $tokens[$index]->getContent() || !$tokens[$index + 1]->equals('(')) { 61 | continue; 62 | } 63 | 64 | $isArrayStart = $index; 65 | 66 | if ($tokens[$isArrayStart - 1]->isGivenKind(T_NS_SEPARATOR)) { 67 | --$isArrayStart; 68 | } 69 | 70 | $prevMeaningful = $tokens->getPrevMeaningfulToken($isArrayStart); 71 | 72 | if (!$tokens[$prevMeaningful]->isGivenKind(T_BOOLEAN_AND) && !$tokens[$prevMeaningful]->equals('(')) { 73 | continue; 74 | } 75 | 76 | $isArrayArg = $index + 1; 77 | $isArrayEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $isArrayArg); 78 | $booleanAnd = $tokens->getNextMeaningfulToken($isArrayEnd); 79 | 80 | if (!$tokens[$booleanAnd]->isGivenKind(T_BOOLEAN_AND)) { 81 | continue; 82 | } 83 | 84 | if (!$empty = $tokens->getNextTokenOfKind($booleanAnd, [[T_ISSET], [T_EMPTY]])) { 85 | continue; 86 | } 87 | 88 | $emptyStart = $empty; 89 | 90 | if ($tokens[$emptyStart - 1]->equals('!')) { 91 | --$emptyStart; 92 | } 93 | 94 | $emptyArg = $empty + 1; 95 | $emptyEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $emptyArg); 96 | 97 | $isArrayContent = $tokens->generatePartialCode($isArrayArg + 1, $isArrayEnd - 1); 98 | $emptyContent = $tokens->generatePartialCode($emptyArg + 1, $emptyEnd - 1); 99 | 100 | if ($isArrayContent !== $emptyContent) { 101 | continue; 102 | } 103 | 104 | $isArrayCode = $tokens->generatePartialCode($isArrayStart, $isArrayEnd); 105 | $emptyCode = $tokens->generatePartialCode($emptyStart, $emptyEnd); 106 | 107 | $tokens->clearRange($isArrayStart, $emptyEnd); 108 | $tokens->insertAt($emptyEnd, Tokens::fromCode($emptyCode.' && '.$isArrayCode)); 109 | 110 | $index = $emptyEnd + 1; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Fixer/MockMethodChainingIndentationFixer.php: -------------------------------------------------------------------------------- 1 | createMock(Foo::class); 60 | $mock 61 | ->method("isBar") 62 | ->willReturn(false); 63 | } 64 | EOT, 65 | ), 66 | ], 67 | ); 68 | } 69 | 70 | public function isCandidate(Tokens $tokens): bool 71 | { 72 | return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); 73 | } 74 | 75 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 76 | { 77 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 78 | if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { 79 | continue; 80 | } 81 | 82 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 83 | 84 | if ( 85 | !$tokens[$nextMeaningful + 1]->equals('(') 86 | || !\in_array($tokens[$nextMeaningful]->getContent(), self::$methods, true) 87 | ) { 88 | continue; 89 | } 90 | 91 | $start = $tokens->getPrevTokenOfKind($index, [';', '{']); 92 | $end = $tokens->getNextTokenOfKind($index, [';']); 93 | $operators = $tokens->findGivenKind(T_OBJECT_OPERATOR, $start, $end); 94 | $operatorsCount = \count($operators); 95 | 96 | /** @var array $variables */ 97 | $variables = $tokens->findGivenKind(T_VARIABLE, $start, $end); 98 | 99 | foreach ($variables as $variableToken) { 100 | if ('$this' === $variableToken->getContent()) { 101 | --$operatorsCount; 102 | } 103 | } 104 | 105 | // Single method call 106 | if (1 === $operatorsCount) { 107 | continue; 108 | } 109 | 110 | $shift = 0; 111 | $indent = $this->getIndent($tokens, $index); 112 | 113 | foreach (array_keys($operators) as $pos) { 114 | $key = $pos + $shift; 115 | 116 | if (!\in_array($tokens[$key + 1]->getContent(), self::$methods, true)) { 117 | continue; 118 | } 119 | 120 | if (!$tokens[$key - 1]->isGivenKind(T_WHITESPACE)) { 121 | $tokens->insertAt($key, new Token([T_WHITESPACE, $indent.' '])); 122 | ++$shift; 123 | } 124 | } 125 | 126 | $index = $end; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Fixer/MultiLineIfIndentationFixer.php: -------------------------------------------------------------------------------- 1 | isAnyTokenKindsFound([T_IF, T_ELSEIF]); 56 | } 57 | 58 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 59 | { 60 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 61 | if (!$tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) { 62 | continue; 63 | } 64 | 65 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 66 | 67 | if (!$tokens[$nextMeaningful]->equals('(')) { 68 | continue; 69 | } 70 | 71 | $indent = $this->getIndent($tokens, $index); 72 | $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextMeaningful); 73 | 74 | if (!$this->isMultiLineStatement($tokens, $nextMeaningful, $index)) { 75 | continue; 76 | } 77 | 78 | // Add a line-break after the opening parenthesis 79 | if (!$tokens[$nextMeaningful + 1]->isGivenKind(T_WHITESPACE)) { 80 | $tokens->insertAt($nextMeaningful + 1, new Token([T_WHITESPACE, $indent.' '])); 81 | ++$index; 82 | } 83 | 84 | // Add a line-break before the closing parenthesis 85 | if (!$tokens[$index - 1]->isGivenKind(T_WHITESPACE)) { 86 | $tokens->insertAt($index, new Token([T_WHITESPACE, $indent])); 87 | ++$index; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Fixer/MultiLineLambdaFunctionArgumentsFixer.php: -------------------------------------------------------------------------------- 1 | isTokenKindFound(T_FUNCTION); 51 | } 52 | 53 | /** 54 | * Must run after MethodArgumentSpaceFixer. 55 | */ 56 | public function getPriority(): int 57 | { 58 | return -3; 59 | } 60 | 61 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 62 | { 63 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 64 | if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { 65 | continue; 66 | } 67 | 68 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 69 | 70 | // Not a lambda function 71 | if (!$tokens[$nextMeaningful]->equals('(')) { 72 | continue; 73 | } 74 | 75 | $prevMeaningful = $tokens->getPrevMeaningfulToken($index); 76 | 77 | if ($tokens[$prevMeaningful]->isGivenKind(T_STATIC)) { 78 | $prevMeaningful = $tokens->getPrevMeaningfulToken($prevMeaningful); 79 | } 80 | 81 | // Not inside a method call 82 | if (!\in_array($tokens[$prevMeaningful]->getContent(), ['(', ','], true)) { 83 | continue; 84 | } 85 | 86 | // The arguments are on separate lines already 87 | if ($this->hasNewline($tokens, $prevMeaningful + 1)) { 88 | continue; 89 | } 90 | 91 | $start = $tokens->getPrevTokenOfKind($index, ['(']); 92 | $end = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $start); 93 | 94 | // No line-breaks required for inline lambda functions 95 | if (!$this->isMultiLineStatement($tokens, $start, $end)) { 96 | continue; 97 | } 98 | 99 | $this->fixIndentation($tokens, $start, $end, $this->getIndent($tokens, $prevMeaningful)); 100 | 101 | $index = $end; 102 | } 103 | } 104 | 105 | private function hasNewline(Tokens $tokens, int $index): bool 106 | { 107 | return $tokens[$index]->isGivenKind(T_WHITESPACE) && str_contains($tokens[$index]->getContent(), "\n"); 108 | } 109 | 110 | private function fixIndentation(Tokens $tokens, int $start, int &$end, string $indent): void 111 | { 112 | $argumentsAnalyzer = new ArgumentsAnalyzer(); 113 | $argumentsIndexes = $argumentsAnalyzer->getArguments($tokens, $start, $end); 114 | 115 | foreach ($argumentsIndexes as $argumentStart => $argumentEnd) { 116 | if ($tokens[$argumentStart]->isGivenKind(T_WHITESPACE)) { 117 | ++$argumentStart; 118 | } 119 | 120 | if ($tokens[$argumentStart]->isGivenKind([T_STATIC, T_FUNCTION])) { 121 | $this->indentFunctionBody($tokens, $argumentStart, $argumentEnd); 122 | } 123 | 124 | if (!$tokens[$argumentEnd + 2]->isGivenKind(T_WHITESPACE)) { 125 | continue; 126 | } 127 | 128 | if ($tokens[$argumentEnd + 1]->equals(',')) { 129 | $tokens->offsetSet($argumentEnd + 2, new Token([T_WHITESPACE, $indent.' '])); 130 | } elseif ($tokens[$argumentEnd + 1]->equals(')')) { 131 | $tokens->offsetSet($argumentEnd + 2, new Token([T_WHITESPACE, substr($indent, 0, -4)])); 132 | } 133 | } 134 | 135 | // Add a line-break after the opening parenthesis 136 | if (!$tokens[$start + 1]->isGivenKind(T_WHITESPACE)) { 137 | $tokens->insertAt($start + 1, new Token([T_WHITESPACE, $indent.' '])); 138 | ++$end; 139 | } 140 | 141 | // Add a line-break before the closing parenthesis 142 | if (!$tokens[$end]->isGivenKind(T_WHITESPACE)) { 143 | $tokens->insertAt($end, new Token([T_WHITESPACE, $indent])); 144 | ++$end; 145 | } 146 | } 147 | 148 | private function indentFunctionBody(Tokens $tokens, int $argumentStart, int $argumentEnd): void 149 | { 150 | if (!$bodyStart = $tokens->getNextTokenOfKind($argumentStart, ['{'])) { 151 | return; 152 | } 153 | 154 | $whitespaces = $tokens->findGivenKind(T_WHITESPACE, $bodyStart, $argumentEnd); 155 | 156 | foreach ($whitespaces as $pos => $whitespace) { 157 | $ws = $whitespace->getContent(); 158 | 159 | if (!str_contains($ws, "\n")) { 160 | continue; 161 | } 162 | 163 | $tokens->offsetSet($pos, new Token([T_WHITESPACE, $ws.' '])); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Fixer/NoExpectsThisAnyFixer.php: -------------------------------------------------------------------------------- 1 | expects($this->any()) assertion is the default and can be removed.', 27 | [ 28 | new CodeSample( 29 | <<<'EOT' 30 | createMock(Foo::class); 39 | $mock 40 | ->expects($this->any()) 41 | ->method("isBar") 42 | ->willReturn(false); 43 | } 44 | EOT, 45 | ), 46 | ], 47 | ); 48 | } 49 | 50 | public function isCandidate(Tokens $tokens): bool 51 | { 52 | return $tokens->isTokenKindFound(T_OBJECT_OPERATOR); 53 | } 54 | 55 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 56 | { 57 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 58 | if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { 59 | continue; 60 | } 61 | 62 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 63 | 64 | if ( 65 | 'expects' !== $tokens[$nextMeaningful]->getContent() 66 | || !$tokens[$nextMeaningful + 1]->equals('(') 67 | ) { 68 | continue; 69 | } 70 | 71 | $end = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextMeaningful + 1); 72 | 73 | if ('($this->any())' !== $tokens->generatePartialCode($nextMeaningful + 1, $end)) { 74 | continue; 75 | } 76 | 77 | if ($tokens[$end + 1]->isGivenKind(T_WHITESPACE)) { 78 | ++$end; 79 | } 80 | 81 | $tokens->clearRange($index, $end); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Fixer/NoLineBreakBetweenMethodArgumentsFixer.php: -------------------------------------------------------------------------------- 1 | isTokenKindFound(T_FUNCTION); 57 | } 58 | 59 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 60 | { 61 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 62 | if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { 63 | continue; 64 | } 65 | 66 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 67 | 68 | if ($tokens[$nextMeaningful]->isGivenKind(CT::T_RETURN_REF)) { 69 | $nextMeaningful = $tokens->getNextMeaningfulToken($nextMeaningful); 70 | } 71 | 72 | $isLambda = !$tokens[$nextMeaningful]->isGivenKind(T_STRING); 73 | $isConstructor = '__construct' === $tokens[$nextMeaningful]->getContent(); 74 | 75 | if (!$isLambda) { 76 | $nextMeaningful = $tokens->getNextMeaningfulToken($nextMeaningful); 77 | } 78 | 79 | if (!$end = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextMeaningful)) { 80 | continue; 81 | } 82 | 83 | $isPropertyPromotion = false; 84 | 85 | if ($isConstructor) { 86 | for ($i = $nextMeaningful; $i < $end; ++$i) { 87 | if ($tokens[$i]->isGivenKind(self::$cppKinds)) { 88 | $isPropertyPromotion = true; 89 | break; 90 | } 91 | } 92 | } 93 | 94 | if ($isPropertyPromotion) { 95 | $index = $end + 1; 96 | continue; 97 | } 98 | 99 | for ($i = $nextMeaningful; $i < $end; ++$i) { 100 | if (!$tokens[$i]->isGivenKind(T_WHITESPACE)) { 101 | continue; 102 | } 103 | 104 | if ($tokens[$i - 1]->equals('(') || $tokens[$i + 1]->equals(')')) { 105 | $tokens->clearAt($i); 106 | } else { 107 | $tokens->offsetSet($i, new Token([T_WHITESPACE, ' '])); 108 | } 109 | } 110 | 111 | $index = $end + 1; 112 | 113 | if ($isLambda) { 114 | continue; 115 | } 116 | 117 | $bodyStart = $tokens->getNextTokenOfKind($index, ['{', ';']); 118 | 119 | // The method is an abstract method 120 | if (!$bodyStart || $tokens[$bodyStart]->equals(';')) { 121 | continue; 122 | } 123 | 124 | // Insert a line-break before the opening curly brace 125 | if (!str_contains($tokens[$bodyStart - 1]->getContent(), "\n")) { 126 | $tokens->offsetSet($bodyStart - 1, new Token([T_WHITESPACE, $this->getIndent($tokens, $index)])); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Fixer/NoSemicolonAfterShortEchoTagFixer.php: -------------------------------------------------------------------------------- 1 | foo; ?>` instruction.', 27 | [new CodeSample('foo; ?>\n')], 28 | ); 29 | } 30 | 31 | public function isCandidate(Tokens $tokens): bool 32 | { 33 | return $tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); 34 | } 35 | 36 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 37 | { 38 | $hasShortEchoTag = false; 39 | 40 | foreach ($tokens as $index => $token) { 41 | if ($token->isGivenKind(T_OPEN_TAG_WITH_ECHO)) { 42 | $hasShortEchoTag = true; 43 | } 44 | 45 | if (!$token->isGivenKind(T_CLOSE_TAG)) { 46 | continue; 47 | } 48 | 49 | $prev = $tokens->getPrevMeaningfulToken($index); 50 | 51 | if ($hasShortEchoTag && $tokens[$prev]->equalsAny([';'])) { 52 | $tokens->clearAt($prev); 53 | } 54 | 55 | $hasShortEchoTag = false; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Fixer/SingleLineConfigureCommandFixer.php: -------------------------------------------------------------------------------- 1 | addArgument("foo", InputArgument::REQUIRED, "The argument") 43 | ->addOption("bar", null, InputOption::VALUE_NONE, "The option"); 44 | } 45 | } 46 | EOT, 47 | ), 48 | ], 49 | ); 50 | } 51 | 52 | public function isCandidate(Tokens $tokens): bool 53 | { 54 | return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION, T_OBJECT_OPERATOR]); 55 | } 56 | 57 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 58 | { 59 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 60 | switch (true) { 61 | case $tokens[$index]->isGivenKind(T_CLASS): 62 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 63 | 64 | // Return if the class is not a command 65 | if (!str_ends_with($tokens[$nextMeaningful]->getContent(), 'Command')) { 66 | return; 67 | } 68 | break; 69 | 70 | case $tokens[$index]->isGivenKind(T_FUNCTION): 71 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 72 | 73 | // Skip the method if it is not the configure() method 74 | if ('configure' !== $tokens[$nextMeaningful]->getContent()) { 75 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 76 | 77 | if ($tokens[$nextMeaningful]->equals('(')) { 78 | $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextMeaningful); 79 | } 80 | } 81 | break; 82 | 83 | case $tokens[$index]->isGivenKind(T_OBJECT_OPERATOR): 84 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 85 | 86 | if (!\in_array($tokens[$nextMeaningful]->getContent(), ['addArgument', 'addOption'], true)) { 87 | continue 2; 88 | } 89 | 90 | $blockStart = $nextMeaningful + 1; 91 | $blockEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $blockStart); 92 | 93 | if ($tokens[$blockStart + 1]->isGivenKind(T_WHITESPACE)) { 94 | $tokens->clearAt(++$blockStart); 95 | } 96 | 97 | if ($tokens[$blockEnd - 1]->isGivenKind(T_WHITESPACE)) { 98 | $tokens->clearAt(--$blockEnd); 99 | } 100 | 101 | for ($i = $blockStart + 1; $i < $blockEnd; ++$i) { 102 | if ($tokens[$i]->isWhitespace()) { 103 | $tokens->offsetSet($i, new Token([T_WHITESPACE, ' '])); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Fixer/TypeHintOrderFixer.php: -------------------------------------------------------------------------------- 1 | isAnyTokenKindsFound([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FUNCTION, T_FN]); 65 | } 66 | 67 | /** 68 | * Must run after NoUselessReturnFixer. 69 | */ 70 | public function getPriority(): int 71 | { 72 | return -20; 73 | } 74 | 75 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 76 | { 77 | for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { 78 | if (!$tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FUNCTION, T_FN])) { 79 | continue; 80 | } 81 | 82 | // Anonymous functions 83 | if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { 84 | $index = $this->handleFunction($tokens, $index); 85 | continue; 86 | } 87 | 88 | $nextMeaningful = $tokens->getNextMeaningfulToken($index); 89 | 90 | // Ignore constants 91 | if ($tokens[$nextMeaningful]->isGivenKind(T_CONST)) { 92 | continue; 93 | } 94 | 95 | if ($tokens[$nextMeaningful]->isGivenKind(T_STATIC)) { 96 | $nextMeaningful = $tokens->getNextMeaningfulToken($nextMeaningful); 97 | } 98 | 99 | if (\defined('T_READONLY') && $tokens[$nextMeaningful]->isGivenKind(T_READONLY)) { 100 | $nextMeaningful = $tokens->getNextMeaningfulToken($nextMeaningful); 101 | } 102 | 103 | // No type hint 104 | if ($tokens[$nextMeaningful]->isGivenKind(T_VARIABLE)) { 105 | continue; 106 | } 107 | 108 | if ($tokens[$nextMeaningful]->isGivenKind(T_FUNCTION)) { 109 | $index = $this->handleFunction($tokens, $nextMeaningful); 110 | } else { 111 | $index = $this->handleClassProperty($tokens, $nextMeaningful); 112 | } 113 | } 114 | } 115 | 116 | private function handleFunction(Tokens $tokens, int $nextMeaningful): int 117 | { 118 | $end = $tokens->getNextTokenOfKind($nextMeaningful, [';', '{']); 119 | 120 | // Arguments 121 | $argsStart = $tokens->getNextTokenOfKind($nextMeaningful, ['(']); 122 | $argsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argsStart); 123 | $vars = $tokens->findGivenKind(T_VARIABLE, $argsStart, $argsEnd); 124 | 125 | if ([] !== $vars) { 126 | foreach (array_keys($vars) as $pos) { 127 | $prevMeaningful = $tokens->getPrevMeaningfulToken($pos); 128 | 129 | // No type hint 130 | if ($tokens[$prevMeaningful]->equals('(') || $tokens[$prevMeaningful]->equals(',')) { 131 | continue; 132 | } 133 | 134 | while ($prevMeaningful - 1 > $argsStart && !$tokens[$prevMeaningful - 1]->isGivenKind(T_WHITESPACE)) { 135 | --$prevMeaningful; 136 | } 137 | 138 | if ($new = $this->orderTypeHint($tokens->generatePartialCode($prevMeaningful, $pos - 2))) { 139 | $tokens->overrideRange($prevMeaningful, $pos - 2, $new); 140 | } 141 | } 142 | } 143 | 144 | // Return type 145 | $vars = $tokens->findGivenKind(CT::T_TYPE_COLON, $argsEnd, $end - 1); 146 | 147 | if ([] !== $vars) { 148 | $start = $stop = array_key_first($vars) + 2; 149 | 150 | while ($stop < $end - 1 && !$tokens[$stop + 1]->isGivenKind(T_WHITESPACE)) { 151 | ++$stop; 152 | } 153 | 154 | if ($new = $this->orderTypeHint($tokens->generatePartialCode($start, $stop))) { 155 | $tokens->overrideRange($start, $stop, $new); 156 | } 157 | } 158 | 159 | return $end; 160 | } 161 | 162 | private function handleClassProperty(Tokens $tokens, int $nextMeaningful): int 163 | { 164 | $end = $tokens->getNextTokenOfKind($nextMeaningful, [';']); 165 | 166 | for ($i = $nextMeaningful; $i <= $end; ++$i) { 167 | if ($tokens[$i]->isGivenKind(T_VARIABLE)) { 168 | if ($new = $this->orderTypeHint($tokens->generatePartialCode($nextMeaningful, $i - 2))) { 169 | $tokens->overrideRange($nextMeaningful, $i - 2, $new); 170 | } 171 | break; 172 | } 173 | } 174 | 175 | return $end; 176 | } 177 | 178 | private function orderTypeHint(string $typehint): Tokens|null 179 | { 180 | if (!str_contains($typehint, '|') && !str_contains($typehint, '?')) { 181 | return null; 182 | } 183 | 184 | $natives = []; 185 | $objects = []; 186 | $hasFalse = false; 187 | $hasNull = false; 188 | 189 | $chunks = explode('|', $typehint); 190 | 191 | foreach ($chunks as $chunk) { 192 | if ('?' === $chunk[0]) { 193 | $chunk = substr($chunk, 1); 194 | $hasNull = true; 195 | } 196 | 197 | if ('false' === $chunk) { 198 | $hasFalse = true; 199 | } elseif ('null' === $chunk) { 200 | $hasNull = true; 201 | } elseif (\in_array($chunk, self::$nativeTypes, true)) { 202 | $natives[$chunk] = $chunk; 203 | } else { 204 | $objects[ltrim($chunk, '\\')] = $chunk; 205 | } 206 | } 207 | 208 | ksort($natives); 209 | ksort($objects); 210 | 211 | $new = implode('|', [...array_values($objects), ...array_values($natives)]); 212 | 213 | if ($hasFalse) { 214 | $new .= '|false'; 215 | } 216 | 217 | if ($hasNull) { 218 | $new .= '|null'; 219 | } 220 | 221 | return Tokens::fromCode($new); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Set/SetList.php: -------------------------------------------------------------------------------- 1 | getTokens(); 29 | 30 | if (T_STRING !== $tokens[$stackPtr]['code'] || !$this->isContaoClass($tokens, $stackPtr)) { 31 | return; 32 | } 33 | 34 | if (T_NS_SEPARATOR !== $tokens[$stackPtr - 1]['code'] && $this->isNamespaced($phpcsFile)) { 35 | return; 36 | } 37 | 38 | if (T_NS_SEPARATOR === $tokens[$stackPtr - 1]['code'] && 'Contao' === $tokens[$stackPtr - 2]['content']) { 39 | return; 40 | } 41 | 42 | if ($this->hasUse($phpcsFile, $tokens[$stackPtr]['content'])) { 43 | return; 44 | } 45 | 46 | $phpcsFile->addError(\sprintf('Using the aliased class "%1$s" is deprecated. Use the original class "Contao\%1$s" instead.', $tokens[$stackPtr]['content']), $stackPtr, self::class); 47 | } 48 | 49 | private function isContaoClass(array $tokens, int $index): bool 50 | { 51 | if (!\in_array($tokens[$index + 1]['code'], [T_OPEN_PARENTHESIS, T_DOUBLE_COLON], true)) { 52 | return false; 53 | } 54 | 55 | if (!preg_match('/^[A-Z]/', (string) $tokens[$index]['content'])) { 56 | return false; 57 | } 58 | 59 | // Skip fully qualified class names 60 | if (T_NS_SEPARATOR === $tokens[$index - 1]['code'] && T_STRING === $tokens[$index - 2]['code']) { 61 | return false; 62 | } 63 | 64 | return class_exists('Contao\\'.$tokens[$index]['content']); 65 | } 66 | 67 | private function isNamespaced(File $file): bool 68 | { 69 | $end = TokenHelper::findNext($file, T_USE, 0); 70 | 71 | return (bool) TokenHelper::findNext($file, T_NAMESPACE, 0, $end); 72 | } 73 | 74 | private function hasUse(File $file, string $class): bool 75 | { 76 | $end = TokenHelper::findNext($file, [T_CLASS, T_INTERFACE, T_TRAIT], 0); 77 | $uses = TokenHelper::findNextAll($file, T_USE, 0, $end); 78 | 79 | foreach ($uses as $use) { 80 | $end = TokenHelper::findNext($file, T_SEMICOLON, $use + 2); 81 | $fqcn = TokenHelper::getContent($file, $use + 2, $end - 1); 82 | 83 | if (preg_match('/\\\\'.preg_quote($class, '/').'$/', $fqcn)) { 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Sniffs/SetDefinitionCommandSniff.php: -------------------------------------------------------------------------------- 1 | getTokens(); 30 | 31 | switch (true) { 32 | case T_CLASS === $tokens[$stackPtr]['code']: 33 | if (!str_ends_with((string) $tokens[$stackPtr + 2]['content'], 'Command')) { 34 | return \count($tokens) + 1; 35 | } 36 | break; 37 | 38 | case T_FUNCTION === $tokens[$stackPtr]['code']: 39 | $this->isConfigure = 'configure' === $tokens[$stackPtr + 2]['content']; 40 | break; 41 | 42 | case T_OBJECT_OPERATOR === $tokens[$stackPtr]['code']: 43 | if ($this->isConfigure && 'setDefinition' === $tokens[$stackPtr + 1]['content']) { 44 | $phpcsFile->addError('Do not use the setDefinition() method to configure commands. Use addArgument() and addOption() instead.', $stackPtr, self::class); 45 | } 46 | break; 47 | } 48 | 49 | return \count($tokens) + 1; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Sniffs/UseSprintfInExceptionsSniff.php: -------------------------------------------------------------------------------- 1 | getTokens(); 29 | 30 | if (T_THROW !== $tokens[$stackPtr]['code']) { 31 | return; 32 | } 33 | 34 | $next = $this->getNextNonWhitespaceToken($tokens, $stackPtr); 35 | 36 | // We are not dealing with "throw new" 37 | if (T_NEW !== $tokens[$next]['code']) { 38 | return; 39 | } 40 | 41 | $next = TokenHelper::findNext($phpcsFile, T_STRING, $next); 42 | 43 | // We are not dealing with an exception class 44 | if (!str_ends_with((string) $tokens[$next]['content'], 'Exception')) { 45 | return; 46 | } 47 | 48 | $next = $this->getNextNonWhitespaceToken($tokens, $next); 49 | 50 | // There is no opening parenthesis after the class name 51 | if (T_OPEN_PARENTHESIS !== $tokens[$next]['code']) { 52 | return; 53 | } 54 | 55 | $next = $this->getNextNonWhitespaceToken($tokens, $next); 56 | 57 | // A non-interpolated string will have the T_CONSTANT_ENCAPSED_STRING code, so it 58 | // is enough to check for T_DOUBLE_QUOTED_STRING here 59 | if (T_DOUBLE_QUOTED_STRING !== $tokens[$next]['code']) { 60 | return; 61 | } 62 | 63 | $phpcsFile->addError('Using string interpolation in exception messages is not allowed. Use sprintf() instead.', $stackPtr, self::class); 64 | } 65 | 66 | private function getNextNonWhitespaceToken(array $tokens, int $index): int 67 | { 68 | do { 69 | ++$index; 70 | } while (T_WHITESPACE === $tokens[$index]['code']); 71 | 72 | return $index; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Fixer/AssertEqualsFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock(\SplFileInfo::class), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | assertEquals(42, $foo->getId()); 47 | $this->assertEquals('foo', $foo->getName()); 48 | $this->assertEquals(new Foo(), $foo); 49 | $this->assertEquals([new Foo(), new Bar()], $foo->getObjects()); 50 | } 51 | } 52 | EOT, 53 | <<<'EOT' 54 | assertSame(42, $foo->getId()); 63 | $this->assertSame('foo', $foo->getName()); 64 | $this->assertEquals(new Foo(), $foo); 65 | $this->assertEquals([new Foo(), new Bar()], $foo->getObjects()); 66 | } 67 | } 68 | EOT, 69 | ]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Fixer/CaseCommentIndentationFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | createMock(Foo::class); 47 | $mock 48 | ->method("isFoo") 49 | ->willReturn(true) 50 | ; 51 | $mock 52 | ->method("isBar") 53 | ->willReturn(false) 54 | ; 55 | 56 | $mock->isFoo(); 57 | 58 | if (true) { 59 | $mock 60 | ->method("isBaz") 61 | ->willReturn(false) 62 | ; 63 | } 64 | 65 | $mock 66 | ->method("isBat") 67 | ->willReturnCallback( 68 | function () { 69 | $bar = $this->createMock(Bar::class); 70 | $bar 71 | ->method("isFoo") 72 | ->willReturn(false) 73 | ; 74 | $bar 75 | ->method("isBar") 76 | ->willReturn(true) 77 | ; 78 | } 79 | ) 80 | ; 81 | } 82 | 83 | public function testBar(): void 84 | { 85 | /* 86 | * Comment 87 | */ 88 | 89 | $this->mock = $this->createMock(Bar::class); 90 | 91 | $this->mock 92 | ->method("isFoo") 93 | ->willReturn(false) 94 | ; 95 | 96 | $this->mock 97 | ->method("isBar") 98 | ->willReturn(true) 99 | ; 100 | } 101 | 102 | public function testBaz(): void 103 | { 104 | $mock = $this->mockClassWithProperties(Baz::class); 105 | $mock->id = 42; 106 | $mock 107 | ->method("isFoo") 108 | ->willReturn(false) 109 | ; 110 | $mock 111 | ->method("isBaz") 112 | ->willReturn(true) 113 | ; 114 | } 115 | 116 | public function testBat(): void 117 | { 118 | $mock = $this->mockClassWithProperties(Bat::class, [ 119 | 'id' => 42, 120 | ]); 121 | $mock 122 | ->method("isBat") 123 | ->willReturn(true) 124 | ; 125 | } 126 | } 127 | EOT, 128 | <<<'EOT' 129 | createMock(Foo::class); 138 | $mock 139 | ->method("isFoo") 140 | ->willReturn(true) 141 | ; 142 | 143 | $mock 144 | ->method("isBar") 145 | ->willReturn(false) 146 | ; 147 | 148 | $mock->isFoo(); 149 | 150 | if (true) { 151 | $mock 152 | ->method("isBaz") 153 | ->willReturn(false) 154 | ; 155 | } 156 | 157 | $mock 158 | ->method("isBat") 159 | ->willReturnCallback( 160 | function () { 161 | $bar = $this->createMock(Bar::class); 162 | $bar 163 | ->method("isFoo") 164 | ->willReturn(false) 165 | ; 166 | 167 | $bar 168 | ->method("isBar") 169 | ->willReturn(true) 170 | ; 171 | } 172 | ) 173 | ; 174 | } 175 | 176 | public function testBar(): void 177 | { 178 | /* 179 | * Comment 180 | */ 181 | 182 | $this->mock = $this->createMock(Bar::class); 183 | $this->mock 184 | ->method("isFoo") 185 | ->willReturn(false) 186 | ; 187 | 188 | $this->mock 189 | ->method("isBar") 190 | ->willReturn(true) 191 | ; 192 | } 193 | 194 | public function testBaz(): void 195 | { 196 | $mock = $this->mockClassWithProperties(Baz::class); 197 | $mock->id = 42; 198 | 199 | $mock 200 | ->method("isFoo") 201 | ->willReturn(false) 202 | ; 203 | 204 | $mock 205 | ->method("isBaz") 206 | ->willReturn(true) 207 | ; 208 | } 209 | 210 | public function testBat(): void 211 | { 212 | $mock = $this->mockClassWithProperties(Bat::class, [ 213 | 'id' => 42, 214 | ]); 215 | 216 | $mock 217 | ->method("isBat") 218 | ->willReturn(true) 219 | ; 220 | } 221 | } 222 | EOT, 223 | ]; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /tests/Fixer/CommentLengthFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | createMock(Foo::class); 45 | $foo 46 | ->method('bar') 47 | ->with($this->callback(function ($a) { return $a; })); 48 | 49 | $foo 50 | ->method('bar') 51 | ->with( 52 | $this->callback(function ($b) { 53 | return $b; 54 | }) 55 | ); 56 | 57 | $foo 58 | ->method('bar') 59 | ->with( 60 | $this->callback( 61 | function ($c) { 62 | return $c; 63 | } 64 | ) 65 | ); 66 | } 67 | } 68 | EOT, 69 | <<<'EOT' 70 | createMock(Foo::class); 77 | $foo 78 | ->method('bar') 79 | ->with($this->callback(function ($a) { return $a; })); 80 | 81 | $foo 82 | ->method('bar') 83 | ->with($this->callback(function ($b) { 84 | return $b; 85 | })); 86 | 87 | $foo 88 | ->method('bar') 89 | ->with($this->callback( 90 | function ($c) { 91 | return $c; 92 | } 93 | )); 94 | } 95 | } 96 | EOT, 97 | ]; 98 | 99 | yield [ 100 | <<<'EOT' 101 | method('bar') 109 | ->with($bar, $this->callback(function ($a) { return $a; })); 110 | 111 | $foo 112 | ->method('bar') 113 | ->with($bar, $this->callback(function ($b) { 114 | return $b; 115 | })); 116 | 117 | $foo 118 | ->method('bar') 119 | ->with($bar, $this->callback( 120 | function ($c) { 121 | return $c; 122 | } 123 | )); 124 | } 125 | } 126 | EOT, 127 | <<<'EOT' 128 | method('bar') 136 | ->with( 137 | $bar, 138 | $this->callback(function ($a) { return $a; }) 139 | ); 140 | 141 | $foo 142 | ->method('bar') 143 | ->with( 144 | $bar, 145 | $this->callback(function ($b) { 146 | return $b; 147 | }) 148 | ); 149 | 150 | $foo 151 | ->method('bar') 152 | ->with( 153 | $bar, 154 | $this->callback( 155 | function ($c) { 156 | return $c; 157 | } 158 | ) 159 | ); 160 | } 161 | } 162 | EOT, 163 | ]; 164 | 165 | yield [ 166 | <<<'EOT' 167 | method('bar') 175 | ->with($foo, $this->callback(function ($a) { return $a; }), $bar); 176 | 177 | $foo 178 | ->method('bar') 179 | ->with($foo, $this->callback(function ($b) { 180 | return $b; 181 | }), $bar); 182 | 183 | $foo 184 | ->method('bar') 185 | ->with($foo, $this->callback( 186 | function ($c) { 187 | return $c; 188 | } 189 | ), $bar); 190 | } 191 | } 192 | EOT, 193 | <<<'EOT' 194 | method('bar') 202 | ->with( 203 | $foo, 204 | $this->callback(function ($a) { return $a; }), 205 | $bar 206 | ); 207 | 208 | $foo 209 | ->method('bar') 210 | ->with( 211 | $foo, 212 | $this->callback(function ($b) { 213 | return $b; 214 | }), 215 | $bar 216 | ); 217 | 218 | $foo 219 | ->method('bar') 220 | ->with( 221 | $foo, 222 | $this->callback( 223 | function ($c) { 224 | return $c; 225 | } 226 | ), 227 | $bar 228 | ); 229 | } 230 | } 231 | EOT, 232 | ]; 233 | 234 | yield [ 235 | <<<'EOT' 236 | method('bar') 244 | ->with($this->callback(function ($a) { return $a; }), $this->callback(function ($b) { return $b; })); 245 | 246 | $foo 247 | ->method('bar') 248 | ->with($this->callback(function ($c) { 249 | return $c; 250 | }), $this->callback(function ($d) { 251 | return $d; 252 | })); 253 | 254 | $foo 255 | ->method('bar') 256 | ->with($this->callback( 257 | function ($e) { 258 | return $e; 259 | } 260 | ), $this->callback( 261 | function ($f) { 262 | return $f; 263 | } 264 | )); 265 | } 266 | } 267 | EOT, 268 | <<<'EOT' 269 | method('bar') 277 | ->with( 278 | $this->callback(function ($a) { return $a; }), 279 | $this->callback(function ($b) { return $b; }) 280 | ); 281 | 282 | $foo 283 | ->method('bar') 284 | ->with( 285 | $this->callback(function ($c) { 286 | return $c; 287 | }), 288 | $this->callback(function ($d) { 289 | return $d; 290 | }) 291 | ); 292 | 293 | $foo 294 | ->method('bar') 295 | ->with( 296 | $this->callback( 297 | function ($e) { 298 | return $e; 299 | } 300 | ), 301 | $this->callback( 302 | function ($f) { 303 | return $f; 304 | } 305 | ) 306 | ); 307 | } 308 | } 309 | EOT, 310 | ]; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /tests/Fixer/FindByPkFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock(\SplFileInfo::class), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | getAdapter(Model::class)->findByPk($id); 46 | $baz = call_user_func(Model::class, 'findByPk'); 47 | } 48 | 49 | public function findByPk(int $id): void 50 | { 51 | parent::findByPk($id); 52 | } 53 | } 54 | EOT, 55 | <<<'EOT' 56 | getAdapter(Model::class)->findById($id); 64 | $baz = call_user_func(Model::class, 'findById'); 65 | } 66 | 67 | public function findByPk(int $id): void 68 | { 69 | parent::findByPk($id); 70 | } 71 | } 72 | EOT, 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Fixer/FunctionCallWithMultilineArrayFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock(\SplFileInfo::class), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | 'bar', 42 | ]); 43 | EOT, 44 | <<<'EOT' 45 | 'bar', 49 | ]); 50 | EOT, 51 | ]; 52 | 53 | yield [ 54 | <<<'EOT' 55 | 'bar', 59 | ]); 60 | EOT, 61 | <<<'EOT' 62 | 'bar', 66 | ]); 67 | EOT, 68 | ]; 69 | 70 | yield [ 71 | <<<'EOT' 72 | 'bar', 78 | ], 79 | ); 80 | EOT, 81 | <<<'EOT' 82 | 'bar', 86 | ]); 87 | EOT, 88 | ]; 89 | 90 | yield [ 91 | <<<'EOT' 92 | 'bar', 96 | ], true); 97 | EOT, 98 | <<<'EOT' 99 | 'bar', 105 | ], 106 | true, 107 | ); 108 | EOT, 109 | ]; 110 | 111 | yield [ 112 | <<<'EOT' 113 | 'bar', 117 | ], true); 118 | EOT, 119 | <<<'EOT' 120 | 'bar', 125 | ], 126 | true, 127 | ); 128 | EOT, 129 | ]; 130 | 131 | yield [ 132 | <<<'EOT' 133 | 'bar', 138 | ]); 139 | } 140 | EOT, 141 | <<<'EOT' 142 | 'bar', 147 | ]); 148 | } 149 | EOT, 150 | ]; 151 | 152 | yield [ 153 | <<<'EOT' 154 | 'bar', 159 | ]); 160 | } 161 | EOT, 162 | <<<'EOT' 163 | 'bar', 168 | ]); 169 | } 170 | EOT, 171 | ]; 172 | 173 | yield [ 174 | <<<'EOT' 175 | 'bar', 182 | ], 183 | ); 184 | } 185 | EOT, 186 | <<<'EOT' 187 | 'bar', 192 | ]); 193 | } 194 | EOT, 195 | ]; 196 | 197 | yield [ 198 | <<<'EOT' 199 | 'bar', 204 | ], true); 205 | } 206 | EOT, 207 | <<<'EOT' 208 | 'bar', 215 | ], 216 | true, 217 | ); 218 | } 219 | EOT, 220 | ]; 221 | 222 | yield [ 223 | <<<'EOT' 224 | 'bar', 229 | ], true); 230 | } 231 | EOT, 232 | <<<'EOT' 233 | 'bar', 239 | ], 240 | true, 241 | ); 242 | } 243 | EOT, 244 | ]; 245 | 246 | yield [ 247 | <<<'EOT' 248 | 'bar', 252 | ]]); 253 | EOT, 254 | <<<'EOT' 255 | 'bar', 259 | ]]); 260 | EOT, 261 | ]; 262 | 263 | yield [ 264 | <<<'EOT' 265 | 'bar', 269 | 'bar' => 'baz', 270 | ]); 271 | EOT, 272 | <<<'EOT' 273 | 'bar', 277 | 'bar' => 'baz', 278 | ]); 279 | EOT, 280 | ]; 281 | 282 | yield [ 283 | <<<'EOT' 284 | 'bar', 290 | 'bar' => 'baz', 291 | ], 292 | ); 293 | EOT, 294 | <<<'EOT' 295 | 'bar', 299 | 'bar' => 'baz', 300 | ]); 301 | EOT, 302 | ]; 303 | 304 | yield [ 305 | <<<'EOT' 306 | 'bar', 310 | 'bar' => 'baz', 311 | ], true); 312 | EOT, 313 | <<<'EOT' 314 | 'bar', 320 | 'bar' => 'baz', 321 | ], 322 | true, 323 | ); 324 | EOT, 325 | ]; 326 | 327 | yield [ 328 | <<<'EOT' 329 | 'bar', 333 | 'bar' => 'baz', 334 | ], true); 335 | EOT, 336 | <<<'EOT' 337 | 'bar', 342 | 'bar' => 'baz', 343 | ], 344 | true, 345 | ); 346 | EOT, 347 | ]; 348 | 349 | yield [ 350 | <<<'EOT' 351 | 'bar']); 355 | } 356 | EOT, 357 | <<<'EOT' 358 | 'bar']); 362 | } 363 | EOT, 364 | ]; 365 | 366 | yield [ 367 | <<<'EOT' 368 | 'bar', 374 | ], 375 | ); 376 | EOT, 377 | <<<'EOT' 378 | 'bar', 382 | ]); 383 | EOT, 384 | ]; 385 | 386 | yield [ 387 | <<<'EOT' 388 | 'bar'], 393 | ); 394 | EOT, 395 | <<<'EOT' 396 | 'bar'], 401 | ); 402 | EOT, 403 | ]; 404 | 405 | yield [ 406 | <<<'EOT' 407 | 'bar', 412 | ], 413 | [ 414 | 'bar' => 'baz', 415 | ], 416 | ); 417 | EOT, 418 | <<<'EOT' 419 | 'bar', 424 | ], 425 | [ 426 | 'bar' => 'baz', 427 | ], 428 | ); 429 | EOT, 430 | ]; 431 | 432 | yield [ 433 | <<<'EOT' 434 | 'bar', 440 | ], 441 | ); 442 | EOT, 443 | <<<'EOT' 444 | 'bar', 450 | ], 451 | ); 452 | EOT, 453 | ]; 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /tests/Fixer/InlinePhpdocCommentFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | createMock(Foo::class); 45 | $foo->method('bar'); 46 | 47 | $foo->method('bar')->willReturn(false); 48 | 49 | $foo 50 | ->expects($this->once()) 51 | ->method('bar') 52 | ->willReturn(false); 53 | 54 | $foo->method('bar')->with('foo')->willReturnSelf(); 55 | 56 | $this->foo->method('bar'); 57 | 58 | $this->foo->method('bar')->with('foo')->willReturnSelf(); 59 | } 60 | } 61 | EOT, 62 | <<<'EOT' 63 | createMock(Foo::class); 70 | $foo->method('bar'); 71 | 72 | $foo 73 | ->method('bar') 74 | ->willReturn(false); 75 | 76 | $foo 77 | ->expects($this->once()) 78 | ->method('bar') 79 | ->willReturn(false); 80 | 81 | $foo 82 | ->method('bar') 83 | ->with('foo') 84 | ->willReturnSelf(); 85 | 86 | $this->foo->method('bar'); 87 | 88 | $this->foo 89 | ->method('bar') 90 | ->with('foo') 91 | ->willReturnSelf(); 92 | } 93 | } 94 | EOT, 95 | ]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Fixer/MultiLineLambdaFunctionArgumentsFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | createMock(Foo::class); 45 | $foo->expects($this->any())->method('bar'); 46 | 47 | $foo 48 | ->expects($this->once()) 49 | ->method('bar'); 50 | 51 | $foo 52 | ->expects($this->any()) 53 | ->method('bar') 54 | ->willReturn(false); 55 | } 56 | } 57 | EOT, 58 | <<<'EOT' 59 | createMock(Foo::class); 66 | $foo->method('bar'); 67 | 68 | $foo 69 | ->expects($this->once()) 70 | ->method('bar'); 71 | 72 | $foo 73 | ->method('bar') 74 | ->willReturn(false); 75 | } 76 | } 77 | EOT, 78 | ]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Fixer/NoLineBreaksBetweenMethodArgumentsFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | a; ?> 39 | b ?> 40 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 55 | 56 | z; ?> 57 | 58 | a ?> 64 | b ?> 65 | 66 | 67 | 68 | 69 | 70 | 75 | 76 | 80 | 81 | z ?> 82 | 83 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | addArgument( 50 | 'foo', 51 | InputArgument::REQUIRED, 52 | 'The argument' 53 | ) 54 | ->addArgument('bar', InputArgument::REQUIRED) 55 | ->addOption( 56 | 'bar', 57 | null, 58 | InputOption::VALUE_NONE, 59 | 'The option' 60 | ); 61 | } 62 | } 63 | EOT, 64 | <<<'EOT' 65 | addArgument('foo', InputArgument::REQUIRED, 'The argument') 77 | ->addArgument('bar', InputArgument::REQUIRED) 78 | ->addOption('bar', null, InputOption::VALUE_NONE, 'The option'); 79 | } 80 | } 81 | EOT, 82 | ]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Fixer/TypeHintOrderFixerTest.php: -------------------------------------------------------------------------------- 1 | fix($this->createMock('SplFileInfo'), $tokens); 30 | 31 | $this->assertSame($expected, $tokens->generateCode()); 32 | } 33 | 34 | public static function getCodeSamples(): iterable 35 | { 36 | yield [ 37 | <<<'EOT' 38 | null; 62 | } 63 | } 64 | EOT, 65 | <<<'EOT' 66 | null; 90 | } 91 | } 92 | EOT, 93 | ]; 94 | } 95 | } 96 | --------------------------------------------------------------------------------