* Dariusz RumiƄski * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace PhpCsFixer\Tests\Fixer\ClassNotation; use PhpCsFixer\Fixer\ClassNotation\ClassDefinitionFixer; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\WhitespacesFixerConfig; /** * @author SpacePossum * * @internal * * @covers \PhpCsFixer\Fixer\ClassNotation\ClassDefinitionFixer */ final class ClassDefinitionFixerTest extends AbstractFixerTestCase { public function testConfigureDefaultToNull(): void { $defaultConfig = [ 'multi_line_extends_each_single_line' => false, 'single_item_single_line' => false, 'single_line' => false, ]; $fixer = new ClassDefinitionFixer(); $fixer->configure($defaultConfig); static::assertConfigurationSame($defaultConfig, $fixer); $fixer->configure([]); static::assertConfigurationSame($defaultConfig, $fixer); } /** * @param string $expected PHP source code * @param string $input PHP source code * @param array $config * * @dataProvider provideAnonymousClassesCases * * @requires PHP 7.0 */ public function testFixingAnonymousClasses(string $expected, string $input, array $config = []): void { $this->fixer->configure($config); $this->doTest($expected, $input); } /** * @param string $expected PHP source code * @param string $input PHP source code * * @dataProvider provideClassesCases */ public function testFixingClasses(string $expected, string $input): void { $this->fixer->configure([]); $this->doTest($expected, $input); } /** * @param string $expected PHP source code * @param string $input PHP source code * @param array $config * * @dataProvider provideClassesWithConfigCases */ public function testFixingClassesWithConfig(string $expected, string $input, array $config): void { $this->fixer->configure($config); $this->doTest($expected, $input); } /** * @param string $expected PHP source code * @param string $input PHP source code * * @dataProvider provideInterfacesCases */ public function testFixingInterfaces(string $expected, string $input): void { $this->fixer->configure([]); $this->doTest($expected, $input); } /** * @param string $expected PHP source code * @param string $input PHP source code * * @dataProvider provideTraitsCases */ public function testFixingTraits(string $expected, string $input): void { $this->fixer->configure([]); $this->doTest($expected, $input); } public function testInvalidConfigurationKey(): void { $this->expectException(\PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class); $this->expectExceptionMessageMatches( '/^\[class_definition\] Invalid configuration: The option "a" does not exist\. Defined options are: "multi_line_extends_each_single_line", "single_item_single_line", "single_line"\.$/' ); $fixer = new ClassDefinitionFixer(); $fixer->configure(['a' => false]); } public function testInvalidConfigurationValueType(): void { $this->expectException(\PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class); $this->expectExceptionMessageMatches( '/^\[class_definition\] Invalid configuration: The option "single_line" with value "z" is expected to be of type "bool", but is of type "string"\.$/' ); $fixer = new ClassDefinitionFixer(); $fixer->configure(['single_line' => 'z']); } public function provideAnonymousClassesCases() { return [ [ ' true], ], [ " true], ], [ 'prop) {};', 'prop ){};', ], [ 'prop, $v[3], 4) {};', 'prop,$v[3], 4) {};', ], 'PSR-12 Extends/Implements Parenthesis on the next line.' => [ ' [ ' [ ' true], ], [ 'provideClassyCases('class'), $this->provideClassyExtendingCases('class'), $this->provideClassyImplementsCases() ); } public function provideClassesWithConfigCases() { return [ [ " true], ], [ " true], ], [ " false, 'single_item_single_line' => true], ], [ " true], ], [ " true], ], [ " true], ], [ " false, 'single_item_single_line' => true], ], [ " false, 'single_item_single_line' => false, 'multi_line_extends_each_single_line' => true, ], ], ]; } public function provideInterfacesCases() { $cases = array_merge( $this->provideClassyCases('interface'), $this->provideClassyExtendingCases('interface') ); $cases[] = [ 'provideClassyCases('trait'); } /** * @param string $source PHP source code * * @dataProvider provideClassyDefinitionInfoCases */ public function testClassyDefinitionInfo(string $source, array $expected): void { Tokens::clearCache(); $tokens = Tokens::fromCode($source); $method = new \ReflectionMethod($this->fixer, 'getClassyDefinitionInfo'); $method->setAccessible(true); $result = $method->invoke($this->fixer, $tokens, $expected['classy']); static::assertSame($expected, $result); } public function provideClassyDefinitionInfoCases() { return [ [ ' 1, 'classy' => 1, 'open' => 4, 'extends' => false, 'implements' => false, 'anonymousClass' => false, ], ], [ ' 1, 'classy' => 3, 'open' => 6, 'extends' => false, 'implements' => false, 'anonymousClass' => false, ], ], [ ' 1, 'classy' => 5, 'open' => 8, 'extends' => false, 'implements' => false, 'anonymousClass' => false, ], ], [ ' 1, 'classy' => 1, 'open' => 9, 'extends' => [ 'start' => 5, 'numberOfExtends' => 1, 'multiLine' => false, ], 'implements' => false, 'anonymousClass' => false, ], ], [ ' 1, 'classy' => 1, 'open' => 13, 'extends' => [ 'start' => 5, 'numberOfExtends' => 3, 'multiLine' => false, ], 'implements' => false, 'anonymousClass' => false, ], ], ]; } /** * @param string $source PHP source code * * @dataProvider provideClassyImplementsInfoCases */ public function testClassyInheritanceInfo(string $source, string $label, array $expected): void { $this->doTestClassyInheritanceInfo($source, $label, $expected); } /** * @param string $source PHP source code * * @requires PHP 7.0 * @dataProvider provideClassyInheritanceInfo7Cases */ public function testClassyInheritanceInfo7(string $source, string $label, array $expected): void { $this->doTestClassyInheritanceInfo($source, $label, $expected); } public function provideClassyImplementsInfoCases() { $tests = [ '1' => [ ' 5, 'numberOfImplements' => 3, 'multiLine' => false], ], '2' => [ ' 5, 'numberOfImplements' => 3, 'multiLine' => false], ], '3' => [ ' 5, 'numberOfImplements' => 1, 'multiLine' => false], ], '4' => [ " 5, 'numberOfImplements' => 2, 'multiLine' => true], ], '5' => [ " 5, 'numberOfImplements' => 3, 'multiLine' => false], ], ]; foreach ($tests as $index => $test) { yield $index => $test; } if (\PHP_VERSION_ID < 80000) { $multiLine = true; $code = 'test(); }'; } else { $multiLine = false; $code = 'test(); }'; } yield [ $code, 'numberOfImplements', ['start' => 36, 'numberOfImplements' => 2, 'multiLine' => $multiLine], ]; } public function provideClassyInheritanceInfo7Cases() { return [ [ " 12, 'numberOfExtends' => 1, 'multiLine' => true], ], [ " 16, 'numberOfImplements' => 2, 'multiLine' => false], ], [ " 12, 'numberOfExtends' => 1, 'multiLine' => true], ], ]; } /** * @dataProvider providePHP7Cases * @requires PHP 7.0 */ public function testFixPHP7(string $expected, ?string $input = null): void { $this->fixer->configure([]); $this->doTest($expected, $input); } public function providePHP7Cases() { return [ [ '', '', ], ]; } /** * @dataProvider provideMessyWhitespacesCases */ public function testMessyWhitespaces(string $expected, ?string $input = null): void { $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t", "\r\n")); $this->fixer->configure([]); $this->doTest($expected, $input); } public function provideMessyWhitespacesCases() { return [ [ "setAccessible(true); static::assertSame($expected, $reflectionProperty->getValue($fixer)); } private function doTestClassyInheritanceInfo(string $source, string $label, array $expected): void { Tokens::clearCache(); $tokens = Tokens::fromCode($source); static::assertTrue($tokens[$expected['start']]->isGivenKind([T_IMPLEMENTS, T_EXTENDS]), sprintf('Token must be "implements" or "extends", got "%s".', $tokens[$expected['start']]->getContent())); $method = new \ReflectionMethod($this->fixer, 'getClassyInheritanceInfo'); $method->setAccessible(true); $result = $method->invoke($this->fixer, $tokens, $expected['start'], $label); static::assertSame($expected, $result); } private function provideClassyCases(string $classy) { return [ [ sprintf("