123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152 |
- <?php
- declare(strict_types=1);
- namespace PhpCsFixer\Tests\AutoReview;
- use PhpCsFixer\AbstractFixer;
- use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
- use PhpCsFixer\AbstractPhpdocTypesFixer;
- use PhpCsFixer\AbstractProxyFixer;
- use PhpCsFixer\Console\Command\FixCommand;
- use PhpCsFixer\DocBlock\Annotation;
- use PhpCsFixer\DocBlock\DocBlock;
- use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
- use PhpCsFixer\Fixer\ConfigurableFixerTrait;
- use PhpCsFixer\Fixer\PhpUnit\PhpUnitNamespacedFixer;
- use PhpCsFixer\FixerFactory;
- use PhpCsFixer\Preg;
- use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
- use PhpCsFixer\Tests\Test\AbstractIntegrationTestCase;
- use PhpCsFixer\Tests\TestCase;
- use PhpCsFixer\Tokenizer\Token;
- use PhpCsFixer\Tokenizer\Tokens;
- use PhpCsFixer\Tokenizer\TokensAnalyzer;
- use Symfony\Component\Finder\Finder;
- use Symfony\Component\Finder\SplFileInfo;
- final class ProjectCodeTest extends TestCase
- {
-
- private static ?array $testClassCases = null;
-
- private static ?array $srcClassCases = null;
-
- private static ?array $dataProviderMethodCases = null;
-
- private static array $tokensCache = [];
- public static function tearDownAfterClass(): void
- {
- self::$srcClassCases = null;
- self::$testClassCases = null;
- self::$tokensCache = [];
- }
-
- public function testThatSrcClassHaveTestClass(string $className): void
- {
- $testClassName = 'PhpCsFixer\Tests'.substr($className, 10).'Test';
- self::assertTrue(class_exists($testClassName), \sprintf('Expected test class "%s" for "%s" not found.', $testClassName, $className));
- }
-
- public function testThatSrcClassesNotAbuseInterfaces(string $className): void
- {
- $rc = new \ReflectionClass($className);
- $allowedMethods = array_map(
- fn (\ReflectionClass $interface): array => $this->getPublicMethodNames($interface),
- $rc->getInterfaces()
- );
- if (\count($allowedMethods) > 0) {
- $allowedMethods = array_unique(array_merge(...array_values($allowedMethods)));
- }
- $allowedMethods[] = '__construct';
- $allowedMethods[] = '__destruct';
- $allowedMethods[] = '__wakeup';
- $exceptionMethods = [
- 'configure',
- 'getConfigurationDefinition',
- 'getDefaultConfiguration',
- 'setWhitespacesConfig',
- 'createConfigurationDefinition',
- ];
- $definedMethods = $this->getPublicMethodNames($rc);
- $extraMethods = array_diff(
- $definedMethods,
- $allowedMethods,
- $exceptionMethods
- );
- sort($extraMethods);
- self::assertEmpty(
- $extraMethods,
- \sprintf(
- "Class '%s' should not have public methods that are not part of implemented interfaces.\nViolations:\n%s",
- $className,
- implode("\n", array_map(static fn (string $item): string => " * {$item}", $extraMethods))
- )
- );
- }
-
- public function testThatSrcClassesNotExposeProperties(string $className): void
- {
- $rc = new \ReflectionClass($className);
- self::assertEmpty(
- $rc->getProperties(\ReflectionProperty::IS_PUBLIC),
- \sprintf('Class \'%s\' should not have public properties.', $className)
- );
- if ($rc->isFinal()) {
- return;
- }
- $allowedProps = [];
- $definedProps = $rc->getProperties(\ReflectionProperty::IS_PROTECTED);
- if (false !== $rc->getParentClass()) {
- $allowedProps = $rc->getParentClass()->getProperties(\ReflectionProperty::IS_PROTECTED);
- }
- $allowedProps = array_map(static fn (\ReflectionProperty $item): string => $item->getName(), $allowedProps);
- $definedProps = array_map(static fn (\ReflectionProperty $item): string => $item->getName(), $definedProps);
- $exceptionPropsPerClass = [
- AbstractFixer::class => ['configuration', 'configurationDefinition', 'whitespacesConfig'],
- AbstractPhpdocToTypeDeclarationFixer::class => ['configuration'],
- AbstractPhpdocTypesFixer::class => ['tags'],
- AbstractProxyFixer::class => ['proxyFixers'],
- ConfigurableFixerTrait::class => ['configuration'],
- FixCommand::class => ['defaultDescription', 'defaultName'],
- ];
- $extraProps = array_diff(
- $definedProps,
- $allowedProps,
- $exceptionPropsPerClass[$className] ?? []
- );
- sort($extraProps);
- self::assertEmpty(
- $extraProps,
- \sprintf(
- "Class '%s' should not have protected properties.\nViolations:\n%s",
- $className,
- implode("\n", array_map(static fn (string $item): string => " * {$item}", $extraProps))
- )
- );
- }
-
- public function testThatTestClassExtendsPhpCsFixerTestCaseClass(string $className): void
- {
- self::assertTrue(is_subclass_of($className, TestCase::class), \sprintf('Expected test class "%s" to be a subclass of "%s".', $className, TestCase::class));
- }
-
- public function testThatTestClassesAreTraitOrAbstractOrFinal(string $testClassName): void
- {
- $rc = new \ReflectionClass($testClassName);
- self::assertTrue(
- $rc->isTrait() || $rc->isAbstract() || $rc->isFinal(),
- \sprintf('Test class %s should be trait, abstract or final.', $testClassName)
- );
- }
-
- public function testThatTestClassesAreInternal(string $testClassName): void
- {
- $rc = new \ReflectionClass($testClassName);
- $doc = new DocBlock($rc->getDocComment());
- self::assertNotEmpty(
- $doc->getAnnotationsOfType('internal'),
- \sprintf('Test class %s should have internal annotation.', $testClassName)
- );
- }
-
- public function testThatTestClassesPublicMethodsAreCorrectlyNamed(string $testClassName): void
- {
- $reflectionClass = new \ReflectionClass($testClassName);
- $publicMethods = array_filter(
- $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
- static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
- );
- if ([] === $publicMethods) {
- $this->expectNotToPerformAssertions();
- return;
- }
- foreach ($publicMethods as $method) {
- self::assertMatchesRegularExpression(
- '/^(test|expect|provide|setUpBeforeClass$|tearDownAfterClass$|__construct$)/',
- $method->getName(),
- \sprintf('Public method "%s::%s" is not properly named.', $reflectionClass->getName(), $method->getName())
- );
- }
- }
-
- public function testThatTestDataProvidersAreUsed(string $testClassName, string $dataProviderName): void
- {
- $usedDataProviderMethodNames = [];
- foreach ($this->getUsedDataProviderMethodNames($testClassName) as $providerName) {
- $usedDataProviderMethodNames[] = $providerName;
- }
- self::assertContains(
- $dataProviderName,
- $usedDataProviderMethodNames,
- \sprintf('Data provider "%s::%s" is not used.', $testClassName, $dataProviderName),
- );
- }
-
- public function testThatTestDataProvidersAreCorrectlyNamed(string $testClassName, string $dataProviderName): void
- {
- self::assertMatchesRegularExpression('/^provide[A-Z]\S+Cases$/', $dataProviderName, \sprintf(
- 'Data provider "%s::%s" is not correctly named.',
- $testClassName,
- $dataProviderName,
- ));
- }
-
- public static function provideDataProviderMethodCases(): iterable
- {
- if (null === self::$dataProviderMethodCases) {
- self::$dataProviderMethodCases = [];
- foreach (self::provideTestClassCases() as $testClassName) {
- $testClassName = reset($testClassName);
- $reflectionClass = new \ReflectionClass($testClassName);
- $dataProviderNames = array_filter(
- $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
- static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName() && str_starts_with($reflectionMethod->getName(), 'provide')
- );
- foreach ($dataProviderNames as $dataProviderName) {
- self::$dataProviderMethodCases[$testClassName.'::'.$dataProviderName->getName()] = [$testClassName, $dataProviderName->getName()];
- }
- }
- }
- yield from self::$dataProviderMethodCases;
- }
-
- public function testThatTestClassCoversAreCorrect(string $testClassName): void
- {
- $reflectionClass = new \ReflectionClass($testClassName);
- if ($reflectionClass->isAbstract() || $reflectionClass->isInterface()) {
- $this->expectNotToPerformAssertions();
- return;
- }
- $doc = $reflectionClass->getDocComment();
- self::assertNotFalse($doc);
- if (Preg::match('/@coversNothing/', $doc)) {
- return;
- }
- $covers = Preg::matchAll('/@covers (\S*)/', $doc, $matches);
- self::assertGreaterThanOrEqual(1, $covers, \sprintf('Missing @covers in PHPDoc of test class "%s".', $testClassName));
- array_shift($matches);
-
- $class = '\\'.str_replace('PhpCsFixer\Tests\\', 'PhpCsFixer\\', substr($testClassName, 0, -4));
- $parentClass = (new \ReflectionClass($class))->getParentClass();
- $parentClassName = false === $parentClass ? null : '\\'.$parentClass->getName();
- foreach ($matches as $match) {
- $classMatch = array_shift($match);
- self::assertTrue(
- $classMatch === $class || $parentClassName === $classMatch,
- \sprintf('Unexpected @covers "%s" for "%s".', $classMatch, $testClassName)
- );
- }
- }
-
- public function testThereIsNoUsageOfExtract(string $className): void
- {
- $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
- $message = \sprintf('Class %s must not use "extract()", explicitly extract only the keys that are needed - you never know what\'s else inside.', $className);
- self::assertNotContains('extract', $calledFunctions, $message);
- }
-
- public function testThereIsNoPregFunctionUsedDirectly(string $className): void
- {
- $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
- $message = \sprintf('Class %s must not use preg_*, it shall use Preg::* instead.', $className);
- self::assertNotContains('preg_filter', $calledFunctions, $message);
- self::assertNotContains('preg_grep', $calledFunctions, $message);
- self::assertNotContains('preg_match', $calledFunctions, $message);
- self::assertNotContains('preg_match_all', $calledFunctions, $message);
- self::assertNotContains('preg_replace', $calledFunctions, $message);
- self::assertNotContains('preg_replace_callback', $calledFunctions, $message);
- self::assertNotContains('preg_split', $calledFunctions, $message);
- }
-
- public function testThereIsNoUsageOfSetAccessible(string $className): void
- {
- $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
- $message = \sprintf('Class %s must not use "setAccessible()", use "Closure::bind()" instead.', $className);
- self::assertNotContains('setAccessible', $calledFunctions, $message);
- }
-
- public function testNoPHPUnitMockUsed(string $className): void
- {
- $calledFunctions = $this->extractFunctionNamesCalledInClass($className);
- $message = \sprintf('Class %s must not use PHPUnit\'s mock, it shall use anonymous class instead.', $className);
- self::assertNotContains('getMockBuilder', $calledFunctions, $message);
- self::assertNotContains('createMock', $calledFunctions, $message);
- self::assertNotContains('createMockForIntersectionOfInterfaces', $calledFunctions, $message);
- self::assertNotContains('createPartialMock', $calledFunctions, $message);
- self::assertNotContains('createTestProxy', $calledFunctions, $message);
- self::assertNotContains('getMockForAbstractClass', $calledFunctions, $message);
- self::assertNotContains('getMockFromWsdl', $calledFunctions, $message);
- self::assertNotContains('getMockForTrait', $calledFunctions, $message);
- self::assertNotContains('getMockClass', $calledFunctions, $message);
- self::assertNotContains('createConfiguredMock', $calledFunctions, $message);
- self::assertNotContains('getObjectForTrait', $calledFunctions, $message);
- }
-
- public function testExpectedInputOrder(string $testClassName): void
- {
- $reflectionClass = new \ReflectionClass($testClassName);
- $publicMethods = array_filter(
- $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
- static fn (\ReflectionMethod $reflectionMethod): bool => $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
- );
- if ([] === $publicMethods) {
- $this->expectNotToPerformAssertions();
- return;
- }
-
- foreach ($publicMethods as $method) {
- $parameters = $method->getParameters();
- if (\count($parameters) < 2) {
- $this->addToAssertionCount(1);
- continue;
- }
- $expected = [
- 'expected' => false,
- 'input' => false,
- ];
- for ($i = \count($parameters) - 1; $i >= 0; --$i) {
- $name = $parameters[$i]->getName();
- if (isset($expected[$name])) {
- $expected[$name] = $i;
- }
- }
- $expectedFound = array_filter($expected, static fn ($item): bool => false !== $item);
- if (\count($expectedFound) < 2) {
- $this->addToAssertionCount(1);
- continue;
- }
- self::assertLessThan(
- $expected['input'],
- $expected['expected'],
- \sprintf('Public method "%s::%s" has parameter \'input\' before \'expected\'.', $reflectionClass->getName(), $method->getName())
- );
- }
- }
-
- public function testDataProvidersAreNonPhpVersionConditional(string $testClassName, string $dataProviderName): void
- {
- $tokens = $this->createTokensForClass($testClassName);
- $tokensAnalyzer = new TokensAnalyzer($tokens);
- $dataProviderElements = array_filter($tokensAnalyzer->getClassyElements(), static function (array $v, int $k) use ($tokens, $dataProviderName) {
- $nextToken = $tokens[$tokens->getNextMeaningfulToken($k)];
-
- return 'method' === $v['type'] && $nextToken->equals([T_STRING, $dataProviderName]);
- }, ARRAY_FILTER_USE_BOTH);
- if (1 !== \count($dataProviderElements)) {
- throw new \UnexpectedValueException(\sprintf('Data provider "%s::%s" should be found exactly once, got %d times.', $testClassName, $dataProviderName, \count($dataProviderElements)));
- }
- $methodIndex = array_key_first($dataProviderElements);
- $startIndex = $tokens->getNextTokenOfKind($methodIndex, ['{']);
- $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex);
- $versionTokens = array_filter($tokens->findGivenKind(T_STRING, $startIndex, $endIndex), static fn (Token $v): bool => $v->equalsAny([
- [T_STRING, 'PHP_VERSION_ID'],
- [T_STRING, 'PHP_MAJOR_VERSION'],
- [T_STRING, 'PHP_MINOR_VERSION'],
- [T_STRING, 'PHP_RELEASE_VERSION'],
- [T_STRING, 'phpversion'],
- ], false));
- self::assertCount(
- 0,
- $versionTokens,
- \sprintf(
- 'Data provider "%s::%s" should not check PHP version and provide different cases depends on it. It leads to situation when DataProvider provides "sometimes 10, sometimes 11" test cases, depends on PHP version. That makes John Doe to see "you run 10/10" and thinking all tests are executed, instead of actually seeing "you run 10/11 and 1 skipped".',
- $testClassName,
- $dataProviderName,
- ),
- );
- }
-
- public function testDataProvidersDeclaredReturnType(string $testClassName, string $dataProviderName): void
- {
- $dataProvider = new \ReflectionMethod($testClassName, $dataProviderName);
- self::assertSame('iterable', $dataProvider->hasReturnType() && $dataProvider->getReturnType() instanceof \ReflectionNamedType ? $dataProvider->getReturnType()->getName() : null, \sprintf('Data provider "%s::%s" must provide `iterable` as return in method prototype.', $testClassName, $dataProviderName));
- $doc = new DocBlock(false !== $dataProvider->getDocComment() ? $dataProvider->getDocComment() : '/** */');
- $returnDocs = $doc->getAnnotationsOfType('return');
- if (\count($returnDocs) > 1) {
- throw new \UnexpectedValueException(\sprintf('Multiple "%s::%s@return" annotations.', $testClassName, $dataProviderName));
- }
- if (1 !== \count($returnDocs)) {
- $this->addToAssertionCount(1);
- return;
- }
- $returnDoc = $returnDocs[0];
- $types = $returnDoc->getTypes();
- self::assertCount(1, $types, \sprintf('Data provider "%s::%s@return" must provide single type.', $testClassName, $dataProviderName));
- self::assertMatchesRegularExpression('/^iterable\</', $types[0], \sprintf('Data provider "%s::%s@return" must return iterable.', $testClassName, $dataProviderName));
- self::assertMatchesRegularExpression('/^iterable\<(?:(?:int\|)?string, )?array\{/', $types[0], \sprintf('Data provider "%s::%s@return" must return iterable of tuples (eg `iterable<string, array{string, string}>`).', $testClassName, $dataProviderName));
- }
-
- public function testAllCodeContainSingleClassy(string $className): void
- {
- $headerTypes = [
- T_ABSTRACT,
- T_AS,
- T_COMMENT,
- T_DECLARE,
- T_DOC_COMMENT,
- T_FINAL,
- T_LNUMBER,
- T_NAMESPACE,
- T_NS_SEPARATOR,
- T_OPEN_TAG,
- T_STRING,
- T_USE,
- T_WHITESPACE,
- ];
- if (\defined('T_READONLY')) {
- $headerTypes[] = T_READONLY;
- }
- $tokens = $this->createTokensForClass($className);
- $classyIndex = null;
- self::assertTrue($tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()), \sprintf('File for "%s" should contains a classy.', $className));
- $count = \count($tokens);
- for ($index = 1; $index < $count; ++$index) {
- if ($tokens[$index]->isClassy()) {
- $classyIndex = $index;
- break;
- }
- if (\defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
- $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
- continue;
- }
- if (!$tokens[$index]->isGivenKind($headerTypes) && !$tokens[$index]->equalsAny([';', '=', '(', ')'])) {
- self::fail(\sprintf('File for "%s" should only contains single classy, found "%s" @ %d.', $className, $tokens[$index]->toJson(), $index));
- }
- }
- self::assertNotNull($classyIndex, \sprintf('File for "%s" does not contain a classy.', $className));
- $nextTokenOfKind = $tokens->getNextTokenOfKind($classyIndex, ['{']);
- if (!\is_int($nextTokenOfKind)) {
- throw new \UnexpectedValueException('Classy without {} - braces.');
- }
- $classyEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextTokenOfKind);
- self::assertNull($tokens->getNextMeaningfulToken($classyEndIndex), \sprintf('File for "%s" should only contains a single classy.', $className));
- }
-
- public function testInheritdocIsNotAbused(string $className): void
- {
- $rc = new \ReflectionClass($className);
- $allowedMethods = array_map(
- fn (\ReflectionClass $interface): array => $this->getPublicMethodNames($interface),
- $rc->getInterfaces()
- );
- if (\count($allowedMethods) > 0) {
- $allowedMethods = array_merge(...array_values($allowedMethods));
- }
- $parentClass = $rc;
- while (false !== $parentClass = $parentClass->getParentClass()) {
- foreach ($parentClass->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
- $allowedMethods[] = $method->getName();
- }
- }
- $allowedMethods = array_unique($allowedMethods);
- $methodsWithInheritdoc = array_filter(
- $rc->getMethods(),
- static fn (\ReflectionMethod $rm): bool => false !== $rm->getDocComment() && stripos($rm->getDocComment(), '@inheritdoc')
- );
- $methodsWithInheritdoc = array_map(
- static fn (\ReflectionMethod $rm): string => $rm->getName(),
- $methodsWithInheritdoc
- );
- $extraMethods = array_diff($methodsWithInheritdoc, $allowedMethods);
- self::assertEmpty(
- $extraMethods,
- \sprintf(
- "Class '%s' should not have methods with '@inheritdoc' in PHPDoc that are not inheriting PHPDoc.\nViolations:\n%s",
- $className,
- implode("\n", array_map(static fn ($item): string => " * {$item}", $extraMethods))
- )
- );
- }
-
- public static function provideSrcClassCases(): iterable
- {
- if (null === self::$srcClassCases) {
- $cases = self::getSrcClasses();
- self::$srcClassCases = array_combine(
- $cases,
- array_map(static fn (string $case): array => [$case], $cases),
- );
- }
- yield from self::$srcClassCases;
- }
-
- public static function provideThatSrcClassesNotAbuseInterfacesCases(): iterable
- {
- return array_map(
- static fn (string $item): array => [$item],
- array_filter(self::getSrcClasses(), static function (string $className): bool {
- $rc = new \ReflectionClass($className);
- $doc = false !== $rc->getDocComment()
- ? new DocBlock($rc->getDocComment())
- : null;
- if (
- $rc->isInterface()
- || (null !== $doc && \count($doc->getAnnotationsOfType('internal')) > 0)
- || \in_array($className, [
- \PhpCsFixer\Finder::class,
- AbstractFixerTestCase::class,
- AbstractIntegrationTestCase::class,
- Tokens::class,
- ], true)
- ) {
- return false;
- }
- $interfaces = $rc->getInterfaces();
- $interfacesCount = \count($interfaces);
- if (0 === $interfacesCount) {
- return false;
- }
- if (1 === $interfacesCount) {
- $interface = reset($interfaces);
- if (\Stringable::class === $interface->getName()) {
- return false;
- }
- }
- return true;
- })
- );
- }
-
- public static function provideThatSrcClassHaveTestClassCases(): iterable
- {
- return array_map(
- static fn (string $item): array => [$item],
- array_filter(
- self::getSrcClasses(),
- static function (string $className): bool {
- $rc = new \ReflectionClass($className);
- return !$rc->isTrait() && !$rc->isAbstract() && !$rc->isInterface() && \count($rc->getMethods(\ReflectionMethod::IS_PUBLIC)) > 0;
- }
- )
- );
- }
- public function testAllTestsForShortOpenTagAreHandled(): void
- {
- $testClassesWithShortOpenTag = array_filter(
- self::getTestClasses(),
- fn (string $className): bool => str_contains($this->getFileContentForClass($className), 'short_open_tag') && self::class !== $className
- );
- $testFilesWithShortOpenTag = array_map(
- fn (string $className): string => './'.$this->getFilePathForClass($className),
- $testClassesWithShortOpenTag
- );
- $phpunitXmlContent = file_get_contents(__DIR__.'/../../phpunit.xml.dist');
- $phpunitFiles = (array) simplexml_load_string($phpunitXmlContent)->xpath('testsuites/testsuite[@name="short-open-tag"]')[0]->file;
- sort($testFilesWithShortOpenTag);
- sort($phpunitFiles);
- self::assertSame($testFilesWithShortOpenTag, $phpunitFiles);
- }
-
- public function testThatTestMethodsAreNotDuplicated(string $className): void
- {
- $class = new \ReflectionClass($className);
- $alreadyFoundMethods = [];
- $duplicates = [];
- foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
- if (!str_starts_with($method->getName(), 'test')) {
- continue;
- }
- $startLine = (int) $method->getStartLine();
- $length = (int) $method->getEndLine() - $startLine;
- if (3 === $length) {
- continue;
- }
-
- $source = file((string) $method->getFileName());
- $candidateContent = implode('', \array_slice($source, $startLine, $length));
- if (str_contains($candidateContent, '$this->doTest(')) {
- continue;
- }
- $foundInDuplicates = false;
- foreach ($alreadyFoundMethods as $methodKey => $methodContent) {
- if ($candidateContent === $methodContent) {
- $duplicates[] = \sprintf('%s is duplicate of %s', $methodKey, $method->getName());
- $foundInDuplicates = true;
- }
- }
- if (!$foundInDuplicates) {
- $alreadyFoundMethods[$method->getName()] = $candidateContent;
- }
- }
- self::assertSame(
- [],
- $duplicates,
- \sprintf(
- "Duplicated methods found in %s:\n - %s",
- $className,
- implode("\n - ", $duplicates)
- )
- );
- }
-
- public function testThatDataFromDataProvidersIsNotDuplicated(string $testClassName, string $dataProviderName): void
- {
- $exceptions = [
- 'PhpCsFixer\Tests\AutoReview\CommandTest::provideCommandHasNameConstCases',
- 'PhpCsFixer\Tests\AutoReview\DocumentationTest::provideFixerDocumentationFileIsUpToDateCases',
- 'PhpCsFixer\Tests\AutoReview\FixerFactoryTest::providePriorityIntegrationTestFilesAreListedInPriorityGraphCases',
- 'PhpCsFixer\Tests\Console\Command\DescribeCommandTest::provideExecuteOutputCases',
- 'PhpCsFixer\Tests\Console\Command\HelpCommandTest::provideGetDisplayableAllowedValuesCases',
- 'PhpCsFixer\Tests\Documentation\FixerDocumentGeneratorTest::provideGenerateRuleSetsDocumentationCases',
- 'PhpCsFixer\Tests\Fixer\Basic\EncodingFixerTest::provideFixCases',
- 'PhpCsFixer\Tests\UtilsTest::provideStableSortCases',
- ];
- if (\in_array($testClassName.'::'.$dataProviderName, $exceptions, true)) {
- $this->addToAssertionCount(1);
- return;
- }
- $dataProvider = new \ReflectionMethod($testClassName, $dataProviderName);
- $duplicates = [];
- $alreadyFoundCases = [];
- foreach ($dataProvider->invoke($dataProvider->getDeclaringClass()->newInstanceWithoutConstructor()) as $candidateKey => $candidateData) {
- $candidateData = serialize($candidateData);
- $foundInDuplicates = false;
- foreach ($alreadyFoundCases as $caseKey => $caseData) {
- if ($candidateData === $caseData) {
- $duplicates[] = \sprintf(
- 'Duplicate in %s::%s: %s and %s.'.PHP_EOL,
- $testClassName,
- $dataProviderName,
- \is_int($caseKey) ? '#'.$caseKey : '"'.$caseKey.'"',
- \is_int($candidateKey) ? '#'.$candidateKey : '"'.$candidateKey.'"',
- );
- $foundInDuplicates = true;
- }
- }
- if (!$foundInDuplicates) {
- $alreadyFoundCases[$candidateKey] = $candidateData;
- }
- }
- self::assertSame([], $duplicates);
- }
-
- public static function provideTestClassCases(): iterable
- {
- if (null === self::$testClassCases) {
- $cases = self::getTestClasses();
- self::$testClassCases = array_combine(
- $cases,
- array_map(static fn (string $case): array => [$case], $cases),
- );
- }
- yield from self::$testClassCases;
- }
-
- public static function provideThereIsNoPregFunctionUsedDirectlyCases(): iterable
- {
- return array_map(
- static fn (string $item): array => [$item],
- array_filter(
- self::getSrcClasses(),
- static fn (string $className): bool => Preg::class !== $className,
- ),
- );
- }
-
- public function testPhpUnitFixerExtendsAbstractPhpUnitFixer(string $className): void
- {
- $reflection = new \ReflectionClass($className);
- self::assertTrue($reflection->isSubclassOf(AbstractPhpUnitFixer::class));
- }
-
- public static function providePhpUnitFixerExtendsAbstractPhpUnitFixerCases(): iterable
- {
- $factory = new FixerFactory();
- $factory->registerBuiltInFixers();
- foreach ($factory->getFixers() as $fixer) {
- if (!str_starts_with($fixer->getName(), 'php_unit_')) {
- continue;
- }
-
- if ($fixer instanceof PhpUnitNamespacedFixer) {
- continue;
- }
- if ($fixer instanceof AbstractProxyFixer) {
- continue;
- }
- yield [\get_class($fixer)];
- }
- }
-
- public function testConstantsAreInUpperCase(string $className): void
- {
- $rc = new \ReflectionClass($className);
- $reflectionClassConstants = $rc->getReflectionConstants();
- if (\count($reflectionClassConstants) < 1) {
- $this->expectNotToPerformAssertions();
- return;
- }
- foreach ($reflectionClassConstants as $constant) {
- $constantName = $constant->getName();
- self::assertSame(strtoupper($constantName), $constantName, $className);
- }
- }
-
- private function extractFunctionNamesCalledInClass(string $className): array
- {
- $tokens = $this->createTokensForClass($className);
- $stringTokens = array_filter(
- $tokens->toArray(),
- static fn (Token $token): bool => $token->isGivenKind(T_STRING)
- );
- $strings = array_map(
- static fn (Token $token): string => $token->getContent(),
- $stringTokens
- );
- return array_unique($strings);
- }
-
- private function getFilePathForClass(string $className): string
- {
- $file = $className;
- $file = preg_replace('#^PhpCsFixer\\\Tests\\\#', 'tests\\', $file);
- $file = preg_replace('#^PhpCsFixer\\\#', 'src\\', $file);
- return str_replace('\\', \DIRECTORY_SEPARATOR, $file).'.php';
- }
-
- private function getFileContentForClass(string $className): string
- {
- return file_get_contents($this->getFilePathForClass($className));
- }
-
- private function createTokensForClass(string $className): Tokens
- {
- if (!isset(self::$tokensCache[$className])) {
- self::$tokensCache[$className] = Tokens::fromCode(self::getFileContentForClass($className));
- }
- return self::$tokensCache[$className];
- }
-
- private function getUsedDataProviderMethodNames(string $testClassName): iterable
- {
- foreach ($this->getAnnotationsOfTestClass($testClassName, 'dataProvider') as $methodName => $dataProviderAnnotation) {
- if (1 === preg_match('/@dataProvider\s+(?P<methodName>\w+)/', $dataProviderAnnotation->getContent(), $matches)) {
- yield $methodName => $matches['methodName'];
- }
- }
- }
-
- private function getAnnotationsOfTestClass(string $testClassName, string $annotation): iterable
- {
- $tokens = $this->createTokensForClass($testClassName);
- foreach ($tokens as $index => $token) {
- if (!$token->isGivenKind(T_DOC_COMMENT)) {
- continue;
- }
- $methodName = $tokens[$tokens->getNextTokenOfKind($index, [[T_STRING]])]->getContent();
- $docBlock = new DocBlock($token->getContent());
- $dataProviderAnnotations = $docBlock->getAnnotationsOfType($annotation);
- foreach ($dataProviderAnnotations as $dataProviderAnnotation) {
- yield $methodName => $dataProviderAnnotation;
- }
- }
- }
-
- private static function getSrcClasses(): array
- {
- static $classes;
- if (null !== $classes) {
- return $classes;
- }
- $finder = Finder::create()
- ->files()
- ->name('*.php')
- ->in(__DIR__.'/../../src')
- ->exclude([
- 'Resources',
- ])
- ;
-
- $classes = array_map(
- static fn (SplFileInfo $file): string => \sprintf(
- '%s\%s%s%s',
- 'PhpCsFixer',
- strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
- '' !== $file->getRelativePath() ? '\\' : '',
- $file->getBasename('.'.$file->getExtension())
- ),
- iterator_to_array($finder, false)
- );
- sort($classes);
- return $classes;
- }
-
- private static function getTestClasses(): array
- {
- static $classes;
- if (null !== $classes) {
- return $classes;
- }
- $finder = Finder::create()
- ->files()
- ->name('*Test.php')
- ->in(__DIR__.'/..')
- ->exclude([
- 'Fixtures',
- ])
- ;
-
- $classes = array_map(
- static fn (SplFileInfo $file): string => \sprintf(
- 'PhpCsFixer\Tests\%s%s%s',
- strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
- '' !== $file->getRelativePath() ? '\\' : '',
- $file->getBasename('.'.$file->getExtension())
- ),
- iterator_to_array($finder, false)
- );
- sort($classes);
- return $classes;
- }
-
- private function getPublicMethodNames(\ReflectionClass $rc): array
- {
- return array_map(
- static fn (\ReflectionMethod $rm): string => $rm->getName(),
- $rc->getMethods(\ReflectionMethod::IS_PUBLIC)
- );
- }
- }
|