123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- <?php
- /*
- * 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\Tests\AutoReview;
- if (!class_exists(\PHPUnit\Runner\Version::class)) {
- class_alias('PHPUnit_Runner_Version', \PHPUnit\Runner\Version::class);
- }
- use PhpCsFixer\DocBlock\DocBlock;
- use PhpCsFixer\Preg;
- use PhpCsFixer\Tests\TestCase;
- use PhpCsFixer\Tokenizer\Token;
- use PhpCsFixer\Tokenizer\Tokens;
- use Symfony\Component\Finder\Finder;
- use Symfony\Component\Finder\SplFileInfo;
- /**
- * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * @internal
- *
- * @coversNothing
- * @group auto-review
- * @group covers-nothing
- */
- final class ProjectCodeTest extends TestCase
- {
- /**
- * This structure contains older classes that are not yet covered by tests.
- *
- * It may only shrink, never add anything to it.
- *
- * @var string[]
- */
- private static $classesWithoutTests = [
- \PhpCsFixer\Console\SelfUpdate\GithubClient::class,
- \PhpCsFixer\Doctrine\Annotation\Tokens::class,
- \PhpCsFixer\Fixer\Operator\AlignDoubleArrowFixerHelper::class,
- \PhpCsFixer\Fixer\Operator\AlignEqualsFixerHelper::class,
- \PhpCsFixer\Fixer\Whitespace\NoExtraConsecutiveBlankLinesFixer::class,
- \PhpCsFixer\Runner\FileCachingLintingIterator::class,
- \PhpCsFixer\Test\AccessibleObject::class,
- ];
- public function testThatClassesWithoutTestsVarIsProper()
- {
- $unknownClasses = array_filter(
- self::$classesWithoutTests,
- static function ($class) { return !class_exists($class) && !trait_exists($class); }
- );
- static::assertSame([], $unknownClasses);
- }
- /**
- * @param string $className
- *
- * @dataProvider provideSrcConcreteClassCases
- */
- public function testThatSrcClassHaveTestClass($className)
- {
- $testClassName = str_replace('PhpCsFixer', 'PhpCsFixer\\Tests', $className).'Test';
- if (\in_array($className, self::$classesWithoutTests, true)) {
- static::assertFalse(class_exists($testClassName), sprintf('Class "%s" already has tests, so it should be removed from "%s::$classesWithoutTests".', $className, __CLASS__));
- static::markTestIncomplete(sprintf('Class "%s" has no tests yet, please help and add it.', $className));
- }
- static::assertTrue(class_exists($testClassName), sprintf('Expected test class "%s" for "%s" not found.', $testClassName, $className));
- static::assertTrue(is_subclass_of($testClassName, TestCase::class), sprintf('Expected test class "%s" to be a subclass of "\PhpCsFixer\Tests\TestCase".', $testClassName));
- }
- /**
- * @param string $className
- *
- * @dataProvider provideSrcClassesNotAbuseInterfacesCases
- */
- public function testThatSrcClassesNotAbuseInterfaces($className)
- {
- $rc = new \ReflectionClass($className);
- $allowedMethods = array_map(
- function (\ReflectionClass $interface) {
- return $this->getPublicMethodNames($interface);
- },
- $rc->getInterfaces()
- );
- if (\count($allowedMethods)) {
- $allowedMethods = array_unique(array_merge(...array_values($allowedMethods)));
- }
- $allowedMethods[] = '__construct';
- $allowedMethods[] = '__destruct';
- $allowedMethods[] = '__wakeup';
- $exceptionMethods = [
- 'configure', // due to AbstractFixer::configure
- 'getConfigurationDefinition', // due to AbstractFixer::getConfigurationDefinition
- 'getDefaultConfiguration', // due to AbstractFixer::getDefaultConfiguration
- 'setWhitespacesConfig', // due to AbstractFixer::setWhitespacesConfig
- ];
- // @TODO: 3.0 should be removed
- $exceptionMethodsPerClass = [
- \PhpCsFixer\Config::class => ['create'],
- \PhpCsFixer\Event\Event::class => ['stopPropagation'],
- \PhpCsFixer\Fixer\FunctionNotation\MethodArgumentSpaceFixer::class => ['fixSpace'],
- ];
- $definedMethods = $this->getPublicMethodNames($rc);
- $extraMethods = array_diff(
- $definedMethods,
- $allowedMethods,
- $exceptionMethods,
- isset($exceptionMethodsPerClass[$className]) ? $exceptionMethodsPerClass[$className] : []
- );
- sort($extraMethods);
- static::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 function ($item) {
- return " * {$item}";
- }, $extraMethods))
- )
- );
- }
- /**
- * @param string $className
- *
- * @dataProvider provideSrcClassCases
- */
- public function testThatSrcClassesNotExposeProperties($className)
- {
- $rc = new \ReflectionClass($className);
- if (\PhpCsFixer\Fixer\Alias\NoMixedEchoPrintFixer::class === $className) {
- static::markTestIncomplete(sprintf(
- 'Public properties of fixer `%s` will be removed on 3.0.',
- \PhpCsFixer\Fixer\Alias\NoMixedEchoPrintFixer::class
- ));
- }
- static::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 function (\ReflectionProperty $item) {
- return $item->getName();
- }, $allowedProps);
- $definedProps = array_map(static function (\ReflectionProperty $item) {
- return $item->getName();
- }, $definedProps);
- $exceptionPropsPerClass = [
- \PhpCsFixer\AbstractPhpdocTypesFixer::class => ['tags'],
- \PhpCsFixer\AbstractAlignFixerHelper::class => ['deepestLevel'],
- \PhpCsFixer\AbstractFixer::class => ['configuration', 'configurationDefinition', 'whitespacesConfig'],
- \PhpCsFixer\AbstractProxyFixer::class => ['proxyFixers'],
- \PhpCsFixer\Test\AbstractFixerTestCase::class => ['fixer', 'linter'],
- \PhpCsFixer\Test\AbstractIntegrationTestCase::class => ['linter'],
- ];
- $extraProps = array_diff(
- $definedProps,
- $allowedProps,
- isset($exceptionPropsPerClass[$className]) ? $exceptionPropsPerClass[$className] : []
- );
- sort($extraProps);
- static::assertEmpty(
- $extraProps,
- sprintf(
- "Class '%s' should not have protected properties.\nViolations:\n%s",
- $className,
- implode("\n", array_map(static function ($item) {
- return " * {$item}";
- }, $extraProps))
- )
- );
- }
- /**
- * @dataProvider provideTestClassCases
- *
- * @param string $testClassName
- */
- public function testThatTestClassesAreTraitOrAbstractOrFinal($testClassName)
- {
- $rc = new \ReflectionClass($testClassName);
- static::assertTrue(
- $rc->isTrait() || $rc->isAbstract() || $rc->isFinal(),
- sprintf('Test class %s should be trait, abstract or final.', $testClassName)
- );
- }
- /**
- * @dataProvider provideTestClassCases
- *
- * @param string $testClassName
- */
- public function testThatTestClassesAreInternal($testClassName)
- {
- $rc = new \ReflectionClass($testClassName);
- $doc = new DocBlock($rc->getDocComment());
- static::assertNotEmpty(
- $doc->getAnnotationsOfType('internal'),
- sprintf('Test class %s should have internal annotation.', $testClassName)
- );
- }
- /**
- * @dataProvider provideTestClassCases
- *
- * @param string $testClassName
- */
- public function testThatPublicMethodsAreCorrectlyNamed($testClassName)
- {
- $reflectionClass = new \ReflectionClass($testClassName);
- $publicMethods = array_filter(
- $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
- static function (\ReflectionMethod $reflectionMethod) use ($reflectionClass) {
- return $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName();
- }
- );
- if ($publicMethods === []) {
- $this->addToAssertionCount(1); // no methods to test, all good!
- }
- foreach ($publicMethods as $method) {
- static::assertRegExp(
- '/^(test|provide|setUpBeforeClass$|tearDownAfterClass$)/',
- $method->getName(),
- sprintf('Public method "%s::%s" is not properly named.', $reflectionClass->getName(), $method->getName())
- );
- }
- }
- /**
- * @dataProvider provideTestClassCases
- *
- * @param string $testClassName
- */
- public function testThatDataProvidersAreCorrectlyNamed($testClassName)
- {
- $usedDataProviderMethodNames = $this->getUsedDataProviderMethodNames($testClassName);
- if (empty($dataProviderMethodNames)) {
- $this->addToAssertionCount(1); // no data providers to test, all good!
- }
- foreach ($usedDataProviderMethodNames as $dataProviderMethodName) {
- static::assertRegExp('/^provide[A-Z]\S+Cases$/', $dataProviderMethodName, sprintf(
- 'Data provider in "%s" with name "%s" is not correctly named.',
- $testClassName,
- $dataProviderMethodName
- ));
- }
- }
- /**
- * @dataProvider provideTestClassCases
- *
- * @param string $testClassName
- */
- public function testThatDataProvidersAreUsed($testClassName)
- {
- $reflectionClass = new \ReflectionClass($testClassName);
- $definedDataProviders = array_filter(
- $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
- static function (\ReflectionMethod $reflectionMethod) use ($reflectionClass) {
- return $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName()
- && 'provide' === substr($reflectionMethod->getName(), 0, 7);
- }
- );
- if ($definedDataProviders === []) {
- $this->addToAssertionCount(1); // no methods to test, all good!
- }
- $usedDataProviderMethodNames = $this->getUsedDataProviderMethodNames($testClassName);
- foreach ($definedDataProviders as $definedDataProvider) {
- static::assertContains(
- $definedDataProvider->getName(),
- $usedDataProviderMethodNames,
- sprintf('Data provider in "%s" with name "%s" is not used.', $definedDataProvider->getDeclaringClass()->getName(), $definedDataProvider->getName())
- );
- }
- }
- /**
- * @dataProvider provideClassesWherePregFunctionsAreForbiddenCases
- *
- * @param string $className
- */
- public function testThereIsNoPregFunctionUsedDirectly($className)
- {
- $rc = new \ReflectionClass($className);
- $tokens = Tokens::fromCode(file_get_contents($rc->getFileName()));
- $stringTokens = array_filter(
- $tokens->toArray(),
- static function (Token $token) {
- return $token->isGivenKind(T_STRING);
- }
- );
- $strings = array_map(
- static function (Token $token) {
- return $token->getContent();
- },
- $stringTokens
- );
- $strings = array_unique($strings);
- $message = sprintf('Class %s must not use preg_*, it shall use Preg::* instead.', $className);
- static::assertNotContains('preg_filter', $strings, $message);
- static::assertNotContains('preg_grep', $strings, $message);
- static::assertNotContains('preg_match', $strings, $message);
- static::assertNotContains('preg_match_all', $strings, $message);
- static::assertNotContains('preg_replace', $strings, $message);
- static::assertNotContains('preg_replace_callback', $strings, $message);
- static::assertNotContains('preg_split', $strings, $message);
- }
- /**
- * @dataProvider provideTestClassCases
- *
- * @param string $testClassName
- */
- public function testExpectedInputOrder($testClassName)
- {
- $reflectionClass = new \ReflectionClass($testClassName);
- $publicMethods = array_filter(
- $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC),
- static function (\ReflectionMethod $reflectionMethod) use ($reflectionClass) {
- return $reflectionMethod->getDeclaringClass()->getName() === $reflectionClass->getName();
- }
- );
- if ($publicMethods === []) {
- $this->addToAssertionCount(1); // no methods to test, all good!
- return;
- }
- /** @var \ReflectionMethod $method */
- foreach ($publicMethods as $method) {
- $parameters = $method->getParameters();
- if (\count($parameters) < 2) {
- $this->addToAssertionCount(1); // not enough parameters to test, all good!
- 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;
- }
- }
- $expected = array_filter($expected);
- if (\count($expected) < 2) {
- $this->addToAssertionCount(1); // not enough parameters to test, all good!
- continue;
- }
- static::assertLessThan(
- $expected['input'],
- $expected['expected'],
- sprintf('Public method "%s::%s" has parameter \'input\' before \'expected\'.', $reflectionClass->getName(), $method->getName())
- );
- }
- }
- public function provideSrcClassCases()
- {
- return array_map(
- static function ($item) {
- return [$item];
- },
- $this->getSrcClasses()
- );
- }
- public function provideSrcClassesNotAbuseInterfacesCases()
- {
- return array_map(
- static function ($item) {
- return [$item];
- },
- array_filter($this->getSrcClasses(), static function ($className) {
- $rc = new \ReflectionClass($className);
- $doc = false !== $rc->getDocComment()
- ? new DocBlock($rc->getDocComment())
- : null;
- if (
- $rc->isInterface()
- || ($doc && \count($doc->getAnnotationsOfType('internal')))
- || 0 === \count($rc->getInterfaces())
- || \in_array($className, [
- \PhpCsFixer\Finder::class,
- \PhpCsFixer\Test\AbstractFixerTestCase::class,
- \PhpCsFixer\Test\AbstractIntegrationTestCase::class,
- \PhpCsFixer\Tests\Test\AbstractFixerTestCase::class,
- \PhpCsFixer\Tests\Test\AbstractIntegrationTestCase::class,
- \PhpCsFixer\Tokenizer\Tokens::class,
- ], true)
- ) {
- return false;
- }
- return true;
- })
- );
- }
- public function provideSrcConcreteClassCases()
- {
- return array_map(
- static function ($item) { return [$item]; },
- array_filter(
- $this->getSrcClasses(),
- static function ($className) {
- $rc = new \ReflectionClass($className);
- return !$rc->isAbstract() && !$rc->isInterface();
- }
- )
- );
- }
- public function provideTestClassCases()
- {
- return array_map(
- static function ($item) {
- return [$item];
- },
- $this->getTestClasses()
- );
- }
- public function provideClassesWherePregFunctionsAreForbiddenCases()
- {
- return array_map(
- static function ($item) {
- return [$item];
- },
- array_filter(
- $this->getSrcClasses(),
- static function ($className) {
- return Preg::class !== $className;
- }
- )
- );
- }
- private function getUsedDataProviderMethodNames($testClassName)
- {
- $dataProviderMethodNames = [];
- $tokens = Tokens::fromCode(file_get_contents(
- str_replace('\\', \DIRECTORY_SEPARATOR, preg_replace('#^PhpCsFixer\\\Tests#', 'tests', $testClassName)).'.php'
- ));
- foreach ($tokens as $token) {
- if ($token->isGivenKind(T_DOC_COMMENT)) {
- $docBlock = new DocBlock($token->getContent());
- $dataProviderAnnotations = $docBlock->getAnnotationsOfType('dataProvider');
- foreach ($dataProviderAnnotations as $dataProviderAnnotation) {
- if (1 === preg_match('/@dataProvider\s+(?P<methodName>\w+)/', $dataProviderAnnotation->getContent(), $matches)) {
- $dataProviderMethodNames[] = $matches['methodName'];
- }
- }
- }
- }
- return array_unique($dataProviderMethodNames);
- }
- private function getSrcClasses()
- {
- static $classes;
- if (null !== $classes) {
- return $classes;
- }
- $finder = Finder::create()
- ->files()
- ->name('*.php')
- ->in(__DIR__.'/../../src')
- ->exclude([
- 'Resources',
- ])
- ;
- $classes = array_map(
- static function (SplFileInfo $file) {
- return 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 function getTestClasses()
- {
- static $classes;
- if (null !== $classes) {
- return $classes;
- }
- $finder = Finder::create()
- ->files()
- ->name('*.php')
- ->in(__DIR__.'/..')
- ->exclude([
- 'Fixtures',
- ])
- ;
- $classes = array_map(
- static function (SplFileInfo $file) {
- return sprintf(
- 'PhpCsFixer\\Tests\\%s%s%s',
- strtr($file->getRelativePath(), \DIRECTORY_SEPARATOR, '\\'),
- $file->getRelativePath() ? '\\' : '',
- $file->getBasename('.'.$file->getExtension())
- );
- },
- iterator_to_array($finder, false)
- );
- $classes = array_filter($classes, static function ($class) {
- return is_subclass_of($class, TestCase::class);
- });
- sort($classes);
- return $classes;
- }
- /**
- * @return string[]
- */
- private function getPublicMethodNames(\ReflectionClass $rc)
- {
- return array_map(
- static function (\ReflectionMethod $rm) {
- return $rm->getName();
- },
- $rc->getMethods(\ReflectionMethod::IS_PUBLIC)
- );
- }
- }
|