Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
tipuloidea
/
back
/
vendor
/
friendsofphp
/
php-cs-fixer
/
src
/
Fixer
/
PhpUnit
:
PhpUnitTestCaseStaticMethodCallsFixer.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php declare(strict_types=1); /* * This file is part of PHP CS Fixer. * * (c) Fabien Potencier <fabien@symfony.com> * Dariusz RumiĆski <dariusz.ruminski@gmail.com> * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace PhpCsFixer\Fixer\PhpUnit; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Future; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @phpstan-type _AutogeneratedInputConfiguration array{ * call_type?: 'self'|'static'|'this', * methods?: array<string, string>, * target?: '10.0'|'11.0'|'newest', * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * call_type: 'self'|'static'|'this', * methods: array<string, string>, * target: '10.0'|'11.0'|'newest', * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @phpstan-import-type _PhpTokenArray from Token * * @author Filippo Tessarotto <zoeslam@gmail.com> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; /** * @internal */ public const CALL_TYPE_THIS = 'this'; /** * @internal */ public const CALL_TYPE_SELF = 'self'; /** * @internal */ public const CALL_TYPE_STATIC = 'static'; /** * @var array<string, true> */ private const METHODS = [ // Assert methods 'anything' => true, 'arrayHasKey' => true, 'assertArrayHasKey' => true, 'assertArrayIsEqualToArrayIgnoringListOfKeys' => true, 'assertArrayIsEqualToArrayOnlyConsideringListOfKeys' => true, 'assertArrayIsIdenticalToArrayIgnoringListOfKeys' => true, 'assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys' => true, 'assertArrayNotHasKey' => true, 'assertArraySubset' => true, 'assertAttributeContains' => true, 'assertAttributeContainsOnly' => true, 'assertAttributeCount' => true, 'assertAttributeEmpty' => true, 'assertAttributeEquals' => true, 'assertAttributeGreaterThan' => true, 'assertAttributeGreaterThanOrEqual' => true, 'assertAttributeInstanceOf' => true, 'assertAttributeInternalType' => true, 'assertAttributeLessThan' => true, 'assertAttributeLessThanOrEqual' => true, 'assertAttributeNotContains' => true, 'assertAttributeNotContainsOnly' => true, 'assertAttributeNotCount' => true, 'assertAttributeNotEmpty' => true, 'assertAttributeNotEquals' => true, 'assertAttributeNotInstanceOf' => true, 'assertAttributeNotInternalType' => true, 'assertAttributeNotSame' => true, 'assertAttributeSame' => true, 'assertClassHasAttribute' => true, 'assertClassHasStaticAttribute' => true, 'assertClassNotHasAttribute' => true, 'assertClassNotHasStaticAttribute' => true, 'assertContains' => true, 'assertContainsEquals' => true, 'assertContainsNotOnlyArray' => true, 'assertContainsNotOnlyBool' => true, 'assertContainsNotOnlyCallable' => true, 'assertContainsNotOnlyClosedResource' => true, 'assertContainsNotOnlyFloat' => true, 'assertContainsNotOnlyInstancesOf' => true, 'assertContainsNotOnlyInt' => true, 'assertContainsNotOnlyIterable' => true, 'assertContainsNotOnlyNull' => true, 'assertContainsNotOnlyNumeric' => true, 'assertContainsNotOnlyObject' => true, 'assertContainsNotOnlyResource' => true, 'assertContainsNotOnlyScalar' => true, 'assertContainsNotOnlyString' => true, 'assertContainsOnly' => true, 'assertContainsOnlyArray' => true, 'assertContainsOnlyBool' => true, 'assertContainsOnlyCallable' => true, 'assertContainsOnlyClosedResource' => true, 'assertContainsOnlyFloat' => true, 'assertContainsOnlyInstancesOf' => true, 'assertContainsOnlyInt' => true, 'assertContainsOnlyIterable' => true, 'assertContainsOnlyNull' => true, 'assertContainsOnlyNumeric' => true, 'assertContainsOnlyObject' => true, 'assertContainsOnlyResource' => true, 'assertContainsOnlyScalar' => true, 'assertContainsOnlyString' => true, 'assertCount' => true, 'assertDirectoryDoesNotExist' => true, 'assertDirectoryExists' => true, 'assertDirectoryIsNotReadable' => true, 'assertDirectoryIsNotWritable' => true, 'assertDirectoryIsReadable' => true, 'assertDirectoryIsWritable' => true, 'assertDirectoryNotExists' => true, 'assertDirectoryNotIsReadable' => true, 'assertDirectoryNotIsWritable' => true, 'assertDoesNotMatchRegularExpression' => true, 'assertEmpty' => true, 'assertEquals' => true, 'assertEqualsCanonicalizing' => true, 'assertEqualsIgnoringCase' => true, 'assertEqualsWithDelta' => true, 'assertEqualXMLStructure' => true, 'assertFalse' => true, 'assertFileDoesNotExist' => true, 'assertFileEquals' => true, 'assertFileEqualsCanonicalizing' => true, 'assertFileEqualsIgnoringCase' => true, 'assertFileExists' => true, 'assertFileIsNotReadable' => true, 'assertFileIsNotWritable' => true, 'assertFileIsReadable' => true, 'assertFileIsWritable' => true, 'assertFileMatchesFormat' => true, 'assertFileMatchesFormatFile' => true, 'assertFileNotEquals' => true, 'assertFileNotEqualsCanonicalizing' => true, 'assertFileNotEqualsIgnoringCase' => true, 'assertFileNotExists' => true, 'assertFileNotIsReadable' => true, 'assertFileNotIsWritable' => true, 'assertFinite' => true, 'assertGreaterThan' => true, 'assertGreaterThanOrEqual' => true, 'assertInfinite' => true, 'assertInstanceOf' => true, 'assertInternalType' => true, 'assertIsArray' => true, 'assertIsBool' => true, 'assertIsCallable' => true, 'assertIsClosedResource' => true, 'assertIsFloat' => true, 'assertIsInt' => true, 'assertIsIterable' => true, 'assertIsList' => true, 'assertIsNotArray' => true, 'assertIsNotBool' => true, 'assertIsNotCallable' => true, 'assertIsNotClosedResource' => true, 'assertIsNotFloat' => true, 'assertIsNotInt' => true, 'assertIsNotIterable' => true, 'assertIsNotNumeric' => true, 'assertIsNotObject' => true, 'assertIsNotReadable' => true, 'assertIsNotResource' => true, 'assertIsNotScalar' => true, 'assertIsNotString' => true, 'assertIsNotWritable' => true, 'assertIsNumeric' => true, 'assertIsObject' => true, 'assertIsReadable' => true, 'assertIsResource' => true, 'assertIsScalar' => true, 'assertIsString' => true, 'assertIsWritable' => true, 'assertJson' => true, 'assertJsonFileEqualsJsonFile' => true, 'assertJsonFileNotEqualsJsonFile' => true, 'assertJsonStringEqualsJsonFile' => true, 'assertJsonStringEqualsJsonString' => true, 'assertJsonStringNotEqualsJsonFile' => true, 'assertJsonStringNotEqualsJsonString' => true, 'assertLessThan' => true, 'assertLessThanOrEqual' => true, 'assertMatchesRegularExpression' => true, 'assertNan' => true, 'assertNotContains' => true, 'assertNotContainsEquals' => true, 'assertNotContainsOnly' => true, 'assertNotCount' => true, 'assertNotEmpty' => true, 'assertNotEquals' => true, 'assertNotEqualsCanonicalizing' => true, 'assertNotEqualsIgnoringCase' => true, 'assertNotEqualsWithDelta' => true, 'assertNotFalse' => true, 'assertNotInstanceOf' => true, 'assertNotInternalType' => true, 'assertNotIsReadable' => true, 'assertNotIsWritable' => true, 'assertNotNull' => true, 'assertNotRegExp' => true, 'assertNotSame' => true, 'assertNotSameSize' => true, 'assertNotTrue' => true, 'assertNull' => true, 'assertObjectEquals' => true, 'assertObjectHasAttribute' => true, 'assertObjectHasProperty' => true, 'assertObjectNotEquals' => true, 'assertObjectNotHasAttribute' => true, 'assertObjectNotHasProperty' => true, 'assertRegExp' => true, 'assertSame' => true, 'assertSameSize' => true, 'assertStringContainsString' => true, 'assertStringContainsStringIgnoringCase' => true, 'assertStringContainsStringIgnoringLineEndings' => true, 'assertStringEndsNotWith' => true, 'assertStringEndsWith' => true, 'assertStringEqualsFile' => true, 'assertStringEqualsFileCanonicalizing' => true, 'assertStringEqualsFileIgnoringCase' => true, 'assertStringEqualsStringIgnoringLineEndings' => true, 'assertStringMatchesFormat' => true, 'assertStringMatchesFormatFile' => true, 'assertStringNotContainsString' => true, 'assertStringNotContainsStringIgnoringCase' => true, 'assertStringNotEqualsFile' => true, 'assertStringNotEqualsFileCanonicalizing' => true, 'assertStringNotEqualsFileIgnoringCase' => true, 'assertStringNotMatchesFormat' => true, 'assertStringNotMatchesFormatFile' => true, 'assertStringStartsNotWith' => true, 'assertStringStartsWith' => true, 'assertThat' => true, 'assertTrue' => true, 'assertXmlFileEqualsXmlFile' => true, 'assertXmlFileNotEqualsXmlFile' => true, 'assertXmlStringEqualsXmlFile' => true, 'assertXmlStringEqualsXmlString' => true, 'assertXmlStringNotEqualsXmlFile' => true, 'assertXmlStringNotEqualsXmlString' => true, 'attribute' => true, 'attributeEqualTo' => true, 'callback' => true, 'classHasAttribute' => true, 'classHasStaticAttribute' => true, 'contains' => true, 'containsEqual' => true, 'containsIdentical' => true, 'containsOnly' => true, 'containsOnlyArray' => true, 'containsOnlyBool' => true, 'containsOnlyCallable' => true, 'containsOnlyClosedResource' => true, 'containsOnlyFloat' => true, 'containsOnlyInstancesOf' => true, 'containsOnlyInt' => true, 'containsOnlyIterable' => true, 'containsOnlyNull' => true, 'containsOnlyNumeric' => true, 'containsOnlyObject' => true, 'containsOnlyResource' => true, 'containsOnlyScalar' => true, 'containsOnlyString' => true, 'countOf' => true, 'directoryExists' => true, 'equalTo' => true, 'equalToCanonicalizing' => true, 'equalToIgnoringCase' => true, 'equalToWithDelta' => true, 'fail' => true, 'fileExists' => true, 'getCount' => true, 'getObjectAttribute' => true, 'getStaticAttribute' => true, 'greaterThan' => true, 'greaterThanOrEqual' => true, 'identicalTo' => true, 'isArray' => true, 'isBool' => true, 'isCallable' => true, 'isClosedResource' => true, 'isEmpty' => true, 'isFalse' => true, 'isFinite' => true, 'isFloat' => true, 'isInfinite' => true, 'isInstanceOf' => true, 'isInt' => true, 'isIterable' => true, 'isJson' => true, 'isList' => true, 'isNan' => true, 'isNull' => true, 'isNumeric' => true, 'isObject' => true, 'isReadable' => true, 'isResource' => true, 'isScalar' => true, 'isString' => true, 'isTrue' => true, 'isType' => true, 'isWritable' => true, 'lessThan' => true, 'lessThanOrEqual' => true, 'logicalAnd' => true, 'logicalNot' => true, 'logicalOr' => true, 'logicalXor' => true, 'markTestIncomplete' => true, 'markTestSkipped' => true, 'matches' => true, 'matchesRegularExpression' => true, 'objectEquals' => true, 'objectHasAttribute' => true, 'readAttribute' => true, 'resetCount' => true, 'stringContains' => true, 'stringEndsWith' => true, 'stringEqualsStringIgnoringLineEndings' => true, 'stringStartsWith' => true, // TestCase methods 'any' => true, 'at' => true, 'atLeast' => true, 'atLeastOnce' => true, 'atMost' => true, 'createStub' => true, 'createConfiguredStub' => true, 'createStubForIntersectionOfInterfaces' => true, 'exactly' => true, 'getStubBuilder' => true, 'never' => true, 'once' => true, 'onConsecutiveCalls' => true, 'returnArgument' => true, 'returnCallback' => true, 'returnSelf' => true, 'returnValue' => true, 'returnValueMap' => true, 'setUpBeforeClass' => true, 'tearDownAfterClass' => true, 'throwException' => true, ]; /** * @var array<string, bool> */ private const ALLOWED_VALUES = [ self::CALL_TYPE_THIS => true, self::CALL_TYPE_SELF => true, self::CALL_TYPE_STATIC => true, ]; /** * @var non-empty-array<string, non-empty-list<_PhpTokenArray>> */ private array $conversionMap = [ self::CALL_TYPE_THIS => [[\T_OBJECT_OPERATOR, '->'], [\T_VARIABLE, '$this']], self::CALL_TYPE_SELF => [[\T_DOUBLE_COLON, '::'], [\T_STRING, 'self']], self::CALL_TYPE_STATIC => [[\T_DOUBLE_COLON, '::'], [\T_STATIC, 'static']], ]; public function getDefinition(): FixerDefinitionInterface { $codeSample = <<<'PHP' <?php final class MyTest extends \PHPUnit_Framework_TestCase { public function testMe() { $this->assertSame(1, 2); self::assertSame(1, 2); static::assertSame(1, 2); static::assertTrue(false); } } PHP; return new FixerDefinition( 'Calls to `PHPUnit\Framework\TestCase` static methods (like assertions) must all be of the same type, either `$this->`, `self::` or `static::`.', [ new CodeSample($codeSample), new CodeSample($codeSample, ['call_type' => self::CALL_TYPE_THIS]), new CodeSample($codeSample, ['methods' => ['assertTrue' => self::CALL_TYPE_THIS]]), ], null, 'Risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.', ); } /** * {@inheritdoc} * * Must run before SelfStaticAccessorFixer. */ public function getPriority(): int { return 0; } public function isRisky(): bool { return true; } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('call_type', 'The call type to use for referring to PHPUnit methods.')) ->setAllowedTypes(['string']) ->setAllowedValues(array_keys(self::ALLOWED_VALUES)) ->setDefault(Future::getV4OrV3(self::CALL_TYPE_THIS, self::CALL_TYPE_STATIC)) // vide https://github.com/sebastianbergmann/phpunit/issues/2104#issuecomment-192919598 ->getOption(), (new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.')) ->setAllowedTypes(['array<string, string>']) ->setAllowedValues([static function (array $option): bool { foreach ($option as $method => $value) { if (!isset(self::METHODS[$method])) { throw new InvalidOptionsException( \sprintf( 'Unexpected "methods" key, expected any of %s, got "%s".', Utils::naturalLanguageJoin(array_keys(self::METHODS)), \gettype($method).'#'.$method, ), ); } if (!isset(self::ALLOWED_VALUES[$value])) { throw new InvalidOptionsException( \sprintf( 'Unexpected value for method "%s", expected any of %s, got "%s".', $method, Utils::naturalLanguageJoin(array_keys(self::ALLOWED_VALUES)), \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value), ), ); } } return true; }]) ->setDefault([]) ->getOption(), (new FixerOptionBuilder('target', 'Target version of PHPUnit.')) ->setAllowedTypes(['string']) ->setAllowedValues([ PhpUnitTargetVersion::VERSION_10_0, PhpUnitTargetVersion::VERSION_11_0, PhpUnitTargetVersion::VERSION_NEWEST, ]) ->setDefault(Future::getV4OrV3(PhpUnitTargetVersion::VERSION_NEWEST, PhpUnitTargetVersion::VERSION_10_0)) ->getOption(), ]); } protected function configurePostNormalisation(): void { $dynamicMethods = []; if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_11_0)) { // not statc since v11 $dynamicMethods = [ 'any', 'atLeast', 'atLeastOnce', 'atMost', 'exactly', 'never', 'once', 'throwException', ]; } if (PhpUnitTargetVersion::VERSION_11_0 === $this->configuration['target']) { // not static since v11, removed in v12 $dynamicMethods[] = 'onConsecutiveCalls'; $dynamicMethods[] = 'returnArgument'; $dynamicMethods[] = 'returnCallback'; $dynamicMethods[] = 'returnSelf'; $dynamicMethods[] = 'returnValue'; $dynamicMethods[] = 'returnValueMap'; } foreach ($dynamicMethods as $method) { if (isset($this->configuration['methods'][$method])) { throw new InvalidFixerConfigurationException( $this->getName(), \sprintf('Configuration cannot contain method "%s" and target "%s", it is dynamic in that PHPUnit version.', $method, $this->configuration['target']), ); } $this->configuration['methods'][$method] = self::CALL_TYPE_THIS; } } protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $analyzer = new TokensAnalyzer($tokens); for ($index = $startIndex; $index < $endIndex; ++$index) { // skip anonymous classes if ($tokens[$index]->isGivenKind(\T_CLASS)) { $index = $this->findEndOfNextBlock($tokens, $index); continue; } $callType = $this->configuration['call_type']; if ($tokens[$index]->isGivenKind(\T_FUNCTION)) { // skip lambda if ($analyzer->isLambda($index)) { $index = $this->findEndOfNextBlock($tokens, $index); continue; } // do not change `self` to `this` in static methods if (self::CALL_TYPE_THIS === $callType) { $attributes = $analyzer->getMethodAttributes($index); if (false !== $attributes['static']) { $index = $this->findEndOfNextBlock($tokens, $index); continue; } } } if (!$tokens[$index]->isGivenKind(\T_STRING) || !isset(self::METHODS[$tokens[$index]->getContent()])) { continue; } $nextIndex = $tokens->getNextMeaningfulToken($index); if (!$tokens[$nextIndex]->equals('(')) { $index = $nextIndex; continue; } if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) { continue; } $methodName = $tokens[$index]->getContent(); if (isset($this->configuration['methods'][$methodName])) { $callType = $this->configuration['methods'][$methodName]; } $operatorIndex = $tokens->getPrevMeaningfulToken($index); $referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex); if (!$this->needsConversion($tokens, $index, $referenceIndex, $callType)) { continue; } $tokens[$operatorIndex] = new Token($this->conversionMap[$callType][0]); $tokens[$referenceIndex] = new Token($this->conversionMap[$callType][1]); } } private function needsConversion(Tokens $tokens, int $index, int $referenceIndex, string $callType): bool { $functionsAnalyzer = new FunctionsAnalyzer(); return $functionsAnalyzer->isTheSameClassCall($tokens, $index) && !$tokens[$referenceIndex]->equals($this->conversionMap[$callType][1], false); } private function findEndOfNextBlock(Tokens $tokens, int $index): int { $nextIndex = $tokens->getNextTokenOfKind($index, [';', '{']); return $tokens[$nextIndex]->equals('{') ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex) : $nextIndex; } }