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
:
PhpUnitMethodCasingFixer.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\DocBlock\DocBlock; use PhpCsFixer\DocBlock\Line; 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\FixerDefinition\VersionSpecification; use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; use PhpCsFixer\Utils; /** * @phpstan-type _AutogeneratedInputConfiguration array{ * case?: 'camel_case'|'snake_case', * } * @phpstan-type _AutogeneratedComputedConfiguration array{ * case: 'camel_case'|'snake_case', * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @author Filippo Tessarotto <zoeslam@gmail.com> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class PhpUnitMethodCasingFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ use ConfigurableFixerTrait; /** * @internal */ public const CAMEL_CASE = 'camel_case'; /** * @internal */ public const SNAKE_CASE = 'snake_case'; public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Enforce camel (or snake) case for PHPUnit test methods, following configuration.', [ new CodeSample( <<<'PHP' <?php class MyTest extends \PhpUnit\FrameWork\TestCase { public function test_my_code() {} } PHP, ), new CodeSample( <<<'PHP' <?php class MyTest extends \PhpUnit\FrameWork\TestCase { public function testMyCode() {} } PHP, ['case' => self::SNAKE_CASE], ), new VersionSpecificCodeSample( <<<'PHP' <?php use \PHPUnit\Framework\Attributes\Test; class MyTest extends \PhpUnit\FrameWork\TestCase { #[PHPUnit\Framework\Attributes\Test] public function test_my_code() {} } PHP, new VersionSpecification(8_00_00), ), new VersionSpecificCodeSample( <<<'PHP' <?php use \PHPUnit\Framework\Attributes\Test; class MyTest extends \PhpUnit\FrameWork\TestCase { #[PHPUnit\Framework\Attributes\Test] public function testMyCode() {} } PHP, new VersionSpecification(8_00_00), ['case' => self::SNAKE_CASE], ), ], ); } /** * {@inheritdoc} * * Must run after PhpUnitTestAnnotationFixer. */ public function getPriority(): int { return 0; } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('case', 'Apply camel or snake case to test methods.')) ->setAllowedValues([self::CAMEL_CASE, self::SNAKE_CASE]) ->setDefault(self::CAMEL_CASE) ->getOption(), ]); } protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $existingFunctionNamesLowercase = []; for ($index = $endIndex - 1; $index > $startIndex; --$index) { if (!$this->isMethod($tokens, $index)) { continue; } $functionNameIndex = $tokens->getNextMeaningfulToken($index); $existingFunctionNamesLowercase[strtolower($tokens[$functionNameIndex]->getContent())] = null; } unset($index); for ($index = $endIndex - 1; $index > $startIndex; --$index) { if (!$this->isTestMethod($tokens, $index)) { continue; } $functionNameIndex = $tokens->getNextMeaningfulToken($index); $functionName = $tokens[$functionNameIndex]->getContent(); $functionNameLowercase = strtolower($functionName); $newFunctionName = $this->updateMethodCasing($functionName); $newFunctionNameLowercase = strtolower($newFunctionName); if ( \array_key_exists($newFunctionNameLowercase, $existingFunctionNamesLowercase) && $functionNameLowercase !== $newFunctionNameLowercase ) { continue; } $existingFunctionNamesLowercase[$newFunctionNameLowercase] = null; if ($newFunctionName !== $functionName) { $tokens[$functionNameIndex] = new Token([\T_STRING, $newFunctionName]); } $docBlockIndex = $this->getDocBlockIndex($tokens, $index); if ($tokens[$docBlockIndex]->isGivenKind(\T_DOC_COMMENT)) { $this->updateDocBlock($tokens, $docBlockIndex); } } } private function updateMethodCasing(string $functionName): string { $parts = explode('::', $functionName); $functionNamePart = array_pop($parts); if (self::CAMEL_CASE === $this->configuration['case']) { $newFunctionNamePart = $functionNamePart; $newFunctionNamePart = ucwords($newFunctionNamePart, '_'); $newFunctionNamePart = str_replace('_', '', $newFunctionNamePart); $newFunctionNamePart = lcfirst($newFunctionNamePart); } else { $newFunctionNamePart = Utils::camelCaseToUnderscore($functionNamePart); } $parts[] = $newFunctionNamePart; return implode('::', $parts); } private function isTestMethod(Tokens $tokens, int $index): bool { // Check if we are dealing with a (non-abstract, non-lambda) function if (!$this->isMethod($tokens, $index)) { return false; } // if the function name starts with test it's a test $functionNameIndex = $tokens->getNextMeaningfulToken($index); $functionName = $tokens[$functionNameIndex]->getContent(); if (str_starts_with($functionName, 'test')) { return true; } if ($this->isTestAttributePresent($tokens, $index)) { return true; } $docBlockIndex = $this->getDocBlockIndex($tokens, $index); return $tokens[$docBlockIndex]->isGivenKind(\T_DOC_COMMENT) // If the function doesn't have test in its name, and no doc block, it's not a test && str_contains($tokens[$docBlockIndex]->getContent(), '@test'); } private function isMethod(Tokens $tokens, int $index): bool { $tokensAnalyzer = new TokensAnalyzer($tokens); return $tokens[$index]->isGivenKind(\T_FUNCTION) && !$tokensAnalyzer->isLambda($index); } private function updateDocBlock(Tokens $tokens, int $docBlockIndex): void { $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); $lines = $doc->getLines(); $docBlockNeedsUpdate = false; for ($inc = 0; $inc < \count($lines); ++$inc) { $lineContent = $lines[$inc]->getContent(); if (!str_contains($lineContent, '@depends')) { continue; } $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', fn (array $matches): string => \sprintf( '%s%s%s', $matches[1], $this->updateMethodCasing($matches[2]), $matches[3], ), $lineContent); if ($newLineContent !== $lineContent) { $lines[$inc] = new Line($newLineContent); $docBlockNeedsUpdate = true; } } if ($docBlockNeedsUpdate) { $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([\T_DOC_COMMENT, $lines]); } } }