├── .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('= $this->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 | = $this->a; ?>
39 | = $this->b ?>
40 |
41 |
42 |
43 |
44 |
45 |
50 |
51 |
55 |
56 | = $this->z; ?>
57 |
58 | a ?>
64 | = $this->b ?>
65 |
66 |
67 |
68 |
69 |
70 |
75 |
76 |
80 |
81 | = $this->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 |
--------------------------------------------------------------------------------