* 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\Import; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\Import\OrderedImportsFixer; use PhpCsFixer\Tests\Test\AbstractFixerTestCase; /** * @internal * * @covers \PhpCsFixer\Fixer\Import\OrderedImportsFixer * * @extends AbstractFixerTestCase<\PhpCsFixer\Fixer\Import\OrderedImportsFixer> * * @phpstan-import-type _AutogeneratedInputConfiguration from \PhpCsFixer\Fixer\Import\OrderedImportsFixer */ final class OrderedImportsFixerTest extends AbstractFixerTestCase { /** * @dataProvider provideFixWithMultipleNamespaceCases */ public function testFixWithMultipleNamespace(string $expected, string $input): void { $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixWithMultipleNamespaceCases(): iterable { $expected = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } namespace BlaRoo { use Foo\Zar\Baz; use SomeClass; use Symfony\Annotation\Template; use Symfony\Doctrine\Entities\Entity, Zoo\Bar; class AnnotatedClass { /** * @Template(foobar=21) * @param Entity $foo */ public function doSomething($foo) { $bar = $foo->toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } EOF; $input = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } namespace BlaRoo { use Foo\Zar\Baz; use Zoo\Bar; use SomeClass; use Symfony\Annotation\Template, Symfony\Doctrine\Entities\Entity; class AnnotatedClass { /** * @Template(foobar=21) * @param Entity $foo */ public function doSomething($foo) { $bar = $foo->toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } EOF; yield [$expected, $input]; $expected = <<<'EOF' [ ' output ', ' output ', ]; } public function testFixWithComment(): void { $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $input = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $this->doTest($expected, $input); } public function testWithTraits(): void { $expected = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $input = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $this->doTest($expected, $input); } public function testFixWithTraitImports(): void { $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $baz) {}; } } EOF; $input = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $baz) {}; } } EOF; $this->doTest($expected, $input); } public function testFixWithDifferentCases(): void { $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Baz; use abc\Bar; doTest($expected, $input); } public function testWithoutUses(): void { $expected = <<<'EOF' doTest($expected); } public function testOrderWithTrailingDigit(): void { $expected = <<<'EOF' doTest($expected, $input); } public function testCodeWithImportsOnly(): void { $expected = <<<'EOF' doTest($expected, $input); } public function testCodeWithCloseTag(): void { $this->doTest( '', '' ); } public function testCodeWithComments(): void { $this->doTest( 'doTest( 'fixer->configure($config); $this->doTest($expected, $input); } public static function provideFixCases(): iterable { yield [ <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar as ZooBar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF, <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar as ZooBar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF, ]; yield [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'const', 'function'], ], ]; yield [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'const', 'function'], ], ]; yield [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'function', 'const'], ], ]; yield [ ' [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'function', 'const'], ], ]; yield 'alpha - [\'class\', \'const\', \'function\']' => [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'const', 'function'], ], ]; yield 'alpha - [\'function\', \'class\', \'const\']' => [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['function', 'class', 'const'], ], ]; yield 'alpha - [\'function\', \'const\', \'class\']' => [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['function', 'const', 'class'], ], ]; yield 'alpha - [\'const\', \'function\', \'class\']' => [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['const', 'function', 'class'], ], ]; yield 'alpha - [\'const\', \'class\', \'function\']' => [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['const', 'class', 'function'], ], ]; yield '"strcasecmp" vs. "strnatcasecmp"' => [ ' OrderedImportsFixer::SORT_ALPHA, ], ]; yield [ ' OrderedImportsFixer::SORT_ALPHA, 'imports_order' => [OrderedImportsFixer::IMPORT_TYPE_CLASS, OrderedImportsFixer::IMPORT_TYPE_CONST, OrderedImportsFixer::IMPORT_TYPE_FUNCTION], ], ]; yield [ ' OrderedImportsFixer::SORT_LENGTH, 'imports_order' => [OrderedImportsFixer::IMPORT_TYPE_CLASS, OrderedImportsFixer::IMPORT_TYPE_CONST, OrderedImportsFixer::IMPORT_TYPE_FUNCTION], ], ]; yield [ ' OrderedImportsFixer::SORT_NONE, 'imports_order' => [OrderedImportsFixer::IMPORT_TYPE_CLASS, OrderedImportsFixer::IMPORT_TYPE_CONST, OrderedImportsFixer::IMPORT_TYPE_FUNCTION], ], ]; yield [ 'expectException(InvalidFixerConfigurationException::class); $this->expectExceptionMessage('[ordered_imports] Invalid configuration: Unknown sort types "foo" and "bar".'); $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'const', 'function', 'foo', 'bar'], ]); } /* |-------------------------------------------------------------------------- | Test sorting by length |-------------------------------------------------------------------------- */ public function testInvalidOrderTypesSize(): void { $this->expectException(InvalidFixerConfigurationException::class); $this->expectExceptionMessage('[ordered_imports] Invalid configuration: Missing sort type "function".'); $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['class', 'const'], ]); } public function testInvalidOrderType(): void { $this->expectException(InvalidFixerConfigurationException::class); $this->expectExceptionMessage('[ordered_imports] Invalid configuration: Missing sort type "class".'); $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_ALPHA, 'imports_order' => ['const', 'function', 'bar'], ]); } /** * @param _AutogeneratedInputConfiguration $configuration * * @dataProvider provideInvalidSortAlgorithmCases */ public function testInvalidSortAlgorithm(array $configuration, string $expectedValue): void { $this->expectException(InvalidFixerConfigurationException::class); $this->expectExceptionMessage(\sprintf( '[ordered_imports] Invalid configuration: The option "sort_algorithm" with value %s is invalid. Accepted values are: "alpha", "length", "none".', $expectedValue )); $this->fixer->configure($configuration); } public static function provideInvalidSortAlgorithmCases(): iterable { yield [ [ 'sort_algorithm' => 'dope', 'imports_order' => null, ], '"dope"', ]; yield [ [ 'sort_algorithm' => [OrderedImportsFixer::SORT_ALPHA, OrderedImportsFixer::SORT_LENGTH], 'imports_order' => null, ], 'array', ]; yield [ [ 'sort_algorithm' => new \stdClass(), 'imports_order' => null, ], \stdClass::class, ]; } public function testByLengthFixWithSameLength(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $input = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $this->doTest($expected, $input); } public function testByLengthFixWithSameLengthAndCaseSensitive(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, 'case_sensitive' => true, ]); $expected = <<<'EOF' doTest($expected, $input); } public function testByLengthFixWithMultipleNamespace(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } namespace BlaRoo { use Zoo\Bar; use SomeClass; use Foo\Zar\Baz; use Symfony\Annotation\Template, Symfony\Doctrine\Entities\Entity; class AnnotatedClass { /** * @Template(foobar=21) * @param Entity $foo */ public function doSomething($foo) { $bar = $foo->toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } EOF; $input = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } namespace BlaRoo { use Foo\Zar\Baz; use Zoo\Bar; use SomeClass; use Symfony\Annotation\Template, Symfony\Doctrine\Entities\Entity; class AnnotatedClass { /** * @Template(foobar=21) * @param Entity $foo */ public function doSomething($foo) { $bar = $foo->toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } } EOF; $this->doTest($expected, $input); } public function testByLengthFixWithComment(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $input = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $this->doTest($expected, $input); } public function testByLength(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $input = <<<'EOF' toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $this->doTest($expected, $input); } public function testByLengthFixWithTraitImports(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $baz) {}; } } EOF; $input = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $baz) {}; } } EOF; $this->doTest($expected, $input); } public function testByLengthFixWithDifferentCases(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Baz; use abc\Bar; doTest($expected, $input); } public function testByLengthOrderWithTrailingDigit(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' doTest($expected, $input); } public function testByLengthCodeWithImportsOnly(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' doTest($expected, $input); } public function testByLengthWithoutUses(): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $expected = <<<'EOF' doTest($expected); } /** * @dataProvider provideFixByLengthCases */ public function testFixByLength(string $expected, ?string $input = null): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => null, ]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixByLengthCases(): iterable { yield [ <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar as ZooBar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF, <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar as ZooBar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF, ]; yield [ 'fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_LENGTH, 'imports_order' => [OrderedImportsFixer::IMPORT_TYPE_CLASS, OrderedImportsFixer::IMPORT_TYPE_CONST, OrderedImportsFixer::IMPORT_TYPE_FUNCTION], ]); $this->doTest($expected, $input); } /** * @return iterable */ public static function provideFixTypesOrderAndLengthCases(): iterable { yield [ ' $importOrder */ public function testFixTypesOrderAndAlphabet(string $expected, ?string $input = null, ?array $importOrder = null): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_ALPHA, 'imports_order' => $importOrder, ]); $this->doTest($expected, $input); } public static function provideFixTypesOrderAndAlphabetCases(): iterable { yield [ ' $importOrder */ public function testFixTypesOrderAndNone(string $expected, ?string $input = null, ?array $importOrder = null): void { $this->fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_NONE, 'imports_order' => $importOrder, ]); $this->doTest($expected, $input); } public static function provideFixTypesOrderAndNoneCases(): iterable { yield [ 'fixer->configure([ 'sort_algorithm' => OrderedImportsFixer::SORT_NONE, 'imports_order' => null, ]); $expected = <<<'EOF' The normal use of this fixer should not change this sentence nor those statements below use Zoo\Bar as ZooBar; use Foo\Bar; use Foo\Zar\Baz; toArray(); /** @var ArrayInterface $bar */ return function () use ($bar, $foo) {}; } } EOF; $this->doTest($expected); } public function testFixWithCaseSensitive(): void { $this->fixer->configure([ 'case_sensitive' => true, ]); $expected = <<<'EOF' doTest($expected, $input); } }