├── .github └── workflows │ ├── code_analysis.yaml │ ├── code_analysis_reuslabe.yaml │ └── tests.yaml ├── LICENSE ├── README.md ├── composer.json ├── config └── sets │ ├── annotations-to-attributes.php │ ├── phpunit-code-quality.php │ ├── phpunit100.php │ ├── phpunit110.php │ ├── phpunit120.php │ ├── phpunit40.php │ ├── phpunit50.php │ ├── phpunit60.php │ ├── phpunit70.php │ ├── phpunit80.php │ └── phpunit90.php ├── rules ├── AnnotationsToAttributes │ └── Rector │ │ ├── ClassMethod │ │ ├── DataProviderAnnotationToAttributeRector.php │ │ ├── DependsAnnotationWithValueToAttributeRector.php │ │ └── TestWithAnnotationToAttributeRector.php │ │ └── Class_ │ │ ├── AnnotationWithValueToAttributeRector.php │ │ ├── CoversAnnotationWithValueToAttributeRector.php │ │ ├── RequiresAnnotationWithValueToAttributeRector.php │ │ └── TicketAnnotationToAttributeRector.php ├── CodeQuality │ ├── Enum │ │ └── NonAssertNonStaticMethods.php │ ├── NodeAnalyser │ │ ├── AssertMethodAnalyzer.php │ │ ├── DoctrineEntityDocumentAnalyser.php │ │ ├── NullableObjectAssignCollector.php │ │ └── SetUpAssignedMockTypesResolver.php │ ├── NodeFactory │ │ └── NestedClosureAssertFactory.php │ ├── Rector │ │ ├── ClassMethod │ │ │ ├── AddInstanceofAssertForNullableInstanceRector.php │ │ │ ├── CreateMockToAnonymousClassRector.php │ │ │ ├── DataProviderArrayItemsNewLinedRector.php │ │ │ ├── EntityDocumentCreateMockToDirectNewRector.php │ │ │ ├── RemoveEmptyTestMethodRector.php │ │ │ ├── ReplaceTestAnnotationWithPrefixedFunctionRector.php │ │ │ └── ReplaceTestFunctionPrefixWithAttributeRector.php │ │ ├── Class_ │ │ │ ├── AddCoversClassAttributeRector.php │ │ │ ├── AddParentSetupCallOnSetupRector.php │ │ │ ├── AddSeeTestAnnotationRector.php │ │ │ ├── ConstructClassMethodToSetUpTestCaseRector.php │ │ │ ├── NarrowUnusedSetUpDefinedPropertyRector.php │ │ │ ├── PreferPHPUnitSelfCallRector.php │ │ │ ├── PreferPHPUnitThisCallRector.php │ │ │ ├── RemoveDataProviderParamKeysRector.php │ │ │ ├── SetUpBeforeClassToSetUpRector.php │ │ │ ├── SingleMockPropertyTypeRector.php │ │ │ ├── TestWithToDataProviderRector.php │ │ │ ├── TypeWillReturnCallableArrowFunctionRector.php │ │ │ └── YieldDataProviderRector.php │ │ ├── Foreach_ │ │ │ └── SimplifyForeachInstanceOfRector.php │ │ ├── FuncCall │ │ │ └── AssertFuncCallToPHPUnitAssertRector.php │ │ └── MethodCall │ │ │ ├── AssertCompareOnCountableWithMethodToAssertCountRector.php │ │ │ ├── AssertComparisonToSpecificMethodRector.php │ │ │ ├── AssertEmptyNullableObjectToAssertInstanceofRector.php │ │ │ ├── AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector.php │ │ │ ├── AssertEqualsToSameRector.php │ │ │ ├── AssertFalseStrposToContainsRector.php │ │ │ ├── AssertInstanceOfComparisonRector.php │ │ │ ├── AssertIssetToSpecificMethodRector.php │ │ │ ├── AssertNotOperatorRector.php │ │ │ ├── AssertPropertyExistsRector.php │ │ │ ├── AssertRegExpRector.php │ │ │ ├── AssertSameBoolNullToSpecificMethodRector.php │ │ │ ├── AssertSameTrueFalseToAssertTrueFalseRector.php │ │ │ ├── AssertTrueFalseToSpecificMethodRector.php │ │ │ ├── FlipAssertRector.php │ │ │ ├── NarrowIdenticalWithConsecutiveRector.php │ │ │ ├── NarrowSingleWillReturnCallbackRector.php │ │ │ ├── RemoveExpectAnyFromMockRector.php │ │ │ ├── SingleWithConsecutiveToWithRector.php │ │ │ ├── UseSpecificWillMethodRector.php │ │ │ └── UseSpecificWithMethodRector.php │ ├── Reflection │ │ └── MethodParametersAndReturnTypesResolver.php │ └── ValueObject │ │ ├── MatchAndReturnMatch.php │ │ ├── ParamTypesAndReturnType.php │ │ ├── VariableNameToType.php │ │ └── VariableNameToTypeCollection.php ├── PHPUnit100 │ ├── NodeDecorator │ │ └── WillReturnIfNodeDecorator.php │ ├── NodeFactory │ │ └── WillReturnCallbackFactory.php │ └── Rector │ │ ├── Class_ │ │ ├── AddProphecyTraitRector.php │ │ ├── ParentTestClassConstructorRector.php │ │ ├── PublicDataProviderClassMethodRector.php │ │ ├── RemoveNamedArgsInDataProviderRector.php │ │ └── StaticDataProviderClassMethodRector.php │ │ ├── MethodCall │ │ ├── PropertyExistsWithoutAssertRector.php │ │ └── RemoveSetMethodsMethodCallRector.php │ │ └── StmtsAwareInterface │ │ ├── ExpectsMethodCallDecorator.php │ │ └── WithConsecutiveRector.php ├── PHPUnit110 │ └── Rector │ │ └── Class_ │ │ └── NamedArgumentForDataProviderRector.php ├── PHPUnit120 │ └── Rector │ │ └── Class_ │ │ └── RemoveOverrideFinalConstructTestCaseRector.php ├── PHPUnit50 │ └── Rector │ │ └── StaticCall │ │ └── GetMockRector.php ├── PHPUnit60 │ └── Rector │ │ ├── ClassMethod │ │ ├── AddDoesNotPerformAssertionToNonAssertingTestRector.php │ │ └── ExceptionAnnotationRector.php │ │ └── MethodCall │ │ ├── DelegateExceptionArgumentsRector.php │ │ └── GetMockBuilderGetMockToCreateMockRector.php ├── PHPUnit70 │ └── Rector │ │ └── Class_ │ │ └── RemoveDataProviderTestPrefixRector.php ├── PHPUnit80 │ └── Rector │ │ └── MethodCall │ │ ├── AssertEqualsParameterToSpecificMethodsTypeRector.php │ │ ├── SpecificAssertContainsRector.php │ │ └── SpecificAssertInternalTypeRector.php └── PHPUnit90 │ └── Rector │ ├── Class_ │ └── TestListenerToHooksRector.php │ └── MethodCall │ ├── ExplicitPhpErrorApiRector.php │ ├── ReplaceAtMethodWithDesiredMatcherRector.php │ └── SpecificAssertContainsWithoutIdentityRector.php └── src ├── Composer └── ProjectPackageVersionResolver.php ├── Enum ├── AssertMethod.php ├── ConsecutiveMethodName.php ├── ConsecutiveVariable.php └── PHPUnitClassName.php ├── MethodCallRemover.php ├── Naming └── TestClassNameResolver.php ├── NodeAnalyzer ├── ArgumentMover.php ├── AssertCallAnalyzer.php ├── IdentifierManipulator.php ├── MockedVariableAnalyzer.php ├── SetUpMethodDecorator.php └── TestsNodeAnalyzer.php ├── NodeFactory ├── AssertCallFactory.php ├── ConsecutiveIfsFactory.php ├── ExpectExceptionMethodCallFactory.php ├── MatcherInvocationCountMethodCallNodeFactory.php └── UsedVariablesResolver.php ├── NodeFinder ├── DataProviderClassMethodFinder.php └── MethodCallNodeFinder.php ├── PhpDoc ├── DataProviderMethodRenamer.php └── PhpDocValueToNodeMapper.php ├── Set ├── PHPUnitSetList.php └── SetProvider │ └── PHPUnitSetProvider.php └── ValueObject ├── AnnotationWithValueToAttribute.php ├── BinaryOpWithAssertMethod.php ├── ConstantWithAssertMethods.php └── FunctionNameWithAssertMethods.php /.github/workflows/code_analysis.yaml: -------------------------------------------------------------------------------- 1 | name: Code Analysis 2 | 3 | on: 4 | pull_request: null 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | # see https://github.com/composer/composer/issues/9368#issuecomment-718112361 11 | COMPOSER_ROOT_VERSION: "dev-main" 12 | 13 | jobs: 14 | code_analysis: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | actions: 19 | - 20 | name: 'Active Classes' 21 | run: composer class-leak 22 | 23 | - 24 | name: 'Finalise classes' 25 | run: vendor/bin/swiss-knife finalize-classes src rules tests 26 | 27 | name: ${{ matrix.actions.name }} 28 | runs-on: ubuntu-latest 29 | timeout-minutes: 10 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: 8.2 38 | coverage: none 39 | 40 | - uses: "ramsey/composer-install@v2" 41 | - run: ${{ matrix.actions.run }} 42 | -------------------------------------------------------------------------------- /.github/workflows/code_analysis_reuslabe.yaml: -------------------------------------------------------------------------------- 1 | name: Code Analysis Reusable 2 | 3 | on: 4 | pull_request: null 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | code_analysis_reusable: 11 | # see https://github.com/rectorphp/reusable-workflows 12 | uses: rectorphp/reusable-workflows/.github/workflows/code_analysis.yaml@main 13 | 14 | rector: 15 | # run only on main repository, not on the forks without access 16 | if: github.repository == 'rectorphp/rector-phpunit' 17 | 18 | # see https://github.com/rectorphp/reusable-workflows 19 | uses: rectorphp/reusable-workflows/.github/workflows/rector.yaml@main 20 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: null 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | # see https://github.com/composer/composer/issues/9368#issuecomment-718112361 11 | COMPOSER_ROOT_VERSION: "dev-main" 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-latest 16 | 17 | name: Tests 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: 8.2 25 | coverage: none 26 | 27 | - uses: "ramsey/composer-install@v2" 28 | 29 | - run: vendor/bin/phpunit 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | --------------- 3 | 4 | Copyright (c) 2017-present Tomáš Votruba (https://tomasvotruba.cz) 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rector Rules for PHPUnit 2 | 3 | See available [PHPUnit rules](https://getrector.com/find-rule?activeRectorSetGroup=phpunit) 4 | 5 | ## Install 6 | 7 | This package is already part of [rector/rector](http://github.com/rectorphp/rector) package, so it works out of the box. 8 | 9 | All you need to do is install the main package, and you're good to go: 10 | 11 | ```bash 12 | composer require rector/rector --dev 13 | ``` 14 | 15 | ## Use Sets 16 | 17 | To add a set to your config, use `Rector\PHPUnit\Set\PHPUnitSetList` class and pick one of constants: 18 | 19 | ```php 20 | use Rector\Config\RectorConfig; 21 | use Rector\PHPUnit\Set\PHPUnitSetList; 22 | 23 | return RectorConfig::configure() 24 | ->withSets([ 25 | PHPUnitSetList::PHPUNIT_90, 26 | ]); 27 | ``` 28 | 29 |
30 | 31 | ## Learn Rector Faster 32 | 33 | Rector is a tool that [we develop](https://getrector.org/) and share for free, so anyone can save hundreds of hours on refactoring. But not everyone has time to understand Rector and AST complexity. You have 2 ways to speed this process up: 34 | 35 | * read a book - The Power of Automated Refactoring 36 | * hire our experienced team to improve your code base 37 | 38 | Both ways support us to and improve Rector in sustainable way by learning from practical projects. 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rector/rector-phpunit", 3 | "type": "rector-extension", 4 | "license": "MIT", 5 | "description": "Rector upgrades rules for PHPUnit", 6 | "require": { 7 | "php": ">=8.2" 8 | }, 9 | "require-dev": { 10 | "phpecs/phpecs": "^2.1.1", 11 | "phpstan/extension-installer": "^1.4", 12 | "phpstan/phpstan": "^2.1.8", 13 | "phpstan/phpstan-deprecation-rules": "^2.0", 14 | "phpstan/phpstan-webmozart-assert": "^2.0", 15 | "phpunit/phpunit": "^11.5", 16 | "rector/rector-src": "dev-main", 17 | "rector/swiss-knife": "^1.0", 18 | "rector/type-perfect": "^2.0", 19 | "symplify/phpstan-extensions": "^12.0", 20 | "symplify/vendor-patches": "^11.4", 21 | "tomasvotruba/class-leak": "^1.2", 22 | "tracy/tracy": "^2.10" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Rector\\PHPUnit\\": ["src", "rules"] 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Rector\\PHPUnit\\Tests\\": ["tests", "rules-tests"] 32 | }, 33 | "classmap": [ 34 | "stubs" 35 | ] 36 | }, 37 | "scripts": { 38 | "complete-check": [ 39 | "@check-cs", 40 | "@class-leak", 41 | "@phpstan", 42 | "@rector", 43 | "@docs", 44 | "phpunit" 45 | ], 46 | "phpstan": "vendor/bin/phpstan analyse --ansi", 47 | "check-cs": "vendor/bin/ecs check --ansi", 48 | "class-leak": "vendor/bin/class-leak check config src rules --skip-suffix \"Rector\"", 49 | "fix-cs": "vendor/bin/ecs check --fix --ansi", 50 | "rector": "vendor/bin/rector process --ansi" 51 | }, 52 | "extra": { 53 | "enable-patching": true 54 | }, 55 | "conflict": { 56 | "rector/rector": "<2.0" 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true, 60 | "config": { 61 | "sort-packages": true, 62 | "allow-plugins": { 63 | "cweagans/composer-patches": true, 64 | "rector/extension-installer": true, 65 | "phpstan/extension-installer": true 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/sets/phpunit110.php: -------------------------------------------------------------------------------- 1 | rule(NamedArgumentForDataProviderRector::class); 10 | }; 11 | -------------------------------------------------------------------------------- /config/sets/phpunit120.php: -------------------------------------------------------------------------------- 1 | rule(RemoveOverrideFinalConstructTestCaseRector::class); 10 | }; 11 | -------------------------------------------------------------------------------- /config/sets/phpunit40.php: -------------------------------------------------------------------------------- 1 | ruleWithConfiguration(RenameMethodRector::class, [ 11 | new MethodCallRename( 12 | 'PHPUnit_Framework_MockObject_MockObject', 13 | # see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/137 14 | 'staticExpects', 15 | 'expects' 16 | ), 17 | ]); 18 | }; 19 | -------------------------------------------------------------------------------- /config/sets/phpunit50.php: -------------------------------------------------------------------------------- 1 | rule(GetMockRector::class); 10 | }; 11 | -------------------------------------------------------------------------------- /config/sets/phpunit70.php: -------------------------------------------------------------------------------- 1 | ruleWithConfiguration(RenameAnnotationRector::class, [ 12 | new RenameAnnotationByType('PHPUnit\Framework\TestCase', 'scenario', 'test'), 13 | ]); 14 | 15 | $rectorConfig->rule(RemoveDataProviderTestPrefixRector::class); 16 | }; 17 | -------------------------------------------------------------------------------- /config/sets/phpunit80.php: -------------------------------------------------------------------------------- 1 | rules([ 20 | SpecificAssertInternalTypeRector::class, 21 | AssertEqualsParameterToSpecificMethodsTypeRector::class, 22 | SpecificAssertContainsRector::class, 23 | ]); 24 | 25 | $rectorConfig->ruleWithConfiguration(RenameClassRector::class, [ 26 | # https://github.com/sebastianbergmann/phpunit/issues/3123 27 | 'PHPUnit_Framework_MockObject_MockObject' => 'PHPUnit\Framework\MockObject\MockObject', 28 | ]); 29 | 30 | $rectorConfig->ruleWithConfiguration(AddParamTypeDeclarationRector::class, [ 31 | // https://github.com/rectorphp/rector/issues/1024 - no type, $dataName 32 | new AddParamTypeDeclaration('PHPUnit\Framework\TestCase', MethodName::CONSTRUCT, 2, new MixedType()), 33 | ]); 34 | 35 | $rectorConfig->ruleWithConfiguration(AddReturnTypeDeclarationRector::class, [ 36 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'setUpBeforeClass', new VoidType()), 37 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'setUp', new VoidType()), 38 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'assertPreConditions', new VoidType()), 39 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'assertPostConditions', new VoidType()), 40 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'tearDown', new VoidType()), 41 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'tearDownAfterClass', new VoidType()), 42 | new AddReturnTypeDeclaration('PHPUnit\Framework\TestCase', 'onNotSuccessfulTest', new VoidType()), 43 | ]); 44 | }; 45 | -------------------------------------------------------------------------------- /config/sets/phpunit90.php: -------------------------------------------------------------------------------- 1 | rules([ 15 | TestListenerToHooksRector::class, 16 | ExplicitPhpErrorApiRector::class, 17 | SpecificAssertContainsWithoutIdentityRector::class, 18 | WithConsecutiveRector::class, 19 | ]); 20 | 21 | $rectorConfig->ruleWithConfiguration(RenameMethodRector::class, [ 22 | // see https://github.com/sebastianbergmann/phpunit/issues/3957 23 | new MethodCallRename( 24 | 'PHPUnit\Framework\TestCase', 25 | 'expectExceptionMessageRegExp', 26 | 'expectExceptionMessageMatches' 27 | ), 28 | ]); 29 | }; 30 | -------------------------------------------------------------------------------- /rules/CodeQuality/Enum/NonAssertNonStaticMethods.php: -------------------------------------------------------------------------------- 1 | var 31 | : $call->class; 32 | 33 | if (! $this->nodeTypeResolver->isObjectType($objectCaller, new ObjectType('PHPUnit\Framework\TestCase'))) { 34 | return false; 35 | } 36 | 37 | $methodName = $this->nodeNameResolver->getName($call->name); 38 | if (! str_starts_with((string) $methodName, 'assert') && ! in_array( 39 | $methodName, 40 | NonAssertNonStaticMethods::ALL, 41 | true 42 | )) { 43 | return false; 44 | } 45 | 46 | if ($call instanceof StaticCall && ! $this->nodeNameResolver->isNames($call->class, ['static', 'self'])) { 47 | return false; 48 | } 49 | 50 | $extendedMethodReflection = $this->resolveMethodReflection($call); 51 | if (! $extendedMethodReflection instanceof ExtendedMethodReflection) { 52 | return false; 53 | } 54 | 55 | // only handle methods in TestCase or Assert class classes 56 | $declaringClassName = $extendedMethodReflection->getDeclaringClass() 57 | ->getName(); 58 | 59 | return in_array($declaringClassName, [PHPUnitClassName::TEST_CASE, PHPUnitClassName::ASSERT]); 60 | } 61 | 62 | public function detectTestCaseCallForStatic(MethodCall $methodCall): bool 63 | { 64 | if (! $this->detectTestCaseCall($methodCall)) { 65 | return false; 66 | } 67 | 68 | $extendedMethodReflection = $this->resolveMethodReflection($methodCall); 69 | 70 | return $extendedMethodReflection instanceof ExtendedMethodReflection && $extendedMethodReflection->isStatic(); 71 | } 72 | 73 | private function resolveMethodReflection(MethodCall|StaticCall $call): ?ExtendedMethodReflection 74 | { 75 | $methodName = $this->nodeNameResolver->getName($call->name); 76 | 77 | $classReflection = $this->reflectionResolver->resolveClassReflection($call); 78 | if (! $classReflection instanceof ClassReflection) { 79 | return null; 80 | } 81 | 82 | return $classReflection->getNativeMethod($methodName); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rules/CodeQuality/NodeAnalyser/DoctrineEntityDocumentAnalyser.php: -------------------------------------------------------------------------------- 1 | getResolvedPhpDoc(); 20 | if (! $resolvedPhpDocBlock instanceof ResolvedPhpDocBlock) { 21 | return false; 22 | } 23 | 24 | foreach (self::ENTITY_DOCBLOCK_MARKERS as $entityDocBlockMarkers) { 25 | if (str_contains($resolvedPhpDocBlock->getPhpDocString(), $entityDocBlockMarkers)) { 26 | return true; 27 | } 28 | } 29 | 30 | // @todo apply attributes as well 31 | 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rules/CodeQuality/NodeAnalyser/NullableObjectAssignCollector.php: -------------------------------------------------------------------------------- 1 | getSomething(); 24 | */ 25 | final readonly class NullableObjectAssignCollector 26 | { 27 | public function __construct( 28 | private NodeNameResolver $nodeNameResolver, 29 | private NodeTypeResolver $nodeTypeResolver, 30 | ) { 31 | } 32 | 33 | public function collect(ClassMethod|Foreach_ $stmtsAware): VariableNameToTypeCollection 34 | { 35 | $variableNamesToType = []; 36 | 37 | // first round to collect assigns 38 | foreach ((array) $stmtsAware->stmts as $stmt) { 39 | if (! $stmt instanceof Expression) { 40 | return new VariableNameToTypeCollection([]); 41 | } 42 | 43 | if (! $stmt->expr instanceof Assign) { 44 | continue; 45 | } 46 | 47 | $variableNameToType = $this->collectFromAssign($stmt->expr); 48 | if (! $variableNameToType instanceof VariableNameToType) { 49 | continue; 50 | } 51 | 52 | $variableNamesToType[] = $variableNameToType; 53 | } 54 | 55 | return new VariableNameToTypeCollection($variableNamesToType); 56 | } 57 | 58 | private function collectFromAssign(Assign $assign): ?VariableNameToType 59 | { 60 | if (! $assign->expr instanceof MethodCall) { 61 | return null; 62 | } 63 | 64 | if (! $assign->var instanceof Variable) { 65 | return null; 66 | } 67 | 68 | $variableType = $this->nodeTypeResolver->getType($assign); 69 | 70 | $bareVariableType = TypeCombinator::removeNull($variableType); 71 | if (! $bareVariableType instanceof ObjectType) { 72 | return null; 73 | } 74 | 75 | $variableName = $this->nodeNameResolver->getName($assign->var); 76 | return new VariableNameToType($variableName, $bareVariableType->getClassName()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rules/CodeQuality/NodeAnalyser/SetUpAssignedMockTypesResolver.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | public function resolveFromClass(Class_ $class): array 30 | { 31 | $setUpClassMethod = $class->getMethod(MethodName::SET_UP); 32 | if (! $setUpClassMethod instanceof ClassMethod) { 33 | return []; 34 | } 35 | 36 | $propertyNameToMockedTypes = []; 37 | foreach ((array) $setUpClassMethod->stmts as $stmt) { 38 | if (! $stmt instanceof Expression) { 39 | continue; 40 | } 41 | 42 | if (! $stmt->expr instanceof Assign) { 43 | continue; 44 | } 45 | 46 | $assign = $stmt->expr; 47 | if (! $assign->expr instanceof MethodCall) { 48 | continue; 49 | } 50 | 51 | if (! $this->nodeNameResolver->isNames($assign->expr->name, ['createMock', 'getMockBuilder'])) { 52 | continue; 53 | } 54 | 55 | if (! $assign->var instanceof PropertyFetch && ! $assign->var instanceof Variable) { 56 | continue; 57 | } 58 | 59 | $mockedClassNameExpr = $assign->expr->getArgs()[0] 60 | ->value; 61 | if (! $mockedClassNameExpr instanceof ClassConstFetch) { 62 | continue; 63 | } 64 | 65 | $propertyOrVariableName = $this->resolvePropertyOrVariableName($assign->var); 66 | $mockedClass = $this->nodeNameResolver->getName($mockedClassNameExpr->class); 67 | 68 | Assert::string($mockedClass); 69 | 70 | $propertyNameToMockedTypes[$propertyOrVariableName] = $mockedClass; 71 | } 72 | 73 | return $propertyNameToMockedTypes; 74 | } 75 | 76 | private function resolvePropertyOrVariableName(PropertyFetch|Variable $propertyFetchOrVariable): ?string 77 | { 78 | if ($propertyFetchOrVariable instanceof Variable) { 79 | return $this->nodeNameResolver->getName($propertyFetchOrVariable); 80 | } 81 | 82 | return $this->nodeNameResolver->getName($propertyFetchOrVariable->name); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rules/CodeQuality/NodeFactory/NestedClosureAssertFactory.php: -------------------------------------------------------------------------------- 1 | getArgs()[0]; 32 | 33 | if ($callableFirstArg->value instanceof ArrowFunction) { 34 | $arrowFunction = $callableFirstArg->value; 35 | if ($arrowFunction->expr instanceof Identical) { 36 | // unwrap closure arrow function to direct assert as more readalbe 37 | $identical = $arrowFunction->expr; 38 | 39 | if ($identical->left instanceof Variable) { 40 | return $this->createAssertSameParameters($identical->right, $assertKey); 41 | } 42 | 43 | if ($identical->right instanceof Variable) { 44 | return $this->createAssertSameParameters($identical->left, $assertKey); 45 | } 46 | } 47 | 48 | if ($arrowFunction->expr instanceof BooleanNot && $arrowFunction->expr->expr instanceof Empty_) { 49 | return $this->createAssertNotEmpty($assertKey, 'assertNotEmpty'); 50 | } 51 | 52 | if ($arrowFunction->expr instanceof Empty_) { 53 | return $this->createAssertNotEmpty($assertKey, 'assertEmpty'); 54 | } 55 | } 56 | 57 | $callbackVariable = new Variable('callback'); 58 | $callbackAssign = new Assign($callbackVariable, $callableFirstArg->value); 59 | 60 | $stmts = [new Expression($callbackAssign)]; 61 | 62 | $parametersArrayDimFetch = new ArrayDimFetch(new Variable('parameters'), new Int_($assertKey)); 63 | $callbackFuncCall = new FuncCall($callbackVariable, [new Arg($parametersArrayDimFetch)]); 64 | 65 | // add assert true to the callback 66 | $assertTrueMethodCall = new MethodCall(new Variable('this'), 'assertTrue', [new Arg($callbackFuncCall)]); 67 | $stmts[] = new Expression($assertTrueMethodCall); 68 | 69 | return $stmts; 70 | } 71 | 72 | /** 73 | * @return Expression[] 74 | */ 75 | private function createAssertSameParameters(Expr $comparedExpr, int $assertKey): array 76 | { 77 | // use assert same directly instead 78 | $args = [ 79 | new Arg($comparedExpr), 80 | new Arg(new ArrayDimFetch(new Variable('parameters'), new Int_($assertKey))), 81 | ]; 82 | 83 | $assertSameMethodCall = new MethodCall(new Variable('this'), new Identifier('assertSame'), $args); 84 | 85 | return [new Expression($assertSameMethodCall)]; 86 | } 87 | 88 | /** 89 | * @return Expression[] 90 | */ 91 | private function createAssertNotEmpty(int $assertKey, string $emptyMethodName): array 92 | { 93 | $arrayDimFetch = new ArrayDimFetch(new Variable(ConsecutiveVariable::PARAMETERS), new Int_($assertKey)); 94 | 95 | $assertEmptyMethodCall = new MethodCall(new Variable('this'), new Identifier($emptyMethodName), [ 96 | new Arg($arrayDimFetch), 97 | ]); 98 | 99 | return [new Expression($assertEmptyMethodCall)]; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/ClassMethod/DataProviderArrayItemsNewLinedRector.php: -------------------------------------------------------------------------------- 1 | > 80 | */ 81 | public function getNodeTypes(): array 82 | { 83 | return [ClassMethod::class]; 84 | } 85 | 86 | /** 87 | * @param ClassMethod $node 88 | */ 89 | public function refactor(Node $node): ?Node 90 | { 91 | if (! $node->isPublic()) { 92 | return null; 93 | } 94 | 95 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 96 | return null; 97 | } 98 | 99 | // skip test methods 100 | if (str_starts_with($node->name->toString(), 'test')) { 101 | return null; 102 | } 103 | 104 | // find array in data provider - must contain a return node 105 | /** @var Return_[] $returns */ 106 | $returns = $this->betterNodeFinder->findInstanceOf((array) $node->stmts, Return_::class); 107 | $hasChanged = \false; 108 | foreach ($returns as $return) { 109 | if (! $return->expr instanceof Array_) { 110 | continue; 111 | } 112 | 113 | $array = $return->expr; 114 | if ($array->items === []) { 115 | continue; 116 | } 117 | 118 | if (! $this->shouldRePrint($array)) { 119 | continue; 120 | } 121 | 122 | // ensure newlined printed 123 | $array->setAttribute(AttributeKey::NEWLINED_ARRAY_PRINT, \true); 124 | // invoke reprint 125 | $array->setAttribute(AttributeKey::ORIGINAL_NODE, null); 126 | $hasChanged = \true; 127 | } 128 | 129 | if ($hasChanged) { 130 | return $node; 131 | } 132 | 133 | return null; 134 | } 135 | 136 | private function shouldRePrint(Array_ $array): bool 137 | { 138 | foreach ($array->items as $key => $item) { 139 | if (! $item instanceof ArrayItem) { 140 | continue; 141 | } 142 | 143 | if (! isset($array->items[$key + 1])) { 144 | continue; 145 | } 146 | 147 | if ($array->items[$key + 1]->getStartLine() !== $item->getEndLine()) { 148 | continue; 149 | } 150 | 151 | return true; 152 | } 153 | 154 | return false; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/ClassMethod/RemoveEmptyTestMethodRector.php: -------------------------------------------------------------------------------- 1 | > 54 | */ 55 | public function getNodeTypes(): array 56 | { 57 | return [ClassMethod::class]; 58 | } 59 | 60 | /** 61 | * @param ClassMethod $node 62 | */ 63 | public function refactor(Node $node): ?int 64 | { 65 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 66 | return null; 67 | } 68 | 69 | if (! str_starts_with($node->name->toString(), 'test')) { 70 | return null; 71 | } 72 | 73 | if ($node->stmts === null) { 74 | return null; 75 | } 76 | 77 | if ($node->stmts !== []) { 78 | return null; 79 | } 80 | 81 | return NodeVisitor::REMOVE_NODE; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/ClassMethod/ReplaceTestAnnotationWithPrefixedFunctionRector.php: -------------------------------------------------------------------------------- 1 | assertSame(2, 1+1); 44 | } 45 | } 46 | CODE_SAMPLE 47 | , 48 | <<<'CODE_SAMPLE' 49 | class SomeTest extends \PHPUnit\Framework\TestCase 50 | { 51 | public function testOnePlusOneShouldBeTwo() 52 | { 53 | $this->assertSame(2, 1+1); 54 | } 55 | } 56 | CODE_SAMPLE 57 | ), 58 | ]); 59 | } 60 | 61 | /** 62 | * @return array> 63 | */ 64 | public function getNodeTypes(): array 65 | { 66 | return [ClassMethod::class]; 67 | } 68 | 69 | /** 70 | * @param ClassMethod $node 71 | */ 72 | public function refactor(Node $node): ?Node 73 | { 74 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 75 | return null; 76 | } 77 | 78 | if (str_starts_with($node->name->toString(), 'test')) { 79 | return null; 80 | } 81 | 82 | $docComment = $node->getDocComment(); 83 | if (! $docComment instanceof Doc) { 84 | return null; 85 | } 86 | 87 | if (! str_contains($docComment->getText(), '@test')) { 88 | return null; 89 | } 90 | 91 | $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); 92 | $this->phpDocTagRemover->removeByName($phpDocInfo, 'test'); 93 | $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); 94 | 95 | $node->name->name = 'test' . ucfirst($node->name->name); 96 | 97 | return $node; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/ClassMethod/ReplaceTestFunctionPrefixWithAttributeRector.php: -------------------------------------------------------------------------------- 1 | assertSame(2, 1+1); 39 | } 40 | } 41 | CODE_SAMPLE 42 | , 43 | <<<'CODE_SAMPLE' 44 | class SomeTest extends \PHPUnit\Framework\TestCase 45 | { 46 | #[Test] 47 | public function onePlusOneShouldBeTwo() 48 | { 49 | $this->assertSame(2, 1+1); 50 | } 51 | } 52 | CODE_SAMPLE 53 | ), 54 | ]); 55 | } 56 | 57 | /** 58 | * @return array> 59 | */ 60 | public function getNodeTypes(): array 61 | { 62 | return [ClassMethod::class]; 63 | } 64 | 65 | /** 66 | * @param ClassMethod $node 67 | */ 68 | public function refactor(Node $node): ?Node 69 | { 70 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 71 | return null; 72 | } 73 | 74 | if (! str_starts_with($node->name->toString(), 'test')) { 75 | return null; 76 | } 77 | 78 | if ($this->phpAttributeAnalyzer->hasPhpAttributes($node, ['PHPUnit\\Framework\\Attributes\\Test'])) { 79 | return null; 80 | } 81 | 82 | if ($node->name->toString() !== 'test' && $node->name->toString() !== 'test_') { 83 | if (str_starts_with($node->name->toString(), 'test_')) { 84 | $node->name->name = lcfirst(substr($node->name->name, 5)); 85 | } elseif (str_starts_with($node->name->toString(), 'test')) { 86 | $node->name->name = lcfirst(substr($node->name->name, 4)); 87 | } 88 | } 89 | 90 | $coversAttributeGroup = $this->createAttributeGroup(); 91 | $node->attrGroups = array_merge($node->attrGroups, [$coversAttributeGroup]); 92 | 93 | return $node; 94 | } 95 | 96 | private function createAttributeGroup(): AttributeGroup 97 | { 98 | $attributeClass = 'PHPUnit\\Framework\\Attributes\\Test'; 99 | 100 | return $this->phpAttributeGroupFactory->createFromClassWithItems($attributeClass, []); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/Class_/AddParentSetupCallOnSetupRector.php: -------------------------------------------------------------------------------- 1 | > 68 | */ 69 | public function getNodeTypes(): array 70 | { 71 | return [Class_::class]; 72 | } 73 | 74 | /** 75 | * @param Class_ $node 76 | */ 77 | public function refactor(Node $node): ?Node 78 | { 79 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 80 | return null; 81 | } 82 | 83 | $setUpMethod = $node->getMethod(MethodName::SET_UP); 84 | if (! $setUpMethod instanceof ClassMethod) { 85 | return null; 86 | } 87 | 88 | if ($setUpMethod->isAbstract() || $setUpMethod->stmts === null) { 89 | return null; 90 | } 91 | 92 | $isSetupExists = (bool) $this->betterNodeFinder->findFirstInFunctionLikeScoped( 93 | $setUpMethod, 94 | function (Node $subNode): bool { 95 | if (! $subNode instanceof StaticCall) { 96 | return false; 97 | } 98 | 99 | if (! $this->isName($subNode->class, 'parent')) { 100 | return false; 101 | } 102 | 103 | return $this->isName($subNode->name, 'setUp'); 104 | } 105 | ); 106 | 107 | if ($isSetupExists) { 108 | return null; 109 | } 110 | 111 | $setUpMethod->stmts = [ 112 | new Expression(new StaticCall(new Name('parent'), new Identifier('setUp'))), 113 | ...$setUpMethod->stmts, 114 | ]; 115 | 116 | return $node; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/Class_/PreferPHPUnitSelfCallRector.php: -------------------------------------------------------------------------------- 1 | assert*() to self::assert*()', [ 31 | new CodeSample( 32 | <<<'CODE_SAMPLE' 33 | use PHPUnit\Framework\TestCase; 34 | 35 | final class SomeClass extends TestCase 36 | { 37 | public function run() 38 | { 39 | $this->assertEquals('expected', $result); 40 | } 41 | } 42 | CODE_SAMPLE 43 | , 44 | <<<'CODE_SAMPLE' 45 | use PHPUnit\Framework\TestCase; 46 | 47 | final class SomeClass extends TestCase 48 | { 49 | public function run() 50 | { 51 | self::assertEquals('expected', $result); 52 | } 53 | } 54 | CODE_SAMPLE 55 | ), 56 | ]); 57 | } 58 | 59 | /** 60 | * @return array> 61 | */ 62 | public function getNodeTypes(): array 63 | { 64 | return [Class_::class]; 65 | } 66 | 67 | /** 68 | * @param Class_ $node 69 | */ 70 | public function refactor(Node $node): ?Node 71 | { 72 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 73 | return null; 74 | } 75 | 76 | $hasChanged = false; 77 | $this->traverseNodesWithCallable($node, function (Node $node) use (&$hasChanged): ?StaticCall { 78 | if (! $node instanceof MethodCall) { 79 | return null; 80 | } 81 | 82 | if ($node->isFirstClassCallable()) { 83 | return null; 84 | } 85 | 86 | if (! $this->assertMethodAnalyzer->detectTestCaseCallForStatic($node)) { 87 | return null; 88 | } 89 | 90 | $methodName = $this->getName($node->name); 91 | 92 | $hasChanged = true; 93 | return $this->nodeFactory->createStaticCall('self', $methodName, $node->getArgs()); 94 | }); 95 | 96 | if ($hasChanged) { 97 | return $node; 98 | } 99 | 100 | return null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/Class_/PreferPHPUnitThisCallRector.php: -------------------------------------------------------------------------------- 1 | assert*()', [ 35 | new CodeSample( 36 | <<<'CODE_SAMPLE' 37 | use PHPUnit\Framework\TestCase; 38 | 39 | final class SomeClass extends TestCase 40 | { 41 | public function run() 42 | { 43 | self::assertEquals('expected', $result); 44 | } 45 | } 46 | CODE_SAMPLE 47 | , 48 | <<<'CODE_SAMPLE' 49 | use PHPUnit\Framework\TestCase; 50 | 51 | final class SomeClass extends TestCase 52 | { 53 | public function run() 54 | { 55 | $this->assertEquals('expected', $result); 56 | } 57 | } 58 | CODE_SAMPLE 59 | ), 60 | ]); 61 | } 62 | 63 | /** 64 | * @return array> 65 | */ 66 | public function getNodeTypes(): array 67 | { 68 | return [Class_::class]; 69 | } 70 | 71 | /** 72 | * @param Class_ $node 73 | */ 74 | public function refactor(Node $node): ?Node 75 | { 76 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 77 | return null; 78 | } 79 | 80 | $hasChanged = false; 81 | 82 | $this->traverseNodesWithCallable($node, function (Node $node) use (&$hasChanged): int|null|MethodCall { 83 | $isInsideStaticFunctionLike = ($node instanceof ClassMethod && $node->isStatic()) || (($node instanceof Closure || $node instanceof ArrowFunction) && $node->static); 84 | if ($isInsideStaticFunctionLike) { 85 | return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; 86 | } 87 | 88 | if (! $node instanceof StaticCall) { 89 | return null; 90 | } 91 | 92 | if ($node->isFirstClassCallable()) { 93 | return null; 94 | } 95 | 96 | if (! $this->assertMethodAnalyzer->detectTestCaseCall($node)) { 97 | return null; 98 | } 99 | 100 | $methodName = $this->getName($node->name); 101 | 102 | $hasChanged = true; 103 | return $this->nodeFactory->createMethodCall('this', $methodName, $node->getArgs()); 104 | }); 105 | 106 | if ($hasChanged) { 107 | return $node; 108 | } 109 | 110 | return null; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/Class_/SingleMockPropertyTypeRector.php: -------------------------------------------------------------------------------- 1 | someEntityMock = $this->createMock(SimpleObject::class); 44 | } 45 | } 46 | CODE_SAMPLE 47 | , 48 | <<<'CODE_SAMPLE' 49 | use PHPUnit\Framework\TestCase; 50 | use PHPUnit\Framework\MockObject\MockObject; 51 | 52 | final class MockingEntity extends TestCase 53 | { 54 | private MockObject $someEntityMock; 55 | 56 | protected function setUp(): void 57 | { 58 | $this->someEntityMock = $this->createMock(SimpleObject::class); 59 | } 60 | } 61 | CODE_SAMPLE 62 | ), 63 | ] 64 | ); 65 | } 66 | 67 | /** 68 | * @return array> 69 | */ 70 | public function getNodeTypes(): array 71 | { 72 | return [Class_::class]; 73 | } 74 | 75 | /** 76 | * @param Class_ $node 77 | */ 78 | public function refactor(Node $node): ?Class_ 79 | { 80 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 81 | return null; 82 | } 83 | 84 | $hasChanged = false; 85 | 86 | foreach ($node->getProperties() as $property) { 87 | if (! $property->type instanceof IntersectionType && ! $property->type instanceof UnionType) { 88 | continue; 89 | } 90 | 91 | $complexType = $property->type; 92 | if (count($complexType->types) !== 2) { 93 | continue; 94 | } 95 | 96 | foreach ($complexType->types as $intersectionType) { 97 | if ($this->isName($intersectionType, MockObject::class)) { 98 | $property->type = $intersectionType; 99 | $hasChanged = true; 100 | 101 | break; 102 | } 103 | } 104 | } 105 | 106 | if (! $hasChanged) { 107 | return null; 108 | } 109 | 110 | return $node; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/Foreach_/SimplifyForeachInstanceOfRector.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(SplFileInfo::class, $foo); 31 | } 32 | CODE_SAMPLE 33 | , 34 | <<<'CODE_SAMPLE' 35 | $this->assertContainsOnlyInstancesOf(\SplFileInfo::class, $foos); 36 | CODE_SAMPLE 37 | ), 38 | ] 39 | ); 40 | } 41 | 42 | /** 43 | * @return array> 44 | */ 45 | public function getNodeTypes(): array 46 | { 47 | return [Foreach_::class]; 48 | } 49 | 50 | /** 51 | * @param Foreach_ $node 52 | */ 53 | public function refactor(Node $node): ?Node 54 | { 55 | if (count($node->stmts) !== 1) { 56 | return null; 57 | } 58 | 59 | $onlyStmt = $node->stmts[0]; 60 | if (! $onlyStmt instanceof Expression) { 61 | return null; 62 | } 63 | 64 | $expr = $onlyStmt->expr; 65 | if (! $expr instanceof MethodCall && ! $expr instanceof StaticCall) { 66 | return null; 67 | } 68 | 69 | if (! $this->isName($expr->name, 'assertInstanceOf')) { 70 | return null; 71 | } 72 | 73 | if ($expr->isFirstClassCallable()) { 74 | return null; 75 | } 76 | 77 | if (! $this->nodeComparator->areNodesEqual($node->valueVar, $expr->getArgs()[1]->value)) { 78 | return null; 79 | } 80 | 81 | // skip if there is a custom message included; it might be per item 82 | if (count($expr->getArgs()) === 3) { 83 | return null; 84 | } 85 | 86 | $newArgs = [$expr->getArgs()[0], new Arg($node->expr)]; 87 | 88 | if ($expr instanceof StaticCall) { 89 | $staticCall = new StaticCall($expr->class, 'assertContainsOnlyInstancesOf', $newArgs); 90 | return new Expression($staticCall); 91 | } 92 | 93 | $methodCall = new MethodCall($expr->var, 'assertContainsOnlyInstancesOf', $newArgs); 94 | return new Expression($methodCall); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertCompareOnCountableWithMethodToAssertCountRector.php: -------------------------------------------------------------------------------- 1 | assertSame(1, $countable->count()); 37 | CODE_SAMPLE 38 | , 39 | <<<'CODE_SAMPLE' 40 | $this->assertCount(1, $countable); 41 | CODE_SAMPLE 42 | ), 43 | new CodeSample( 44 | '$this->assertSame(10, count($anything), "message");', 45 | '$this->assertCount(10, $anything, "message");' 46 | ), 47 | ] 48 | ); 49 | } 50 | 51 | /** 52 | * @return array> 53 | */ 54 | public function getNodeTypes(): array 55 | { 56 | return [MethodCall::class, StaticCall::class]; 57 | } 58 | 59 | /** 60 | * @param MethodCall|StaticCall $node 61 | */ 62 | public function refactor(Node $node): MethodCall|StaticCall|null 63 | { 64 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames( 65 | $node, 66 | ['assertSame', 'assertNotSame', 'assertEquals', 'assertNotEquals'] 67 | )) { 68 | return null; 69 | } 70 | 71 | if ($node->isFirstClassCallable()) { 72 | return null; 73 | } 74 | 75 | $assertArgs = $node->getArgs(); 76 | if (count($assertArgs) < 2) { 77 | return null; 78 | } 79 | 80 | $comparedExpr = $assertArgs[1]->value; 81 | 82 | if ( 83 | $comparedExpr instanceof FuncCall 84 | && $this->isNames($comparedExpr->name, ['count', 'sizeof', 'iterator_count']) 85 | ) { 86 | $countArg = $comparedExpr->getArgs()[0]; 87 | $assertArgs[1] = new Arg($countArg->value); 88 | 89 | $node->args = $assertArgs; 90 | $this->renameMethod($node); 91 | 92 | return $node; 93 | } 94 | 95 | if ( 96 | ($comparedExpr instanceof MethodCall) 97 | && $this->isName($comparedExpr->name, 'count') 98 | && $comparedExpr->getArgs() === [] 99 | ) { 100 | $type = $this->getType($comparedExpr->var); 101 | 102 | if ((new ObjectType('Countable'))->isSuperTypeOf($type)->yes()) { 103 | $args = $assertArgs; 104 | $args[1] = new Arg($comparedExpr->var); 105 | 106 | $node->args = $args; 107 | $this->renameMethod($node); 108 | } 109 | } 110 | 111 | return null; 112 | } 113 | 114 | private function renameMethod(MethodCall|StaticCall $node): void 115 | { 116 | if ($this->isNames($node->name, ['assertSame', 'assertEquals'])) { 117 | $node->name = new Identifier('assertCount'); 118 | } elseif ($this->isNames($node->name, ['assertNotSame', 'assertNotEquals'])) { 119 | $node->name = new Identifier('assertNotCount'); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertEmptyNullableObjectToAssertInstanceofRector.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty($someObject); 47 | } 48 | } 49 | CODE_SAMPLE 50 | , 51 | <<<'CODE_SAMPLE' 52 | use PHPUnit\Framework\TestCase; 53 | 54 | class SomeClass extends TestCase 55 | { 56 | public function test() 57 | { 58 | $someObject = new stdClass(); 59 | 60 | $this->assertInstanceof(stdClass::class, $someObject); 61 | } 62 | } 63 | CODE_SAMPLE 64 | ), 65 | 66 | ] 67 | ); 68 | } 69 | 70 | /** 71 | * @return array> 72 | */ 73 | public function getNodeTypes(): array 74 | { 75 | return [MethodCall::class]; 76 | } 77 | 78 | /** 79 | * @param MethodCall $node 80 | */ 81 | public function refactor(Node $node): ?Node 82 | { 83 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 84 | return null; 85 | } 86 | 87 | if (! $this->isNames($node->name, ['assertNotEmpty', 'assertEmpty', 'assertNull', 'assertNotNull'])) { 88 | return null; 89 | } 90 | 91 | if ($node->isFirstClassCallable()) { 92 | return null; 93 | } 94 | 95 | $firstArg = $node->getArgs()[0] ?? null; 96 | if (! $firstArg instanceof Arg) { 97 | return null; 98 | } 99 | 100 | $firstArgType = $this->getType($firstArg->value); 101 | if (! $firstArgType instanceof UnionType) { 102 | return null; 103 | } 104 | 105 | $pureType = TypeCombinator::removeNull($firstArgType); 106 | if (! $pureType instanceof ObjectType) { 107 | return null; 108 | } 109 | 110 | $methodName = $this->isNames( 111 | $node->name, 112 | ['assertEmpty', 'assertNull'] 113 | ) ? 'assertNotInstanceOf' : 'assertInstanceOf'; 114 | 115 | $node->name = new Identifier($methodName); 116 | 117 | $fullyQualified = new FullyQualified($pureType->getClassName()); 118 | 119 | $customMessageArg = $node->getArgs()[1] ?? null; 120 | 121 | $node->args[0] = new Arg(new ClassConstFetch($fullyQualified, 'class')); 122 | $node->args[1] = $firstArg; 123 | 124 | if ($customMessageArg instanceof Arg) { 125 | $node->args[] = $customMessageArg; 126 | } 127 | 128 | return $node; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertEqualsOrAssertSameFloatParameterToSpecificMethodsTypeRector.php: -------------------------------------------------------------------------------- 1 | assertSame(10.20, $value); 41 | $this->assertEquals(10.200, $value); 42 | CODE_SAMPLE 43 | , 44 | <<<'CODE_SAMPLE' 45 | $this->assertEqualsWithDelta(10.20, $value, PHP_FLOAT_EPSILON); 46 | $this->assertEqualsWithDelta(10.200, $value, PHP_FLOAT_EPSILON); 47 | CODE_SAMPLE 48 | ), 49 | ] 50 | ); 51 | } 52 | 53 | /** 54 | * @return array> 55 | */ 56 | public function getNodeTypes(): array 57 | { 58 | return [MethodCall::class]; 59 | } 60 | 61 | /** 62 | * @param MethodCall $node 63 | */ 64 | public function refactor(Node $node): ?Node 65 | { 66 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, ['assertEquals', 'assertSame'])) { 67 | return null; 68 | } 69 | 70 | if ($node->isFirstClassCallable()) { 71 | return null; 72 | } 73 | 74 | $args = $node->getArgs(); 75 | 76 | $firstValue = $args[0]->value; 77 | if (! $firstValue instanceof Float_) { 78 | return null; 79 | } 80 | 81 | $customMessageArg = $args[2] ?? null; 82 | 83 | $newMethodCall = $this->assertCallFactory->createCallWithName($node, 'assertEqualsWithDelta'); 84 | $newMethodCall->args[0] = $args[0]; 85 | $newMethodCall->args[1] = $args[1]; 86 | $newMethodCall->args[2] = new Arg(new ConstFetch(new Name('PHP_FLOAT_EPSILON'))); 87 | 88 | if ($customMessageArg instanceof Arg) { 89 | $newMethodCall->args[] = $customMessageArg; 90 | } 91 | 92 | return $newMethodCall; 93 | } 94 | 95 | public function provideMinPhpVersion(): int 96 | { 97 | return PhpVersion::PHP_72; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertFalseStrposToContainsRector.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | private const RENAME_METHODS_MAP = [ 26 | 'assertFalse' => 'assertStringNotContainsString', 27 | 'assertNotFalse' => 'assertStringContainsString', 28 | ]; 29 | 30 | public function __construct( 31 | private readonly IdentifierManipulator $identifierManipulator, 32 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer 33 | ) { 34 | } 35 | 36 | public function getRuleDefinition(): RuleDefinition 37 | { 38 | return new RuleDefinition( 39 | 'Turns `strpos`/`stripos` comparisons to their method name alternatives in PHPUnit TestCase', 40 | [ 41 | new CodeSample( 42 | '$this->assertFalse(strpos($anything, "foo"), "message");', 43 | '$this->assertNotContains("foo", $anything, "message");' 44 | )] 45 | ); 46 | } 47 | 48 | /** 49 | * @return array> 50 | */ 51 | public function getNodeTypes(): array 52 | { 53 | return [MethodCall::class, StaticCall::class]; 54 | } 55 | 56 | /** 57 | * @param MethodCall|StaticCall $node 58 | */ 59 | public function refactor(Node $node): ?Node 60 | { 61 | $oldMethodName = array_keys(self::RENAME_METHODS_MAP); 62 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, $oldMethodName)) { 63 | return null; 64 | } 65 | 66 | if ($node->isFirstClassCallable()) { 67 | return null; 68 | } 69 | 70 | $firstArgumentValue = $node->getArgs()[0] 71 | ->value; 72 | if ($firstArgumentValue instanceof StaticCall) { 73 | return null; 74 | } 75 | 76 | if ($firstArgumentValue instanceof MethodCall) { 77 | return null; 78 | } 79 | 80 | if (! $this->isNames($firstArgumentValue, ['strpos', 'stripos'])) { 81 | return null; 82 | } 83 | 84 | $this->identifierManipulator->renameNodeWithMap($node, self::RENAME_METHODS_MAP); 85 | 86 | return $this->changeArgumentsOrder($node); 87 | } 88 | 89 | private function changeArgumentsOrder(MethodCall|StaticCall $node): MethodCall|StaticCall|null 90 | { 91 | $oldArguments = $node->getArgs(); 92 | 93 | $strposFuncCallNode = $oldArguments[0]->value; 94 | if (! $strposFuncCallNode instanceof FuncCall) { 95 | return null; 96 | } 97 | 98 | $firstArgument = $strposFuncCallNode->getArgs()[1]; 99 | $secondArgument = $strposFuncCallNode->getArgs()[0]; 100 | 101 | unset($oldArguments[0]); 102 | 103 | $newArgs = [$firstArgument, $secondArgument]; 104 | $node->args = [...$newArgs, ...$oldArguments]; 105 | 106 | return $node; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertIssetToSpecificMethodRector.php: -------------------------------------------------------------------------------- 1 | assertTrue(isset($anything["foo"]), "message");', 37 | '$this->assertArrayHasKey("foo", $anything, "message");' 38 | ), 39 | ] 40 | ); 41 | } 42 | 43 | /** 44 | * @return array> 45 | */ 46 | public function getNodeTypes(): array 47 | { 48 | return [MethodCall::class, StaticCall::class]; 49 | } 50 | 51 | /** 52 | * @param MethodCall|StaticCall $node 53 | */ 54 | public function refactor(Node $node): ?Node 55 | { 56 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames( 57 | $node, 58 | [AssertMethod::ASSERT_TRUE, AssertMethod::ASSERT_FALSE] 59 | )) { 60 | return null; 61 | } 62 | 63 | if ($node->isFirstClassCallable()) { 64 | return null; 65 | } 66 | 67 | $firstArg = $node->getArgs()[0]; 68 | $firstArgumentValue = $firstArg->value; 69 | 70 | // is property access 71 | if (! $firstArgumentValue instanceof Isset_) { 72 | return null; 73 | } 74 | 75 | $issetExpr = $firstArgumentValue->vars[0]; 76 | if (! $issetExpr instanceof ArrayDimFetch) { 77 | return null; 78 | } 79 | 80 | return $this->refactorArrayDimFetchNode($node, $issetExpr); 81 | } 82 | 83 | private function refactorArrayDimFetchNode(MethodCall|StaticCall $node, ArrayDimFetch $arrayDimFetch): Node 84 | { 85 | $this->identifierManipulator->renameNodeWithMap($node, [ 86 | AssertMethod::ASSERT_TRUE => 'assertArrayHasKey', 87 | AssertMethod::ASSERT_FALSE => 'assertArrayNotHasKey', 88 | ]); 89 | 90 | $oldArgs = $node->getArgs(); 91 | unset($oldArgs[0]); 92 | 93 | $node->args = [...$this->nodeFactory->createArgs([$arrayDimFetch->dim, $arrayDimFetch->var]), ...$oldArgs]; 94 | return $node; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertNotOperatorRector.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | private const RENAME_METHODS_MAP = [ 27 | 'assertTrue' => 'assertFalse', 28 | 'assertFalse' => 'assertTrue', 29 | ]; 30 | 31 | public function __construct( 32 | private readonly IdentifierManipulator $identifierManipulator, 33 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer 34 | ) { 35 | } 36 | 37 | public function getRuleDefinition(): RuleDefinition 38 | { 39 | return new RuleDefinition( 40 | 'Turns not-operator comparisons to their method name alternatives in PHPUnit TestCase', 41 | [ 42 | new CodeSample('$this->assertTrue(!$foo, "message");', '$this->assertFalse($foo, "message");'), 43 | new CodeSample('$this->assertFalse(!$foo, "message");', '$this->assertTrue($foo, "message");'), 44 | ] 45 | ); 46 | } 47 | 48 | /** 49 | * @return array> 50 | */ 51 | public function getNodeTypes(): array 52 | { 53 | return [MethodCall::class, StaticCall::class]; 54 | } 55 | 56 | /** 57 | * @param MethodCall|StaticCall $node 58 | */ 59 | public function refactor(Node $node): ?Node 60 | { 61 | $oldMethodNames = array_keys(self::RENAME_METHODS_MAP); 62 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, $oldMethodNames)) { 63 | return null; 64 | } 65 | 66 | if ($node->isFirstClassCallable()) { 67 | return null; 68 | } 69 | 70 | $firstArgumentValue = $node->getArgs()[0] 71 | ->value; 72 | if (! $firstArgumentValue instanceof BooleanNot) { 73 | return null; 74 | } 75 | 76 | $this->identifierManipulator->renameNodeWithMap($node, self::RENAME_METHODS_MAP); 77 | 78 | $oldArguments = $node->getArgs(); 79 | 80 | /** @var BooleanNot $negation */ 81 | $negation = $oldArguments[0]->value; 82 | 83 | $expression = $negation->expr; 84 | 85 | unset($oldArguments[0]); 86 | 87 | $node->args = array_merge([new Arg($expression)], $oldArguments); 88 | 89 | return $node; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertPropertyExistsRector.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | private const RENAME_METHODS_WITH_OBJECT_MAP = [ 29 | 'assertTrue' => 'assertObjectHasAttribute', 30 | 'assertFalse' => 'assertObjectNotHasAttribute', 31 | ]; 32 | 33 | /** 34 | * @var array 35 | */ 36 | private const RENAME_METHODS_WITH_CLASS_MAP = [ 37 | 'assertTrue' => 'assertClassHasAttribute', 38 | 'assertFalse' => 'assertClassNotHasAttribute', 39 | ]; 40 | 41 | public function __construct( 42 | private readonly IdentifierManipulator $identifierManipulator, 43 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer 44 | ) { 45 | } 46 | 47 | public function getRuleDefinition(): RuleDefinition 48 | { 49 | return new RuleDefinition( 50 | 'Turns `property_exists` comparisons to their method name alternatives in PHPUnit TestCase', 51 | [ 52 | new CodeSample( 53 | <<<'CODE_SAMPLE' 54 | $this->assertFalse(property_exists(new Class, "property")); 55 | $this->assertTrue(property_exists(new Class, "property")); 56 | CODE_SAMPLE 57 | , 58 | <<<'CODE_SAMPLE' 59 | $this->assertClassHasAttribute("property", "Class"); 60 | $this->assertClassNotHasAttribute("property", "Class"); 61 | CODE_SAMPLE 62 | ), 63 | ] 64 | ); 65 | } 66 | 67 | /** 68 | * @return array> 69 | */ 70 | public function getNodeTypes(): array 71 | { 72 | return [MethodCall::class, StaticCall::class]; 73 | } 74 | 75 | /** 76 | * @param MethodCall|StaticCall $node 77 | */ 78 | public function refactor(Node $node): ?Node 79 | { 80 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, ['assertTrue', 'assertFalse'])) { 81 | return null; 82 | } 83 | 84 | if ($node->isFirstClassCallable()) { 85 | return null; 86 | } 87 | 88 | $firstArgumentValue = $node->getArgs()[0] 89 | ->value; 90 | if (! $firstArgumentValue instanceof FuncCall) { 91 | return null; 92 | } 93 | 94 | if (! $this->isName($firstArgumentValue, 'property_exists')) { 95 | return null; 96 | } 97 | 98 | $propertyExistsMethodCall = $node->getArgs()[0] 99 | ->value; 100 | if (! $propertyExistsMethodCall instanceof FuncCall) { 101 | return null; 102 | } 103 | 104 | $firstArgument = $propertyExistsMethodCall->getArgs()[0]; 105 | $secondArgument = $propertyExistsMethodCall->getArgs()[1]; 106 | 107 | if ($firstArgument->value instanceof Variable) { 108 | $secondArg = new Variable($firstArgument->value->name); 109 | $map = self::RENAME_METHODS_WITH_OBJECT_MAP; 110 | } elseif ($firstArgument->value instanceof New_) { 111 | $secondArg = $this->getName($firstArgument->value->class); 112 | $map = self::RENAME_METHODS_WITH_CLASS_MAP; 113 | } else { 114 | return null; 115 | } 116 | 117 | if (! $secondArgument->value instanceof String_) { 118 | return null; 119 | } 120 | 121 | unset($node->args[0]); 122 | 123 | $newArgs = $this->nodeFactory->createArgs([$secondArgument->value->value, $secondArg]); 124 | $node->args = array_merge($newArgs, $node->getArgs()); 125 | 126 | $this->identifierManipulator->renameNodeWithMap($node, $map); 127 | 128 | return $node; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertSameBoolNullToSpecificMethodRector.php: -------------------------------------------------------------------------------- 1 | constantWithAssertMethods = [ 35 | new ConstantWithAssertMethods('null', 'assertNull', 'assertNotNull'), 36 | new ConstantWithAssertMethods('true', 'assertTrue', 'assertNotTrue'), 37 | new ConstantWithAssertMethods('false', 'assertFalse', 'assertNotFalse'), 38 | ]; 39 | } 40 | 41 | public function getRuleDefinition(): RuleDefinition 42 | { 43 | return new RuleDefinition( 44 | 'Turns same bool and null comparisons to their method name alternatives in PHPUnit TestCase', 45 | [ 46 | new CodeSample('$this->assertSame(null, $anything);', '$this->assertNull($anything);'), 47 | new CodeSample('$this->assertNotSame(false, $anything);', '$this->assertNotFalse($anything);'), 48 | ] 49 | ); 50 | } 51 | 52 | /** 53 | * @return array> 54 | */ 55 | public function getNodeTypes(): array 56 | { 57 | return [MethodCall::class, StaticCall::class]; 58 | } 59 | 60 | /** 61 | * @param MethodCall|StaticCall $node 62 | */ 63 | public function refactor(Node $node): ?Node 64 | { 65 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, ['assertSame', 'assertNotSame'])) { 66 | return null; 67 | } 68 | 69 | if ($node->isFirstClassCallable()) { 70 | return null; 71 | } 72 | 73 | $firstArgumentValue = $node->getArgs()[0] 74 | ->value; 75 | if (! $firstArgumentValue instanceof ConstFetch) { 76 | return null; 77 | } 78 | 79 | foreach ($this->constantWithAssertMethods as $constantWithAssertMethod) { 80 | if (! $this->isName($firstArgumentValue, $constantWithAssertMethod->getConstant())) { 81 | continue; 82 | } 83 | 84 | $this->renameMethod($node, $constantWithAssertMethod); 85 | $this->argumentMover->removeFirstArg($node); 86 | 87 | return $node; 88 | } 89 | 90 | return null; 91 | } 92 | 93 | private function renameMethod( 94 | MethodCall|StaticCall $node, 95 | ConstantWithAssertMethods $constantWithAssertMethods 96 | ): void { 97 | $this->identifierManipulator->renameNodeWithMap($node, [ 98 | 'assertSame' => $constantWithAssertMethods->getAssetMethodName(), 99 | 'assertNotSame' => $constantWithAssertMethods->getNotAssertMethodName(), 100 | ]); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/AssertSameTrueFalseToAssertTrueFalseRector.php: -------------------------------------------------------------------------------- 1 | assertSame(true, ...) to assertTrue()', 33 | [ 34 | new CodeSample( 35 | <<<'CODE_SAMPLE' 36 | use PHPUnit\Framework\TestCase; 37 | 38 | final class SomeTest extends TestCase 39 | { 40 | public function test() 41 | { 42 | $value = (bool) mt_rand(0, 1); 43 | $this->assertSame(true, $value); 44 | } 45 | } 46 | CODE_SAMPLE 47 | 48 | , 49 | <<<'CODE_SAMPLE' 50 | use PHPUnit\Framework\TestCase; 51 | 52 | final class SomeTest extends TestCase 53 | { 54 | public function test() 55 | { 56 | $value = (bool) mt_rand(0, 1); 57 | $this->assertTrue($value); 58 | } 59 | } 60 | CODE_SAMPLE 61 | ), 62 | ] 63 | ); 64 | } 65 | 66 | /** 67 | * @return array> 68 | */ 69 | public function getNodeTypes(): array 70 | { 71 | return [MethodCall::class]; 72 | } 73 | 74 | /** 75 | * @param MethodCall $node 76 | */ 77 | public function refactor(Node $node): ?Node 78 | { 79 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames( 80 | $node, 81 | ['assertSame', 'assertEqual', 'assertNotSame', 'assertNotEqual'] 82 | )) { 83 | return null; 84 | } 85 | 86 | if ($node->isFirstClassCallable()) { 87 | return null; 88 | } 89 | 90 | $firstArg = $node->getArgs()[0]; 91 | 92 | if ($this->valueResolver->isTrue($firstArg->value)) { 93 | $this->argumentMover->removeFirstArg($node); 94 | 95 | $node->name = new Identifier('assertTrue'); 96 | 97 | return $node; 98 | } 99 | 100 | if ($this->valueResolver->isFalse($firstArg->value)) { 101 | $this->argumentMover->removeFirstArg($node); 102 | 103 | $node->name = new Identifier('assertFalse'); 104 | return $node; 105 | } 106 | 107 | return null; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/FlipAssertRector.php: -------------------------------------------------------------------------------- 1 | assertSame($result, 'expected'); 53 | } 54 | } 55 | CODE_SAMPLE 56 | , 57 | <<<'CODE_SAMPLE' 58 | assertSame('expected', $result); 68 | } 69 | } 70 | CODE_SAMPLE 71 | ), 72 | ] 73 | ); 74 | } 75 | 76 | /** 77 | * @return array> 78 | */ 79 | public function getNodeTypes(): array 80 | { 81 | return [MethodCall::class, StaticCall::class]; 82 | } 83 | 84 | /** 85 | * @param MethodCall|StaticCall $node 86 | */ 87 | public function refactor(Node $node): ?Node 88 | { 89 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, self::METHOD_NAMES)) { 90 | return null; 91 | } 92 | 93 | if ($node->isFirstClassCallable()) { 94 | return null; 95 | } 96 | 97 | $firstArg = $node->getArgs()[0]; 98 | $secondArg = $node->getArgs()[1]; 99 | 100 | // correct location 101 | if ($this->isScalarValue($firstArg->value)) { 102 | return null; 103 | } 104 | 105 | if (! $this->isScalarValue($secondArg->value)) { 106 | return null; 107 | } 108 | 109 | $oldArgs = $node->getArgs(); 110 | // flip args 111 | [$oldArgs[0], $oldArgs[1]] = [$oldArgs[1], $oldArgs[0]]; 112 | 113 | $node->args = $oldArgs; 114 | return $node; 115 | } 116 | 117 | private function isScalarValue(Expr $expr): bool 118 | { 119 | if ($expr instanceof Scalar) { 120 | return true; 121 | } 122 | 123 | if ($expr instanceof ConstFetch) { 124 | return true; 125 | } 126 | 127 | return $expr instanceof ClassConstFetch; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/NarrowIdenticalWithConsecutiveRector.php: -------------------------------------------------------------------------------- 1 | personServiceMock->expects($this->exactly(3)) 41 | ->method('prepare') 42 | ->withConsecutive( 43 | [1], 44 | [1], 45 | [1], 46 | ) 47 | ->willReturnOnConsecutiveCalls( 48 | [2], 49 | [2], 50 | [2], 51 | ); 52 | } 53 | } 54 | CODE_SAMPLE 55 | , 56 | <<<'CODE_SAMPLE' 57 | use PHPUnit\Framework\TestCase; 58 | 59 | final class SomeTest extends TestCase 60 | { 61 | public function run() 62 | { 63 | $this->personServiceMock->expects($this->exactly(3)) 64 | ->method('prepare') 65 | ->with([1]) 66 | ->willReturn([2]); 67 | } 68 | } 69 | CODE_SAMPLE 70 | ), 71 | ] 72 | ); 73 | } 74 | 75 | /** 76 | * @return array> 77 | */ 78 | public function getNodeTypes(): array 79 | { 80 | return [MethodCall::class]; 81 | } 82 | 83 | /** 84 | * @param MethodCall $node 85 | */ 86 | public function refactor(Node $node): MethodCall|null 87 | { 88 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 89 | return null; 90 | } 91 | 92 | if (! $this->isNames($node->name, ['withConsecutive', 'willReturnOnConsecutiveCalls'])) { 93 | return null; 94 | } 95 | 96 | if ($node->isFirstClassCallable()) { 97 | return null; 98 | } 99 | 100 | $firstArg = $node->getArgs()[0]; 101 | 102 | // skip as most likely nested array of unique values 103 | if ($firstArg->unpack) { 104 | return null; 105 | } 106 | 107 | $uniqueArgValues = $this->resolveUniqueArgValues($node); 108 | 109 | // multiple unique values 110 | if (count($uniqueArgValues) !== 1) { 111 | return null; 112 | } 113 | 114 | $firstArg = $node->getArgs()[0]; 115 | 116 | if ($this->isName($node->name, 'withConsecutive')) { 117 | $node->name = new Identifier('with'); 118 | } else { 119 | $node->name = new Identifier('willReturn'); 120 | } 121 | 122 | // use simpler with() instead 123 | $node->args = [new Arg($firstArg->value)]; 124 | 125 | return $node; 126 | } 127 | 128 | /** 129 | * @return string[] 130 | */ 131 | private function resolveUniqueArgValues(MethodCall $methodCall): array 132 | { 133 | $printerStandard = new Standard(); 134 | $printedValues = []; 135 | 136 | foreach ($methodCall->getArgs() as $arg) { 137 | $printedValues[] = $printerStandard->prettyPrintExpr($arg->value); 138 | } 139 | 140 | return array_unique($printedValues); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/RemoveExpectAnyFromMockRector.php: -------------------------------------------------------------------------------- 1 | any())` from mocks as it has no added value', 30 | [ 31 | new CodeSample( 32 | <<<'CODE_SAMPLE' 33 | use PHPUnit\Framework\TestCase; 34 | 35 | class SomeClass extends TestCase 36 | { 37 | public function test() 38 | { 39 | $translator = $this->getMock('SomeClass'); 40 | $translator->expects($this->any()) 41 | ->method('trans') 42 | ->willReturn('translated max {{ max }}!'); 43 | } 44 | } 45 | CODE_SAMPLE 46 | , 47 | <<<'CODE_SAMPLE' 48 | use PHPUnit\Framework\TestCase; 49 | 50 | class SomeClass extends TestCase 51 | { 52 | public function test() 53 | { 54 | $translator = $this->getMock('SomeClass'); 55 | $translator->method('trans') 56 | ->willReturn('translated max {{ max }}!'); 57 | } 58 | } 59 | CODE_SAMPLE 60 | ), 61 | ] 62 | ); 63 | } 64 | 65 | /** 66 | * @return array> 67 | */ 68 | public function getNodeTypes(): array 69 | { 70 | return [MethodCall::class]; 71 | } 72 | 73 | /** 74 | * @param MethodCall $node 75 | */ 76 | public function refactor(Node $node): ?Node 77 | { 78 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 79 | return null; 80 | } 81 | 82 | if (! $this->isName($node->name, 'expects')) { 83 | return null; 84 | } 85 | 86 | if ($node->isFirstClassCallable()) { 87 | return null; 88 | } 89 | 90 | if (count($node->args) !== 1) { 91 | return null; 92 | } 93 | 94 | $onlyArgument = $node->getArgs()[0] 95 | ->value; 96 | if (! $this->isMethodCallOnVariableNamed($onlyArgument, 'this', 'any')) { 97 | return null; 98 | } 99 | 100 | return $node->var; 101 | } 102 | 103 | private function isMethodCallOnVariableNamed(Expr $expr, string $variableName, string $methodName): bool 104 | { 105 | if (! $expr instanceof MethodCall) { 106 | return false; 107 | } 108 | 109 | if (! $this->isName($expr->var, $variableName)) { 110 | return false; 111 | } 112 | 113 | return $this->isName($expr->name, $methodName); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/SingleWithConsecutiveToWithRector.php: -------------------------------------------------------------------------------- 1 | personServiceMock->expects($this->exactly(3)) 43 | ->method('prepare') 44 | ->withConsecutive( 45 | [1], 46 | ); 47 | } 48 | } 49 | CODE_SAMPLE 50 | , 51 | <<<'CODE_SAMPLE' 52 | use PHPUnit\Framework\TestCase; 53 | 54 | final class SomeTest extends TestCase 55 | { 56 | public function run() 57 | { 58 | $this->personServiceMock->expects($this->exactly(3)) 59 | ->method('prepare') 60 | ->with([1]); 61 | } 62 | } 63 | CODE_SAMPLE 64 | ), 65 | ] 66 | ); 67 | } 68 | 69 | /** 70 | * @return array> 71 | */ 72 | public function getNodeTypes(): array 73 | { 74 | return [MethodCall::class]; 75 | } 76 | 77 | /** 78 | * @param MethodCall $node 79 | */ 80 | public function refactor(Node $node): MethodCall|null 81 | { 82 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 83 | return null; 84 | } 85 | 86 | if (! $this->isNames($node->name, ['withConsecutive', 'willReturnOnConsecutiveCalls'])) { 87 | return null; 88 | } 89 | 90 | if ($node->isFirstClassCallable()) { 91 | return null; 92 | } 93 | 94 | if (count($node->getArgs()) !== 1) { 95 | return null; 96 | } 97 | 98 | $firstArg = $node->getArgs()[0]; 99 | 100 | // skip as multiple unique values 101 | if ($firstArg->unpack) { 102 | return null; 103 | } 104 | 105 | // use simpler with()/willReturn() instead 106 | if ($this->isName($node->name, 'withConsecutive')) { 107 | $node->name = new Identifier('with'); 108 | } else { 109 | $node->name = new Identifier('willReturn'); 110 | } 111 | 112 | // has assert inside? 113 | $hasAssertInside = (bool) $this->betterNodeFinder->findFirst( 114 | $firstArg->value, 115 | function (Node $node): bool { 116 | if (! $node instanceof MethodCall) { 117 | return false; 118 | } 119 | 120 | return $this->isNames($node->name, ['equalTo', 'instanceOf']); 121 | } 122 | ); 123 | 124 | // replace $this->equalsTo() with direct value 125 | $this->traverseNodesWithCallable($firstArg->value, function (Node $node): ?Node { 126 | if (! $node instanceof MethodCall) { 127 | return null; 128 | } 129 | 130 | if (! $this->isName($node->name, 'equalTo')) { 131 | return null; 132 | } 133 | 134 | return $node->getArgs()[0] 135 | ->value; 136 | }); 137 | 138 | if ($hasAssertInside && $firstArg->value instanceof Array_) { 139 | $args = $this->nodeFactory->createArgs($firstArg->value->items); 140 | } else { 141 | $args = [new Arg($firstArg->value)]; 142 | } 143 | 144 | $node->args = $args; 145 | 146 | return $node; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /rules/CodeQuality/Rector/MethodCall/UseSpecificWithMethodRector.php: -------------------------------------------------------------------------------- 1 | with() to more specific method', 31 | [ 32 | new CodeSample( 33 | <<<'CODE_SAMPLE' 34 | class SomeClass extends PHPUnit\Framework\TestCase 35 | { 36 | public function test() 37 | { 38 | $translator = $this->createMock('SomeClass'); 39 | 40 | $translator->expects($this->any()) 41 | ->method('trans') 42 | ->with($this->equalTo('old max {{ max }}!')); 43 | } 44 | } 45 | CODE_SAMPLE 46 | , 47 | <<<'CODE_SAMPLE' 48 | class SomeClass extends PHPUnit\Framework\TestCase 49 | { 50 | public function test() 51 | { 52 | $translator = $this->createMock('SomeClass'); 53 | 54 | $translator->expects($this->any()) 55 | ->method('trans') 56 | ->with('old max {{ max }}!'); 57 | } 58 | } 59 | CODE_SAMPLE 60 | ), 61 | ] 62 | ); 63 | } 64 | 65 | /** 66 | * @return array> 67 | */ 68 | public function getNodeTypes(): array 69 | { 70 | return [MethodCall::class, StaticCall::class]; 71 | } 72 | 73 | /** 74 | * @param MethodCall|StaticCall $node 75 | */ 76 | public function refactor(Node $node): ?Node 77 | { 78 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 79 | return null; 80 | } 81 | 82 | // we cannot check caller types, as on old PHPUnit version, this the magic ->method() call result to a mixed type 83 | if (! $this->isName($node->name, 'with')) { 84 | return null; 85 | } 86 | 87 | if ($node->isFirstClassCallable()) { 88 | return null; 89 | } 90 | 91 | foreach ($node->getArgs() as $i => $argNode) { 92 | if (! $argNode->value instanceof MethodCall) { 93 | continue; 94 | } 95 | 96 | $methodCall = $argNode->value; 97 | if (! $this->isName($methodCall->name, 'equalTo')) { 98 | continue; 99 | } 100 | 101 | $node->args[$i] = $methodCall->getArgs()[0]; 102 | } 103 | 104 | return $node; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php: -------------------------------------------------------------------------------- 1 | getTypes() as $intersectionedType) { 23 | if (! $intersectionedType instanceof ObjectType) { 24 | continue; 25 | } 26 | 27 | if ($intersectionedType->getClassName() === ClassName::MOCK_OBJECT) { 28 | continue; 29 | } 30 | 31 | $classReflection = $intersectionedType->getClassReflection(); 32 | if (! $classReflection instanceof ClassReflection) { 33 | continue; 34 | } 35 | 36 | if (! $classReflection->hasNativeMethod($methodName)) { 37 | continue; 38 | } 39 | 40 | $mockedMethodReflection = $classReflection->getNativeMethod($methodName); 41 | 42 | $parameterTypes = $this->resolveParameterTypes($mockedMethodReflection); 43 | $returnType = $this->resolveReturnType($mockedMethodReflection); 44 | 45 | return new ParamTypesAndReturnType($parameterTypes, $returnType); 46 | } 47 | 48 | return null; 49 | } 50 | 51 | /** 52 | * @return Type[] 53 | */ 54 | private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodReflection): array 55 | { 56 | $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( 57 | $extendedMethodReflection->getVariants() 58 | ); 59 | 60 | $parameterTypes = []; 61 | foreach ($extendedParametersAcceptor->getParameters() as $parameterReflection) { 62 | $parameterTypes[] = $parameterReflection->getType(); 63 | } 64 | 65 | return $parameterTypes; 66 | } 67 | 68 | private function resolveReturnType(ExtendedMethodReflection $extendedMethodReflection): Type 69 | { 70 | $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( 71 | $extendedMethodReflection->getVariants() 72 | ); 73 | 74 | return $extendedParametersAcceptor->getReturnType(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rules/CodeQuality/ValueObject/MatchAndReturnMatch.php: -------------------------------------------------------------------------------- 1 | consecutiveMatch; 23 | } 24 | 25 | public function getConsecutiveMatchExpr(): Expr 26 | { 27 | $soleArm = $this->consecutiveMatch->arms[0]; 28 | if ($soleArm->body instanceof CallLike) { 29 | $assertCall = $soleArm->body; 30 | $firstArg = $assertCall->getArgs()[0]; 31 | return $firstArg->value; 32 | } 33 | 34 | throw new ShouldNotHappenException(); 35 | } 36 | 37 | public function getWillReturnMatch(): ?Match_ 38 | { 39 | return $this->willReturnMatch; 40 | } 41 | 42 | public function getWillReturnMatchExpr(): Expr 43 | { 44 | if (! $this->willReturnMatch instanceof Match_) { 45 | throw new ShouldNotHappenException(); 46 | } 47 | 48 | $soleArm = $this->willReturnMatch->arms[0]; 49 | return $soleArm->body; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rules/CodeQuality/ValueObject/ParamTypesAndReturnType.php: -------------------------------------------------------------------------------- 1 | paramTypes; 26 | } 27 | 28 | public function getReturnType(): ?Type 29 | { 30 | return $this->returnType; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rules/CodeQuality/ValueObject/VariableNameToType.php: -------------------------------------------------------------------------------- 1 | variableName; 18 | } 19 | 20 | public function getObjectType(): string 21 | { 22 | return $this->objectType; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rules/CodeQuality/ValueObject/VariableNameToTypeCollection.php: -------------------------------------------------------------------------------- 1 | variableNameToType as $variableNameToType) { 20 | if ($variableNameToType->getVariableName() !== $variableName) { 21 | continue; 22 | } 23 | 24 | return $variableNameToType; 25 | } 26 | 27 | return null; 28 | } 29 | 30 | public function remove(VariableNameToType $matchedNullableVariableNameToType): void 31 | { 32 | foreach ($this->variableNameToType as $key => $variableNamesToType) { 33 | if ($matchedNullableVariableNameToType !== $variableNamesToType) { 34 | continue; 35 | } 36 | 37 | unset($this->variableNameToType[$key]); 38 | break; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rules/PHPUnit100/NodeDecorator/WillReturnIfNodeDecorator.php: -------------------------------------------------------------------------------- 1 | stmts as $key => $stmt) { 21 | if (! $stmt instanceof If_) { 22 | continue; 23 | } 24 | 25 | $currentArg = $willReturnOnConsecutiveMethodCall->getArgs()[$key]; 26 | $stmt->stmts[] = new Return_($currentArg->value); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rules/PHPUnit100/NodeFactory/WillReturnCallbackFactory.php: -------------------------------------------------------------------------------- 1 | usedVariablesResolver->resolveUsedVariables($withConsecutiveMethodCall, $returnStmt); 42 | 43 | $closureStmts = $this->createParametersMatch($withConsecutiveMethodCall); 44 | if ($returnStmt instanceof Stmt) { 45 | $closureStmts[] = $returnStmt; 46 | } 47 | 48 | $parametersParam = new Param(new Variable(ConsecutiveVariable::PARAMETERS)); 49 | $parametersParam->variadic = true; 50 | 51 | return new Closure([ 52 | 'byRef' => $this->isByRef($referenceVariable), 53 | 'uses' => $this->createClosureUses($matcherVariable, $usedVariables), 54 | 'params' => [$parametersParam], 55 | 'stmts' => $closureStmts, 56 | ]); 57 | } 58 | 59 | /** 60 | * @return Stmt[] 61 | */ 62 | public function createParametersMatch(MethodCall $withConsecutiveMethodCall): array 63 | { 64 | $parametersVariable = new Variable(ConsecutiveVariable::PARAMETERS); 65 | 66 | $firstArg = $withConsecutiveMethodCall->getArgs()[0] ?? null; 67 | if ($firstArg instanceof Arg && $firstArg->unpack) { 68 | $assertSameMethodCall = $this->createAssertSameDimFetch($firstArg, $parametersVariable); 69 | return [new Expression($assertSameMethodCall)]; 70 | } 71 | 72 | $numberOfInvocationsMethodCall = $this->matcherInvocationCountMethodCallNodeFactory->create(); 73 | 74 | return $this->consecutiveIfsFactory->createIfs($withConsecutiveMethodCall, $numberOfInvocationsMethodCall); 75 | } 76 | 77 | private function createAssertSameDimFetch(Arg $firstArg, Variable $variable): MethodCall 78 | { 79 | $matcherCountMethodCall = $this->matcherInvocationCountMethodCallNodeFactory->create(); 80 | 81 | $currentValueArrayDimFetch = new ArrayDimFetch($firstArg->value, new Minus( 82 | $matcherCountMethodCall, 83 | new Int_(1) 84 | )); 85 | 86 | $compareArgs = [new Arg($currentValueArrayDimFetch), new Arg($variable)]; 87 | 88 | return $this->builderFactory->methodCall(new Variable('this'), 'assertSame', $compareArgs); 89 | } 90 | 91 | private function isByRef(Expr|Variable|null $referenceVariable): bool 92 | { 93 | return $referenceVariable instanceof Variable; 94 | } 95 | 96 | /** 97 | * @param Variable[] $usedVariables 98 | * @return ClosureUse[] 99 | */ 100 | private function createClosureUses(Variable $matcherVariable, array $usedVariables): array 101 | { 102 | $uses = [new ClosureUse($matcherVariable)]; 103 | 104 | foreach ($usedVariables as $usedVariable) { 105 | $uses[] = new ClosureUse($usedVariable); 106 | } 107 | 108 | return $uses; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/Class_/AddProphecyTraitRector.php: -------------------------------------------------------------------------------- 1 | prophesize()', 44 | [ 45 | new CodeSample( 46 | <<<'CODE_SAMPLE' 47 | use PHPUnit\Framework\TestCase; 48 | 49 | final class ExampleTest extends TestCase 50 | { 51 | public function testOne(): void 52 | { 53 | $prophecy = $this->prophesize(\AnInterface::class); 54 | } 55 | } 56 | CODE_SAMPLE 57 | , 58 | <<<'CODE_SAMPLE' 59 | use PHPUnit\Framework\TestCase; 60 | use Prophecy\PhpUnit\ProphecyTrait; 61 | 62 | final class ExampleTest extends TestCase 63 | { 64 | use ProphecyTrait; 65 | 66 | public function testOne(): void 67 | { 68 | $prophecy = $this->prophesize(\AnInterface::class); 69 | } 70 | } 71 | CODE_SAMPLE 72 | ), 73 | ] 74 | ); 75 | } 76 | 77 | /** 78 | * @return array> 79 | */ 80 | public function getNodeTypes(): array 81 | { 82 | return [Class_::class]; 83 | } 84 | 85 | /** 86 | * @param Class_ $node 87 | */ 88 | public function refactor(Node $node): ?Node 89 | { 90 | if ($this->shouldSkipClass($node)) { 91 | return null; 92 | } 93 | 94 | $traitUse = new TraitUse([new FullyQualified(self::PROPHECY_TRAIT)]); 95 | 96 | $node->stmts = array_merge([$traitUse], $node->stmts); 97 | 98 | return $node; 99 | } 100 | 101 | private function shouldSkipClass(Class_ $class): bool 102 | { 103 | $hasProphesizeMethodCall = (bool) $this->betterNodeFinder->findFirst( 104 | $class, 105 | fn (Node $node): bool => $this->testsNodeAnalyzer->isAssertMethodCallName($node, 'prophesize') 106 | ); 107 | 108 | if (! $hasProphesizeMethodCall) { 109 | return true; 110 | } 111 | 112 | $classReflection = $this->reflectionResolver->resolveClassReflection($class); 113 | if (! $classReflection instanceof ClassReflection) { 114 | return false; 115 | } 116 | 117 | return $classReflection->hasTraitUse(self::PROPHECY_TRAIT); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/Class_/ParentTestClassConstructorRector.php: -------------------------------------------------------------------------------- 1 | > 65 | */ 66 | public function getNodeTypes(): array 67 | { 68 | return [Class_::class]; 69 | } 70 | 71 | /** 72 | * @param Class_ $node 73 | */ 74 | public function refactor(Node $node): ?Node 75 | { 76 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 77 | return null; 78 | } 79 | 80 | if ($this->shouldSkipClass($node)) { 81 | return null; 82 | } 83 | 84 | // it already has a constructor, skip as it might require specific tweaking 85 | if ($node->getMethod(MethodName::CONSTRUCT)) { 86 | return null; 87 | } 88 | 89 | $constructorClassMethod = new ClassMethod(MethodName::CONSTRUCT); 90 | $constructorClassMethod->flags |= Modifiers::PUBLIC; 91 | $constructorClassMethod->stmts[] = new Expression($this->createParentConstructorCall()); 92 | 93 | $node->stmts = array_merge([$constructorClassMethod], $node->stmts); 94 | 95 | return $node; 96 | } 97 | 98 | private function createParentConstructorCall(): StaticCall 99 | { 100 | $staticClassConstFetch = new ClassConstFetch(new Name('static'), 'class'); 101 | 102 | return new StaticCall(new Name('parent'), MethodName::CONSTRUCT, [new Arg($staticClassConstFetch)]); 103 | } 104 | 105 | private function shouldSkipClass(Class_ $class): bool 106 | { 107 | if ($class->isAbstract()) { 108 | return true; 109 | } 110 | 111 | if ($class->isAnonymous()) { 112 | return true; 113 | } 114 | 115 | $className = $this->getName($class); 116 | 117 | // loaded automatically by PHPUnit 118 | if (str_ends_with((string) $className, 'Test')) { 119 | return true; 120 | } 121 | 122 | if (str_ends_with((string) $className, 'TestCase')) { 123 | return true; 124 | } 125 | 126 | return (bool) $class->getAttribute('hasRemovedFinalConstruct'); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/Class_/PublicDataProviderClassMethodRector.php: -------------------------------------------------------------------------------- 1 | > 77 | */ 78 | public function getNodeTypes(): array 79 | { 80 | return [Class_::class]; 81 | } 82 | 83 | /** 84 | * @param Class_ $node 85 | */ 86 | public function refactor(Node $node): ?Node 87 | { 88 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 89 | return null; 90 | } 91 | 92 | // 1. find all data providers 93 | $dataProviderClassMethods = $this->dataProviderClassMethodFinder->find($node); 94 | 95 | $hasChanged = false; 96 | 97 | foreach ($dataProviderClassMethods as $dataProviderClassMethod) { 98 | if ($this->skipMethod($dataProviderClassMethod)) { 99 | continue; 100 | } 101 | 102 | $this->visibilityManipulator->makePublic($dataProviderClassMethod); 103 | $hasChanged = true; 104 | } 105 | 106 | if ($hasChanged) { 107 | return $node; 108 | } 109 | 110 | return null; 111 | } 112 | 113 | private function skipMethod(ClassMethod $classMethod): bool 114 | { 115 | return $classMethod->isPublic(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/Class_/RemoveNamedArgsInDataProviderRector.php: -------------------------------------------------------------------------------- 1 | 100]; 52 | } 53 | } 54 | CODE_SAMPLE 55 | 56 | , 57 | <<<'CODE_SAMPLE' 58 | use PHPUnit\Framework\TestCase; 59 | 60 | final class SomeTest extends TestCase 61 | { 62 | /** 63 | * @dataProvider provideData() 64 | */ 65 | public function test() 66 | { 67 | } 68 | 69 | public static function provideData() 70 | { 71 | yield [100]; 72 | } 73 | } 74 | CODE_SAMPLE 75 | ), 76 | ]); 77 | } 78 | 79 | /** 80 | * @return array> 81 | */ 82 | public function getNodeTypes(): array 83 | { 84 | return [Class_::class]; 85 | } 86 | 87 | /** 88 | * @param Class_ $node 89 | */ 90 | public function refactor(Node $node): ?Node 91 | { 92 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 93 | return null; 94 | } 95 | 96 | $hasChanged = false; 97 | 98 | $dataProviders = $this->dataProviderClassMethodFinder->find($node); 99 | foreach ($dataProviders as $dataProvider) { 100 | /** @var Expression $stmt */ 101 | foreach ($dataProvider->getStmts() ?? [] as $stmt) { 102 | $expr = $stmt->expr; 103 | $arrayChanged = false; 104 | if ($expr instanceof Yield_) { 105 | $arrayChanged = $this->handleArray($expr->value); 106 | } elseif ($expr instanceof Array_) { 107 | $arrayChanged = $this->handleArray($expr); 108 | } 109 | 110 | if ($arrayChanged) { 111 | $hasChanged = true; 112 | } 113 | } 114 | } 115 | 116 | if ($hasChanged) { 117 | return $node; 118 | } 119 | 120 | return null; 121 | } 122 | 123 | private function handleArray(Array_ $array): bool 124 | { 125 | $hasChanged = false; 126 | foreach ($array->items as $item) { 127 | if (! $item instanceof ArrayItem) { 128 | continue; 129 | } 130 | 131 | if (! $item->key instanceof Expr) { 132 | continue; 133 | } 134 | 135 | if (! $item->key instanceof Int_ && $item->key instanceof Expr) { 136 | $item->key = null; 137 | $hasChanged = true; 138 | } 139 | } 140 | 141 | return $hasChanged; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/Class_/StaticDataProviderClassMethodRector.php: -------------------------------------------------------------------------------- 1 | > 80 | */ 81 | public function getNodeTypes(): array 82 | { 83 | return [Class_::class]; 84 | } 85 | 86 | /** 87 | * @param Class_ $node 88 | */ 89 | public function refactor(Node $node): ?Node 90 | { 91 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 92 | return null; 93 | } 94 | 95 | // 1. find all data providers 96 | $dataProviderClassMethods = $this->dataProviderClassMethodFinder->find($node); 97 | 98 | $hasChanged = false; 99 | 100 | foreach ($dataProviderClassMethods as $dataProviderClassMethod) { 101 | if ($this->skipMethod($dataProviderClassMethod)) { 102 | continue; 103 | } 104 | 105 | $this->visibilityManipulator->makeStatic($dataProviderClassMethod); 106 | $hasChanged = true; 107 | } 108 | 109 | if ($hasChanged) { 110 | return $node; 111 | } 112 | 113 | return null; 114 | } 115 | 116 | private function skipMethod(ClassMethod $classMethod): bool 117 | { 118 | if ($classMethod->isStatic()) { 119 | return true; 120 | } 121 | 122 | if ($classMethod->stmts === null) { 123 | return false; 124 | } 125 | 126 | return (bool) $this->betterNodeFinder->findFirst( 127 | $classMethod->stmts, 128 | fn (Node $node): bool => $node instanceof Variable && $this->isName($node, 'this') 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/MethodCall/PropertyExistsWithoutAssertRector.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | private const RENAME_METHODS_WITH_OBJECT_MAP = [ 30 | 'assertClassHasStaticAttribute' => 'assertTrue', 31 | 'classHasStaticAttribute' => 'assertTrue', 32 | 'assertClassNotHasStaticAttribute' => 'assertFalse', 33 | ]; 34 | 35 | public function __construct( 36 | private readonly IdentifierManipulator $identifierManipulator, 37 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer 38 | ) { 39 | } 40 | 41 | public function getRuleDefinition(): RuleDefinition 42 | { 43 | return new RuleDefinition( 44 | 'Replace deleted PHPUnit methods: assertClassHasStaticAttribute, classHasStaticAttribute and assertClassNotHasStaticAttribute by property_exists()', 45 | [ 46 | new CodeSample( 47 | <<<'CODE_SAMPLE' 48 | $this->assertClassHasStaticAttribute("Class", "property"); 49 | $this->classHasStaticAttribute("Class", "property"); 50 | $this->assertClassNotHasStaticAttribute("Class", "property"); 51 | CODE_SAMPLE 52 | , 53 | <<<'CODE_SAMPLE' 54 | $this->assertTrue(property_exists("Class", "property")); 55 | $this->assertTrue(property_exists("Class", "property")); 56 | $this->assertFalse(property_exists("Class", "property")); 57 | CODE_SAMPLE 58 | ), 59 | ] 60 | ); 61 | } 62 | 63 | /** 64 | * @return array> 65 | */ 66 | public function getNodeTypes(): array 67 | { 68 | return [MethodCall::class]; 69 | } 70 | 71 | /** 72 | * @param MethodCall $node 73 | */ 74 | public function refactor(Node $node): ?Node 75 | { 76 | $map = self::RENAME_METHODS_WITH_OBJECT_MAP; 77 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, array_keys($map))) { 78 | return null; 79 | } 80 | 81 | if ($node->isFirstClassCallable() || ! isset($node->getArgs()[0], $node->getArgs()[1])) { 82 | return null; 83 | } 84 | 85 | $firstNode = new Arg($node->getArgs()[0]->value); 86 | 87 | if ($node->getArgs()[1]->value instanceof ClassConstFetch) { 88 | $secondNode = $node->getArgs()[1]; 89 | } else { 90 | $secondNode = new Arg($node->getArgs()[1]->value); 91 | } 92 | 93 | $funcCall = new FuncCall(new Name('property_exists'), [$secondNode, $firstNode]); 94 | 95 | $newArgs = $this->nodeFactory->createArgs([$funcCall]); 96 | 97 | unset($node->args[0], $node->args[1]); 98 | $node->args = array_merge($newArgs, $node->getArgs()); 99 | 100 | $this->identifierManipulator->renameNodeWithMap($node, $map); 101 | 102 | return $node; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rules/PHPUnit100/Rector/StmtsAwareInterface/ExpectsMethodCallDecorator.php: -------------------------------------------------------------------------------- 1 | expects(...) 29 | * with 30 | * $expects = ... 31 | * 32 | * @param Expression $expression 33 | */ 34 | public function decorate(Expression $expression): MethodCall|StaticCall|null 35 | { 36 | /** @var MethodCall|StaticCall|null $expectsExactlyCall */ 37 | $expectsExactlyCall = null; 38 | 39 | $this->simpleCallableNodeTraverser->traverseNodesWithCallable($expression, function (Node $node) use ( 40 | &$expectsExactlyCall 41 | ): ?MethodCall { 42 | if (! $node instanceof MethodCall) { 43 | return null; 44 | } 45 | 46 | if (! $this->nodeNameResolver->isName($node->name, 'expects')) { 47 | return null; 48 | } 49 | 50 | if ($node->isFirstClassCallable()) { 51 | return null; 52 | } 53 | 54 | $firstArg = $node->getArgs()[0]; 55 | if (! $firstArg->value instanceof MethodCall && ! $firstArg->value instanceof StaticCall) { 56 | return null; 57 | } 58 | 59 | $expectsExactlyCall = $firstArg->value; 60 | 61 | $node->args = [new Arg(new Variable(ConsecutiveVariable::MATCHER))]; 62 | 63 | return $node; 64 | }); 65 | 66 | // add expects() method 67 | if (! $expectsExactlyCall instanceof Expr) { 68 | $this->simpleCallableNodeTraverser->traverseNodesWithCallable($expression, function (Node $node): ?int { 69 | if (! $node instanceof MethodCall) { 70 | return null; 71 | } 72 | 73 | if ($node->var instanceof MethodCall) { 74 | return null; 75 | } 76 | 77 | $node->var = new MethodCall($node->var, 'expects', [ 78 | new Arg(new Variable(ConsecutiveVariable::MATCHER)), 79 | ]); 80 | 81 | return NodeVisitor::STOP_TRAVERSAL; 82 | }); 83 | } 84 | 85 | return $expectsExactlyCall; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rules/PHPUnit120/Rector/Class_/RemoveOverrideFinalConstructTestCaseRector.php: -------------------------------------------------------------------------------- 1 | > 59 | */ 60 | public function getNodeTypes(): array 61 | { 62 | return [Class_::class]; 63 | } 64 | 65 | /** 66 | * @param Class_ $node 67 | */ 68 | public function refactor(Node $node): Node|null 69 | { 70 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 71 | return null; 72 | } 73 | 74 | $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); 75 | 76 | if ($constructClassMethod instanceof ClassMethod) { 77 | foreach ($node->stmts as $key => $stmt) { 78 | if ($stmt instanceof ClassMethod && $this->isName($stmt, MethodName::CONSTRUCT)) { 79 | unset($node->stmts[$key]); 80 | 81 | $node->setAttribute('hasRemovedFinalConstruct', true); 82 | return $node; 83 | } 84 | } 85 | } 86 | 87 | return null; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /rules/PHPUnit50/Rector/StaticCall/GetMockRector.php: -------------------------------------------------------------------------------- 1 | getMock("Class"); 44 | } 45 | } 46 | CODE_SAMPLE 47 | , 48 | <<<'CODE_SAMPLE' 49 | use PHPUnit\Framework\TestCase; 50 | 51 | final class SomeTest extends TestCase 52 | { 53 | public function test() 54 | { 55 | $classMock = $this->createMock("Class"); 56 | } 57 | } 58 | CODE_SAMPLE 59 | ), ]); 60 | } 61 | 62 | /** 63 | * @return array> 64 | */ 65 | public function getNodeTypes(): array 66 | { 67 | return [MethodCall::class, StaticCall::class]; 68 | } 69 | 70 | /** 71 | * @param MethodCall|StaticCall $node 72 | */ 73 | public function refactor(Node $node): ?Node 74 | { 75 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames( 76 | $node, 77 | ['getMock', 'getMockWithoutInvokingTheOriginalConstructor'] 78 | )) { 79 | return null; 80 | } 81 | 82 | if ($node instanceof MethodCall && $node->var instanceof MethodCall) { 83 | return null; 84 | } 85 | 86 | $classReflection = $this->reflectionResolver->resolveClassReflectionSourceObject($node); 87 | if ($classReflection instanceof ClassReflection && $classReflection->getName() !== 'PHPUnit\Framework\TestCase') { 88 | return null; 89 | } 90 | 91 | if ($node->isFirstClassCallable()) { 92 | return null; 93 | } 94 | 95 | // narrow args to one 96 | if (count($node->args) > 1) { 97 | $node->args = [$node->getArgs()[0]]; 98 | } 99 | 100 | $node->name = new Identifier('createMock'); 101 | 102 | return $node; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rules/PHPUnit60/Rector/ClassMethod/ExceptionAnnotationRector.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | private const ANNOTATION_TO_METHOD = [ 33 | 'expectedExceptionMessageRegExp' => 'expectExceptionMessageRegExp', 34 | 'expectedExceptionMessage' => 'expectExceptionMessage', 35 | 'expectedExceptionCode' => 'expectExceptionCode', 36 | 'expectedException' => 'expectException', 37 | ]; 38 | 39 | public function __construct( 40 | private readonly ExpectExceptionMethodCallFactory $expectExceptionMethodCallFactory, 41 | private readonly PhpDocTagRemover $phpDocTagRemover, 42 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer, 43 | private readonly DocBlockUpdater $docBlockUpdater, 44 | private readonly PhpDocInfoFactory $phpDocInfoFactory, 45 | ) { 46 | } 47 | 48 | public function getRuleDefinition(): RuleDefinition 49 | { 50 | return new RuleDefinition( 51 | 'Changes `@expectedException annotations to `expectException*()` methods', 52 | [ 53 | new CodeSample( 54 | <<<'CODE_SAMPLE' 55 | /** 56 | * @expectedException Exception 57 | * @expectedExceptionMessage Message 58 | */ 59 | public function test() 60 | { 61 | // tested code 62 | } 63 | CODE_SAMPLE 64 | , 65 | <<<'CODE_SAMPLE' 66 | public function test() 67 | { 68 | $this->expectException('Exception'); 69 | $this->expectExceptionMessage('Message'); 70 | // tested code 71 | } 72 | CODE_SAMPLE 73 | ), 74 | ] 75 | ); 76 | } 77 | 78 | /** 79 | * @return array> 80 | */ 81 | public function getNodeTypes(): array 82 | { 83 | return [ClassMethod::class]; 84 | } 85 | 86 | /** 87 | * @param ClassMethod $node 88 | */ 89 | public function refactor(Node $node): ?Node 90 | { 91 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 92 | return null; 93 | } 94 | 95 | $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); 96 | if (! $phpDocInfo instanceof PhpDocInfo) { 97 | return null; 98 | } 99 | 100 | $hasChanged = false; 101 | 102 | foreach (self::ANNOTATION_TO_METHOD as $annotationName => $methodName) { 103 | if (! $phpDocInfo->hasByName($annotationName)) { 104 | continue; 105 | } 106 | 107 | $methodCallExpressions = $this->expectExceptionMethodCallFactory->createFromTagValueNodes( 108 | $phpDocInfo->getTagsByName($annotationName), 109 | $methodName, 110 | ); 111 | $node->stmts = [...$methodCallExpressions, ...(array) $node->stmts]; 112 | 113 | $this->phpDocTagRemover->removeByName($phpDocInfo, $annotationName); 114 | $hasChanged = true; 115 | } 116 | 117 | if (! $hasChanged) { 118 | return null; 119 | } 120 | 121 | $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); 122 | 123 | return $node; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /rules/PHPUnit60/Rector/MethodCall/GetMockBuilderGetMockToCreateMockRector.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('SomeClass') 44 | ->disableOriginalConstructor() 45 | ->getMock(); 46 | } 47 | } 48 | CODE_SAMPLE 49 | , 50 | <<<'CODE_SAMPLE' 51 | use PHPUnit\Framework\TestCase; 52 | 53 | final class SomeTest extends TestCase 54 | { 55 | public function test() 56 | { 57 | $applicationMock = $this->createMock('SomeClass'); 58 | } 59 | } 60 | CODE_SAMPLE 61 | ), 62 | ]); 63 | } 64 | 65 | /** 66 | * @return array> 67 | */ 68 | public function getNodeTypes(): array 69 | { 70 | return [MethodCall::class]; 71 | } 72 | 73 | /** 74 | * @param MethodCall $node 75 | */ 76 | public function refactor(Node $node): ?Node 77 | { 78 | if (! $this->isName($node->name, 'getMock')) { 79 | return null; 80 | } 81 | 82 | if (! $node->var instanceof MethodCall) { 83 | return null; 84 | } 85 | 86 | // traverse up over useless methods until we reach the top one 87 | $currentMethodCall = $node->var; 88 | 89 | while ($currentMethodCall instanceof MethodCall && $this->isNames( 90 | $currentMethodCall->name, 91 | self::USELESS_METHOD_NAMES 92 | )) { 93 | $currentMethodCall = $currentMethodCall->var; 94 | } 95 | 96 | if (! $currentMethodCall instanceof MethodCall) { 97 | return null; 98 | } 99 | 100 | // can be only local call, as createMock() is protected method 101 | if (! $this->isLocalScopeCaller($currentMethodCall)) { 102 | return null; 103 | } 104 | 105 | // must be be test case class 106 | if (! $this->isObjectType($currentMethodCall->var, new ObjectType('PHPUnit\Framework\TestCase'))) { 107 | return null; 108 | } 109 | 110 | if (! $this->isName($currentMethodCall->name, 'getMockBuilder')) { 111 | return null; 112 | } 113 | 114 | $args = $currentMethodCall->args; 115 | $thisVariable = $currentMethodCall->var; 116 | 117 | return new MethodCall($thisVariable, 'createMock', $args); 118 | } 119 | 120 | private function isLocalScopeCaller(MethodCall $currentMethodCall): bool 121 | { 122 | if (! $currentMethodCall->var instanceof Variable) { 123 | return false; 124 | } 125 | 126 | return $currentMethodCall->var->name === 'this'; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /rules/PHPUnit70/Rector/Class_/RemoveDataProviderTestPrefixRector.php: -------------------------------------------------------------------------------- 1 | > 80 | */ 81 | public function getNodeTypes(): array 82 | { 83 | return [Class_::class]; 84 | } 85 | 86 | /** 87 | * @param Class_ $node 88 | */ 89 | public function refactor(Node $node): ?Node 90 | { 91 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 92 | return null; 93 | } 94 | 95 | $hasChanged = false; 96 | 97 | $dataProviderClassMethods = $this->dataProviderClassMethodFinder->find($node); 98 | foreach ($dataProviderClassMethods as $dataProviderClassMethod) { 99 | $dataProviderClassMethodName = $dataProviderClassMethod->name->toString(); 100 | 101 | if (! str_starts_with($dataProviderClassMethodName, 'test')) { 102 | continue; 103 | } 104 | 105 | $shortMethodName = Strings::substring($dataProviderClassMethodName, 4); 106 | $shortMethodName = lcfirst($shortMethodName); 107 | 108 | $dataProviderClassMethod->name = new Identifier($shortMethodName); 109 | $hasChanged = true; 110 | } 111 | 112 | $this->dataProviderMethodRenamer->removeTestPrefix($node); 113 | 114 | if ($hasChanged) { 115 | return $node; 116 | } 117 | 118 | return null; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /rules/PHPUnit80/Rector/MethodCall/SpecificAssertContainsRector.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | private const OLD_TO_NEW_METHOD_NAMES = [ 30 | 'assertContains' => 'assertStringContainsString', 31 | 'assertNotContains' => 'assertStringNotContainsString', 32 | ]; 33 | 34 | public function __construct( 35 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer 36 | ) { 37 | } 38 | 39 | public function getRuleDefinition(): RuleDefinition 40 | { 41 | return new RuleDefinition( 42 | 'Change assertContains()/assertNotContains() method to new string and iterable alternatives', 43 | [ 44 | new CodeSample( 45 | <<<'CODE_SAMPLE' 46 | final class SomeTest extends \PHPUnit\Framework\TestCase 47 | { 48 | public function test() 49 | { 50 | $this->assertContains('foo', 'foo bar'); 51 | $this->assertNotContains('foo', 'foo bar'); 52 | } 53 | } 54 | CODE_SAMPLE 55 | , 56 | <<<'CODE_SAMPLE' 57 | final class SomeTest extends \PHPUnit\Framework\TestCase 58 | { 59 | public function test() 60 | { 61 | $this->assertStringContainsString('foo', 'foo bar'); 62 | $this->assertStringNotContainsString('foo', 'foo bar'); 63 | } 64 | } 65 | CODE_SAMPLE 66 | ), 67 | ] 68 | ); 69 | } 70 | 71 | /** 72 | * @return array> 73 | */ 74 | public function getNodeTypes(): array 75 | { 76 | return [MethodCall::class, StaticCall::class]; 77 | } 78 | 79 | /** 80 | * @param MethodCall|StaticCall $node 81 | */ 82 | public function refactor(Node $node): ?Node 83 | { 84 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, ['assertContains', 'assertNotContains'])) { 85 | return null; 86 | } 87 | 88 | if ($node->isFirstClassCallable()) { 89 | return null; 90 | } 91 | 92 | if (! $this->isPossiblyStringType($node->getArgs()[1]->value)) { 93 | return null; 94 | } 95 | 96 | $methodName = $this->getName($node->name); 97 | $newMethodName = self::OLD_TO_NEW_METHOD_NAMES[$methodName]; 98 | $node->name = new Identifier($newMethodName); 99 | 100 | return $node; 101 | } 102 | 103 | private function isPossiblyStringType(Expr $expr): bool 104 | { 105 | $exprType = $this->getType($expr); 106 | 107 | if ($exprType instanceof UnionType) { 108 | foreach ($exprType->getTypes() as $unionedType) { 109 | if ($unionedType instanceof StringType) { 110 | return true; 111 | } 112 | } 113 | } 114 | 115 | return $exprType instanceof StringType; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /rules/PHPUnit80/Rector/MethodCall/SpecificAssertInternalTypeRector.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | private const TYPE_TO_METHOD = [ 29 | 'array' => ['assertIsArray', 'assertIsNotArray'], 30 | 'bool' => ['assertIsBool', 'assertIsNotBool'], 31 | 'boolean' => ['assertIsBool', 'assertIsNotBool'], 32 | 'float' => ['assertIsFloat', 'assertIsNotFloat'], 33 | 'int' => ['assertIsInt', 'assertIsNotInt'], 34 | 'integer' => ['assertIsInt', 'assertIsNotInt'], 35 | 'numeric' => ['assertIsNumeric', 'assertIsNotNumeric'], 36 | 'object' => ['assertIsObject', 'assertIsNotObject'], 37 | 'resource' => ['assertIsResource', 'assertIsNotResource'], 38 | 'string' => ['assertIsString', 'assertIsNotString'], 39 | 'scalar' => ['assertIsScalar', 'assertIsNotScalar'], 40 | 'callable' => ['assertIsCallable', 'assertIsNotCallable'], 41 | 'iterable' => ['assertIsIterable', 'assertIsNotIterable'], 42 | 'null' => ['assertNull', 'assertNotNull'], 43 | ]; 44 | 45 | public function __construct( 46 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer 47 | ) { 48 | } 49 | 50 | public function getRuleDefinition(): RuleDefinition 51 | { 52 | return new RuleDefinition( 53 | 'Change assertInternalType()/assertNotInternalType() method to new specific alternatives', 54 | [ 55 | new CodeSample( 56 | <<<'CODE_SAMPLE' 57 | final class SomeTest extends \PHPUnit\Framework\TestCase 58 | { 59 | public function test() 60 | { 61 | $value = 'value'; 62 | $this->assertInternalType('string', $value); 63 | $this->assertNotInternalType('array', $value); 64 | } 65 | } 66 | CODE_SAMPLE 67 | , 68 | <<<'CODE_SAMPLE' 69 | final class SomeTest extends \PHPUnit\Framework\TestCase 70 | { 71 | public function test() 72 | { 73 | $value = 'value'; 74 | $this->assertIsString($value); 75 | $this->assertIsNotArray($value); 76 | } 77 | } 78 | CODE_SAMPLE 79 | ), 80 | ] 81 | ); 82 | } 83 | 84 | /** 85 | * @return array> 86 | */ 87 | public function getNodeTypes(): array 88 | { 89 | return [MethodCall::class, StaticCall::class]; 90 | } 91 | 92 | /** 93 | * @param MethodCall|StaticCall $node 94 | */ 95 | public function refactor(Node $node): ?Node 96 | { 97 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames( 98 | $node, 99 | ['assertInternalType', 'assertNotInternalType'] 100 | )) { 101 | return null; 102 | } 103 | 104 | if ($node->isFirstClassCallable()) { 105 | return null; 106 | } 107 | 108 | $typeNode = $node->getArgs()[0] 109 | ->value; 110 | if (! $typeNode instanceof String_) { 111 | return null; 112 | } 113 | 114 | $type = $typeNode->value; 115 | if (! isset(self::TYPE_TO_METHOD[$type])) { 116 | return null; 117 | } 118 | 119 | array_shift($node->args); 120 | 121 | $position = $this->isName($node->name, 'assertInternalType') ? 0 : 1; 122 | $methodName = self::TYPE_TO_METHOD[$type][$position]; 123 | 124 | $node->name = new Identifier($methodName); 125 | 126 | return $node; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /rules/PHPUnit90/Rector/MethodCall/ReplaceAtMethodWithDesiredMatcherRector.php: -------------------------------------------------------------------------------- 1 | expects($this->at(0)) 37 | ->method('foo') 38 | ->willReturn('1'); 39 | CODE_SAMPLE 40 | , 41 | <<<'CODE_SAMPLE' 42 | $mock->expects($this->never()) 43 | ->method('foo') 44 | ->willReturn('1'); 45 | CODE_SAMPLE 46 | ), 47 | ] 48 | ); 49 | } 50 | 51 | /** 52 | * @return array> 53 | */ 54 | public function getNodeTypes(): array 55 | { 56 | return [MethodCall::class]; 57 | } 58 | 59 | /** 60 | * @param MethodCall $node 61 | */ 62 | public function refactor(Node $node): null|MethodCall 63 | { 64 | $this->hasChanged = false; 65 | 66 | if (! $this->testsNodeAnalyzer->isInTestClass($node)) { 67 | return null; 68 | } 69 | 70 | if ($node->var instanceof MethodCall && $arg = $this->findAtMethodCall($node->var)) { 71 | $this->replaceWithDesiredMatcher($arg); 72 | } 73 | 74 | if ($this->hasChanged) { 75 | return $node; 76 | } 77 | 78 | return null; 79 | } 80 | 81 | private function findAtMethodCall(MethodCall $methodCall): ?Arg 82 | { 83 | foreach ($methodCall->getArgs() as $arg) { 84 | if ($arg->value instanceof MethodCall && 85 | $arg->value->name instanceof Identifier && 86 | $arg->value->name->toString() === 'at' 87 | ) { 88 | return $arg; 89 | } 90 | } 91 | 92 | if ($methodCall->var instanceof MethodCall) { 93 | $this->findAtMethodCall($methodCall->var); 94 | } 95 | 96 | return null; 97 | } 98 | 99 | private function replaceWithDesiredMatcher(Arg $arg): void 100 | { 101 | if (! $arg->value instanceof MethodCall) { 102 | return; 103 | } 104 | 105 | foreach ($arg->value->getArgs() as $item) { 106 | if ($item->value instanceof Int_) { 107 | $count = $item->value->value; 108 | } 109 | } 110 | 111 | if (! isset($count)) { 112 | return; 113 | } 114 | 115 | if ($count === 0) { 116 | $arg->value = new MethodCall($arg->value->var, 'never'); 117 | $this->hasChanged = true; 118 | } elseif ($count === 1) { 119 | $arg->value = new MethodCall($arg->value->var, 'once'); 120 | $this->hasChanged = true; 121 | } elseif ($count > 1) { 122 | $arg->value = new MethodCall($arg->value->var, 'exactly', [new Arg(new Int_($count))]); 123 | $this->hasChanged = true; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /rules/PHPUnit90/Rector/MethodCall/SpecificAssertContainsWithoutIdentityRector.php: -------------------------------------------------------------------------------- 1 | > 26 | */ 27 | private const OLD_METHODS_NAMES_TO_NEW_NAMES = [ 28 | 'string' => [ 29 | 'assertContains' => 'assertContainsEquals', 30 | 'assertNotContains' => 'assertNotContainsEquals', 31 | ], 32 | ]; 33 | 34 | public function __construct( 35 | private readonly TestsNodeAnalyzer $testsNodeAnalyzer, 36 | private readonly ValueResolver $valueResolver 37 | ) { 38 | } 39 | 40 | public function getRuleDefinition(): RuleDefinition 41 | { 42 | return new RuleDefinition( 43 | 'Change assertContains()/assertNotContains() with non-strict comparison to new specific alternatives', 44 | [ 45 | new CodeSample( 46 | <<<'CODE_SAMPLE' 47 | final class SomeTest extends \PHPUnit\Framework\TestCase 48 | { 49 | public function test() 50 | { 51 | $objects = [ new \stdClass(), new \stdClass(), new \stdClass() ]; 52 | $this->assertContains(new \stdClass(), $objects, 'message', false, false); 53 | $this->assertNotContains(new \stdClass(), $objects, 'message', false, false); 54 | } 55 | } 56 | CODE_SAMPLE 57 | , 58 | <<<'CODE_SAMPLE' 59 | final class SomeTest extends TestCase 60 | { 61 | public function test() 62 | { 63 | $objects = [ new \stdClass(), new \stdClass(), new \stdClass() ]; 64 | $this->assertContainsEquals(new \stdClass(), $objects, 'message'); 65 | $this->assertNotContainsEquals(new \stdClass(), $objects, 'message'); 66 | } 67 | } 68 | CODE_SAMPLE 69 | ), 70 | ] 71 | ); 72 | } 73 | 74 | /** 75 | * @return array> 76 | */ 77 | public function getNodeTypes(): array 78 | { 79 | return [MethodCall::class, StaticCall::class]; 80 | } 81 | 82 | /** 83 | * @param MethodCall|StaticCall $node 84 | */ 85 | public function refactor(Node $node): ?Node 86 | { 87 | if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames($node, ['assertContains', 'assertNotContains'])) { 88 | return null; 89 | } 90 | 91 | if ($node->isFirstClassCallable()) { 92 | return null; 93 | } 94 | 95 | if (count($node->getArgs()) < 2) { 96 | return null; 97 | } 98 | 99 | // when second argument is string: do nothing 100 | $secondArgType = $this->getType($node->getArgs()[1]->value); 101 | if ($secondArgType instanceof StringType) { 102 | return null; 103 | } 104 | 105 | //when less then 5 arguments given: do nothing 106 | if (! isset($node->getArgs()[4])) { 107 | return null; 108 | } 109 | 110 | $fourthArg = $node->getArgs()[4]; 111 | 112 | //when 5th argument check identity is true: do nothing 113 | if ($this->valueResolver->isValue($fourthArg->value, true)) { 114 | return null; 115 | } 116 | 117 | /* here we search for element of array without identity check and we can replace functions */ 118 | $methodName = $this->getName($node->name); 119 | 120 | $node->name = new Identifier(self::OLD_METHODS_NAMES_TO_NEW_NAMES['string'][$methodName]); 121 | unset($node->args[3], $node->args[4]); 122 | 123 | return $node; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Composer/ProjectPackageVersionResolver.php: -------------------------------------------------------------------------------- 1 | installedPackageResolver = new InstalledPackageResolver(getcwd()); 22 | } 23 | 24 | public function findPackageVersion(string $packageName): ?string 25 | { 26 | $rootProjectInstalledPackages = $this->installedPackageResolver->resolve(); 27 | 28 | foreach ($rootProjectInstalledPackages as $rootProjectInstalledPackage) { 29 | if ($rootProjectInstalledPackage->getName() === $packageName) { 30 | return $rootProjectInstalledPackage->getVersion(); 31 | } 32 | } 33 | 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Enum/AssertMethod.php: -------------------------------------------------------------------------------- 1 | simpleCallableNodeTraverser->traverseNodesWithCallable($expression, function (Node $node) use ( 24 | $methodName 25 | ): ?Node { 26 | if (! $node instanceof MethodCall) { 27 | return null; 28 | } 29 | 30 | if (! $this->nodeNameResolver->isName($node->name, $methodName)) { 31 | return null; 32 | } 33 | 34 | return $node->var; 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Naming/TestClassNameResolver.php: -------------------------------------------------------------------------------- 1 | isFirstClassCallable()) { 15 | return; 16 | } 17 | 18 | $methodArguments = $node->getArgs(); 19 | array_shift($methodArguments); 20 | 21 | $node->args = $methodArguments; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/NodeAnalyzer/IdentifierManipulator.php: -------------------------------------------------------------------------------- 1 | $renameMethodMap 30 | */ 31 | public function renameNodeWithMap( 32 | ClassConstFetch | MethodCall | PropertyFetch | StaticCall | ClassMethod $node, 33 | array $renameMethodMap 34 | ): bool { 35 | $oldNodeMethodName = $this->resolveOldMethodName($node); 36 | if (! is_string($oldNodeMethodName)) { 37 | return false; 38 | } 39 | 40 | $node->name = new Identifier($renameMethodMap[$oldNodeMethodName]); 41 | return true; 42 | } 43 | 44 | private function resolveOldMethodName( 45 | ClassConstFetch | MethodCall | PropertyFetch | StaticCall | ClassMethod $node 46 | ): ?string { 47 | if ($node instanceof StaticCall || $node instanceof MethodCall) { 48 | return $this->nodeNameResolver->getName($node->name); 49 | } 50 | 51 | return $this->nodeNameResolver->getName($node); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/NodeAnalyzer/MockedVariableAnalyzer.php: -------------------------------------------------------------------------------- 1 | simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use ( 34 | &$doesContainMock 35 | ): null { 36 | if ($this->isMockeryStaticCall($node)) { 37 | $doesContainMock = true; 38 | return null; 39 | } 40 | 41 | if (! $node instanceof PropertyFetch && ! $node instanceof Variable) { 42 | return null; 43 | } 44 | 45 | $variableType = $this->nodeTypeResolver->getType($node); 46 | if ($variableType instanceof MixedType) { 47 | return null; 48 | } 49 | 50 | if ($this->isIntersectionTypeWithMockObject($variableType)) { 51 | $doesContainMock = true; 52 | } 53 | 54 | if ($variableType->isSuperTypeOf(new ObjectType('PHPUnit\Framework\MockObject\MockObject'))->yes()) { 55 | $doesContainMock = true; 56 | } 57 | 58 | return null; 59 | }); 60 | 61 | return $doesContainMock; 62 | } 63 | 64 | private function isIntersectionTypeWithMockObject(Type $variableType): bool 65 | { 66 | if ($variableType instanceof IntersectionType) { 67 | foreach ($variableType->getTypes() as $variableTypeType) { 68 | if ($variableTypeType->isSuperTypeOf( 69 | new ObjectType('PHPUnit\Framework\MockObject\MockObject') 70 | )->yes()) { 71 | return true; 72 | } 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | 79 | private function isMockeryStaticCall(Node $node): bool 80 | { 81 | if (! $node instanceof StaticCall) { 82 | return false; 83 | } 84 | 85 | // is mockery mock 86 | if (! $this->nodeNameResolver->isName($node->class, 'Mockery')) { 87 | return false; 88 | } 89 | 90 | return $this->nodeNameResolver->isName($node->name, 'mock'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/NodeAnalyzer/SetUpMethodDecorator.php: -------------------------------------------------------------------------------- 1 | astResolver->resolveClassMethod('PHPUnit\Framework\TestCase', MethodName::SET_UP); 31 | if (! $setUpClassMethod instanceof ClassMethod) { 32 | return; 33 | } 34 | 35 | if ($setUpClassMethod->returnType instanceof Identifier) { 36 | $classMethod->returnType = new Identifier($setUpClassMethod->returnType->toString()); 37 | return; 38 | } 39 | 40 | $classMethod->returnType = null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/NodeAnalyzer/TestsNodeAnalyzer.php: -------------------------------------------------------------------------------- 1 | reflectionResolver->resolveClassReflection($node); 37 | 38 | if (! $classReflection instanceof ClassReflection) { 39 | return false; 40 | } 41 | 42 | foreach (self::TEST_CASE_OBJECT_CLASSES as $testCaseObjectClass) { 43 | if ($classReflection->is($testCaseObjectClass)) { 44 | return true; 45 | } 46 | } 47 | 48 | return false; 49 | } 50 | 51 | public function isTestClassMethod(ClassMethod $classMethod): bool 52 | { 53 | if (! $classMethod->isPublic()) { 54 | return false; 55 | } 56 | 57 | if (str_starts_with($classMethod->name->toString(), 'test')) { 58 | return true; 59 | } 60 | 61 | foreach ($classMethod->getAttrGroups() as $attributeGroup) { 62 | foreach ($attributeGroup->attrs as $attribute) { 63 | if ($attribute->name->toString() === 'PHPUnit\\Framework\\Attributes\\Test') { 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); 70 | return $phpDocInfo->hasByName('test'); 71 | } 72 | 73 | public function isAssertMethodCallName(Node $node, string $name): bool 74 | { 75 | if ($node instanceof StaticCall) { 76 | $callerType = $this->nodeTypeResolver->getType($node->class); 77 | } elseif ($node instanceof MethodCall) { 78 | $callerType = $this->nodeTypeResolver->getType($node->var); 79 | } else { 80 | return false; 81 | } 82 | 83 | $assertObjectType = new ObjectType('PHPUnit\Framework\Assert'); 84 | if (! $assertObjectType->isSuperTypeOf($callerType) 85 | ->yes()) { 86 | return false; 87 | } 88 | 89 | /** @var StaticCall|MethodCall $node */ 90 | return $this->nodeNameResolver->isName($node->name, $name); 91 | } 92 | 93 | /** 94 | * @param string[] $names 95 | */ 96 | public function isPHPUnitMethodCallNames(Node $node, array $names): bool 97 | { 98 | if (! $this->isPHPUnitTestCaseCall($node)) { 99 | return false; 100 | } 101 | 102 | /** @var MethodCall|StaticCall $node */ 103 | return $this->nodeNameResolver->isNames($node->name, $names); 104 | } 105 | 106 | public function isPHPUnitTestCaseCall(Node $node): bool 107 | { 108 | if ($node instanceof MethodCall) { 109 | return $this->isInTestClass($node); 110 | } 111 | 112 | if ($node instanceof StaticCall) { 113 | $classType = $this->nodeTypeResolver->getType($node->class); 114 | if ($classType instanceof ObjectType) { 115 | if ($classType->isInstanceOf(PHPUnitClassName::TEST_CASE)->yes()) { 116 | return true; 117 | } 118 | 119 | if ($classType->isInstanceOf(PHPUnitClassName::ASSERT)->yes()) { 120 | return true; 121 | } 122 | } 123 | } 124 | 125 | return false; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/NodeFactory/AssertCallFactory.php: -------------------------------------------------------------------------------- 1 | var, $name); 16 | } 17 | 18 | return new StaticCall($node->class, $name); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/NodeFactory/ExpectExceptionMethodCallFactory.php: -------------------------------------------------------------------------------- 1 | createMethodCall($phpDocTagNode, $methodName); 35 | $methodCallExpressions[] = new Expression($methodCall); 36 | } 37 | 38 | return $methodCallExpressions; 39 | } 40 | 41 | private function createMethodCall(PhpDocTagNode $phpDocTagNode, string $methodName): MethodCall 42 | { 43 | if (! $phpDocTagNode->value instanceof GenericTagValueNode) { 44 | throw new ShouldNotHappenException(); 45 | } 46 | 47 | $expr = $this->createExpectedExpr($phpDocTagNode, $phpDocTagNode->value); 48 | return $this->nodeFactory->createMethodCall('this', $methodName, [new Arg($expr)]); 49 | } 50 | 51 | private function createExpectedExpr(PhpDocTagNode $phpDocTagNode, GenericTagValueNode $genericTagValueNode): Expr 52 | { 53 | if ($phpDocTagNode->name === '@expectedExceptionMessage') { 54 | return new String_($genericTagValueNode->value); 55 | } 56 | 57 | return $this->phpDocValueToNodeMapper->mapGenericTagValueNode($genericTagValueNode); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/NodeFactory/MatcherInvocationCountMethodCallNodeFactory.php: -------------------------------------------------------------------------------- 1 | getInvocationMethodName(); 23 | 24 | $matcherVariable = new Variable(ConsecutiveVariable::MATCHER); 25 | 26 | return new MethodCall($matcherVariable, new Identifier($invocationMethodName)); 27 | } 28 | 29 | private function getInvocationMethodName(): string 30 | { 31 | $projectPHPUnitVersion = $this->projectPackageVersionResolver->findPackageVersion('phpunit/phpunit'); 32 | 33 | if ($projectPHPUnitVersion === null || version_compare($projectPHPUnitVersion, '10.0', '>=')) { 34 | // phpunit 10 naming 35 | return 'numberOfInvocations'; 36 | } 37 | 38 | // phpunit 9 naming 39 | return 'getInvocationCount'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/NodeFactory/UsedVariablesResolver.php: -------------------------------------------------------------------------------- 1 | getArgs(); 29 | 30 | $stmtVariables = $returnStmt instanceof Stmt ? $this->resolveUniqueVariables([$returnStmt]) : []; 31 | 32 | return $this->resolveUniqueVariables(array_merge($consecutiveArgs, $stmtVariables)); 33 | } 34 | 35 | /** 36 | * @param Node[] $nodes 37 | * @return Variable[] 38 | */ 39 | private function resolveUniqueVariables(array $nodes): array 40 | { 41 | /** @var Variable[] $usedVariables */ 42 | $usedVariables = $this->betterNodeFinder->findInstancesOfScoped($nodes, Variable::class); 43 | 44 | $uniqueUsedVariables = []; 45 | 46 | foreach ($usedVariables as $usedVariable) { 47 | if ($this->nodeNameResolver->isNames( 48 | $usedVariable, 49 | ['this', ConsecutiveVariable::MATCHER, ConsecutiveVariable::PARAMETERS] 50 | )) { 51 | continue; 52 | } 53 | 54 | $usedVariableName = $this->nodeNameResolver->getName($usedVariable); 55 | $uniqueUsedVariables[$usedVariableName] = $usedVariable; 56 | } 57 | 58 | return $uniqueUsedVariables; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/NodeFinder/MethodCallNodeFinder.php: -------------------------------------------------------------------------------- 1 | betterNodeFinder->find($expression, function (Node $node) use ( 27 | $methodNames 28 | ): bool { 29 | if (! $node instanceof MethodCall) { 30 | return false; 31 | } 32 | 33 | return $this->nodeNameResolver->isNames($node->name, $methodNames); 34 | }); 35 | 36 | return $desiredMethodCalls !== []; 37 | } 38 | 39 | public function findByName(Expression $expression, string $methodName): ?MethodCall 40 | { 41 | if (! $expression->expr instanceof MethodCall) { 42 | return null; 43 | } 44 | 45 | /** @var MethodCall|null $methodCall */ 46 | $methodCall = $this->betterNodeFinder->findFirst($expression->expr, function (Node $node) use ( 47 | $methodName 48 | ): bool { 49 | if (! $node instanceof MethodCall) { 50 | return false; 51 | } 52 | 53 | return $this->nodeNameResolver->isName($node->name, $methodName); 54 | }); 55 | 56 | return $methodCall; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/PhpDoc/DataProviderMethodRenamer.php: -------------------------------------------------------------------------------- 1 | getMethods() as $classMethod) { 25 | $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); 26 | 27 | $hasClassMethodChanged = false; 28 | 29 | foreach ($phpDocInfo->getTagsByName('dataProvider') as $phpDocTagNode) { 30 | if (! $phpDocTagNode->value instanceof GenericTagValueNode) { 31 | continue; 32 | } 33 | 34 | $oldMethodName = $phpDocTagNode->value->value; 35 | if (! \str_starts_with($oldMethodName, 'test')) { 36 | continue; 37 | } 38 | 39 | $newMethodName = $this->createMethodNameWithoutPrefix($oldMethodName, 'test'); 40 | $phpDocTagNode->value->value = Strings::replace( 41 | $oldMethodName, 42 | '#' . preg_quote($oldMethodName, '#') . '#', 43 | $newMethodName 44 | ); 45 | 46 | // invoke reprint 47 | $phpDocTagNode->setAttribute(PhpDocAttributeKey::START_AND_END, null); 48 | $hasClassMethodChanged = true; 49 | } 50 | 51 | if ($hasClassMethodChanged) { 52 | $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod); 53 | } 54 | } 55 | } 56 | 57 | private function createMethodNameWithoutPrefix(string $methodName, string $prefix): string 58 | { 59 | $newMethodName = Strings::substring($methodName, strlen($prefix)); 60 | return lcfirst($newMethodName); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/PhpDoc/PhpDocValueToNodeMapper.php: -------------------------------------------------------------------------------- 1 | value, '::')) { 25 | [$class, $constant] = explode('::', $genericTagValueNode->value); 26 | 27 | $name = new Name($class); 28 | return $this->nodeFactory->createClassConstFetchFromName($name, $constant); 29 | } 30 | 31 | $reference = ltrim($genericTagValueNode->value, '\\'); 32 | 33 | if ($this->reflectionProvider->hasClass($reference)) { 34 | return $this->nodeFactory->createClassConstReference($reference); 35 | } 36 | 37 | return new String_($reference); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Set/PHPUnitSetList.php: -------------------------------------------------------------------------------- 1 | $valueMap 11 | */ 12 | public function __construct( 13 | private string $annotationName, 14 | private string $attributeClass, 15 | private array $valueMap = [], 16 | private bool $isOnClassLevel = false, 17 | ) { 18 | } 19 | 20 | public function getAnnotationName(): string 21 | { 22 | return $this->annotationName; 23 | } 24 | 25 | public function getAttributeClass(): string 26 | { 27 | return $this->attributeClass; 28 | } 29 | 30 | /** 31 | * @return array 32 | */ 33 | public function getValueMap(): array 34 | { 35 | return $this->valueMap; 36 | } 37 | 38 | public function getIsOnClassLevel(): bool 39 | { 40 | return $this->isOnClassLevel; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ValueObject/BinaryOpWithAssertMethod.php: -------------------------------------------------------------------------------- 1 | binaryOpClass; 19 | } 20 | 21 | public function getAssetMethodName(): string 22 | { 23 | return $this->assetMethodName; 24 | } 25 | 26 | public function getNotAssertMethodName(): string 27 | { 28 | return $this->notAssertMethodName; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ValueObject/ConstantWithAssertMethods.php: -------------------------------------------------------------------------------- 1 | constant; 19 | } 20 | 21 | public function getAssetMethodName(): string 22 | { 23 | return $this->assetMethodName; 24 | } 25 | 26 | public function getNotAssertMethodName(): string 27 | { 28 | return $this->notAssertMethodName; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ValueObject/FunctionNameWithAssertMethods.php: -------------------------------------------------------------------------------- 1 | functionName; 19 | } 20 | 21 | public function getAssetMethodName(): string 22 | { 23 | return $this->assetMethodName; 24 | } 25 | 26 | public function getNotAssertMethodName(): string 27 | { 28 | return $this->notAssertMethodName; 29 | } 30 | } 31 | --------------------------------------------------------------------------------