Browse Source

Enhancement: Allow sorting of property, property-read, and property-write annotations by value
Enhancement: Allow sorting of method annotations by value

Andreas Möller 4 years ago
parent
commit
f98de85f88

+ 1 - 1
doc/rules/phpdoc/phpdoc_order_by_value.rst

@@ -12,7 +12,7 @@ Configuration
 
 List of annotations to order, e.g. ``["covers"]``.
 
-Allowed values: a subset of ``['author', 'covers', 'coversNothing', 'dataProvider', 'depends', 'group', 'internal', 'requires', 'throws', 'uses']``
+Allowed values: a subset of ``['author', 'covers', 'coversNothing', 'dataProvider', 'depends', 'group', 'internal', 'method', 'property', 'property-read', 'property-write', 'requires', 'throws', 'uses']``
 
 Default value: ``['covers']``
 

+ 36 - 7
src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php

@@ -23,6 +23,7 @@ use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\Preg;
 use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
+use Symfony\Component\OptionsResolver\Options;
 
 /**
  * @author Filippo Tessarotto <zoeslam@gmail.com>
@@ -96,7 +97,7 @@ final class MyTest extends \PHPUnit_Framework_TestCase
         }
 
         for ($index = $tokens->count() - 1; $index > 0; --$index) {
-            foreach ($this->configuration['annotations'] as $type) {
+            foreach ($this->configuration['annotations'] as $type => $typeLowerCase) {
                 $findPattern = sprintf(
                     '/@%s\s.+@%s\s/s',
                     $type,
@@ -113,20 +114,33 @@ final class MyTest extends \PHPUnit_Framework_TestCase
                 $docBlock = new DocBlock($tokens[$index]->getContent());
 
                 $annotations = $docBlock->getAnnotationsOfType($type);
-
                 $annotationMap = [];
 
-                $replacePattern = sprintf(
-                    '/\*\s*@%s\s+(.+)/',
-                    $type
-                );
+                if (\in_array($type, ['property', 'property-read', 'property-write'], true)) {
+                    $replacePattern = sprintf(
+                        '/(?s)\*\s*@%s\s+(?P<optionalTypes>.+\s+)?\$(?P<comparableContent>[^\s]+).*/',
+                        $type
+                    );
+
+                    $replacement = '\2';
+                } elseif ('method' === $type) {
+                    $replacePattern = '/(?s)\*\s*@method\s+(?P<optionalReturnTypes>.+\s+)?(?P<comparableContent>.+)\(.*/';
+                    $replacement = '\2';
+                } else {
+                    $replacePattern = sprintf(
+                        '/\*\s*@%s\s+(?P<comparableContent>.+)/',
+                        $typeLowerCase
+                    );
+
+                    $replacement = '\1';
+                }
 
                 foreach ($annotations as $annotation) {
                     $rawContent = $annotation->getContent();
 
                     $comparableContent = Preg::replace(
                         $replacePattern,
-                        '\1',
+                        $replacement,
                         strtolower(trim($rawContent))
                     );
 
@@ -167,6 +181,10 @@ final class MyTest extends \PHPUnit_Framework_TestCase
             'depends',
             'group',
             'internal',
+            'method',
+            'property',
+            'property-read',
+            'property-write',
             'requires',
             'throws',
             'uses',
@@ -180,6 +198,17 @@ final class MyTest extends \PHPUnit_Framework_TestCase
                 ->setAllowedValues([
                     new AllowedValueSubset($allowedValues),
                 ])
+                ->setNormalizer(function (Options $options, $value) {
+                    $normalized = [];
+
+                    foreach ($value as $index => $annotation) {
+                        // since we will be using strtolower on the input annotations when building the sorting
+                        // map we must match the type in lower case as well
+                        $normalized[$annotation] = strtolower($annotation);
+                    }
+
+                    return $normalized;
+                })
                 ->setDefault([
                     'covers',
                 ])

+ 460 - 0
tests/Fixer/Phpdoc/PhpdocOrderByValueFixerTest.php

@@ -930,6 +930,466 @@ final class PhpdocOrderByValueFixerTest extends AbstractFixerTestCase
         ];
     }
 
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixWithMethodCases
+     */
+    public function testFixWithMethod($expected, $input = null)
+    {
+        $this->fixer->configure([
+            'annotations' => [
+                'method',
+            ],
+        ]);
+
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixWithMethodCases()
+    {
+        return [
+            'skip on 1 or 0 occurrences' => [
+                '<?php
+                /**
+                 * @method int foo(array $b)
+                 */
+                class Foo {}
+                ',
+            ],
+            'base case' => [
+                '<?php
+                /**
+                 * @method bool bar(int $a, bool $b)
+                 * @method array|null baz()
+                 * @method int foo(array $b)
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @method int foo(array $b)
+                 * @method bool bar(int $a, bool $b)
+                 * @method array|null baz()
+                 */
+                class Foo {}
+                ',
+            ],
+            'preserve positions if other docblock parts are present' => [
+                '<?php
+                /**
+                 * Comment 1
+                 * @method bool bar(int $a, bool $b)
+                 * Comment 3
+                 * @method int foo(array $b)
+                 * Comment 2
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * Comment 1
+                 * @method int foo(array $b)
+                 * Comment 2
+                 * @method bool bar(int $a, bool $b)
+                 * Comment 3
+                 */
+                class Foo {}
+                ',
+            ],
+            'case-insensitive' => [
+                '<?php
+                /**
+                 * @method int A()
+                 * @method bool b()
+                 * @method array|null c()
+                 * @method float D()
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @method array|null c()
+                 * @method float D()
+                 * @method bool b()
+                 * @method int A()
+                 */
+                class Foo {}
+                ',
+            ],
+            'with-possibly-unexpected-comparable' => [
+                '<?php
+                /**
+                 * @method int foo(Z $b)
+                 * @method int fooA( $b)
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @method int fooA( $b)
+                 * @method int foo(Z $b)
+                 */
+                class Foo {}
+                ',
+            ],
+            'with-and-without-types' => [
+                '<?php
+                /**
+                 * @method int A()
+                 * @method b()
+                 * @method array|null c()
+                 * @method D()
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @method array|null c()
+                 * @method D()
+                 * @method b()
+                 * @method int A()
+                 */
+                class Foo {}
+                ',
+            ],
+        ];
+    }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixWithPropertyCases
+     */
+    public function testFixWithProperty($expected, $input = null)
+    {
+        $this->fixer->configure([
+            'annotations' => [
+                'property',
+            ],
+        ]);
+
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixWithPropertyCases()
+    {
+        return [
+            'skip on 1 or 0 occurrences' => [
+                '<?php
+                /**
+                 * @property int|\stdClass $foo
+                 */
+                class Foo {}
+                ',
+            ],
+            'base case' => [
+                '<?php
+                /**
+                 * @property bool $bar
+                 * @property array|null $baz
+                 * @property int|\stdClass $foo
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property int|\stdClass $foo
+                 * @property bool $bar
+                 * @property array|null $baz
+                 */
+                class Foo {}
+                ',
+            ],
+            'preserve positions if other docblock parts are present' => [
+                '<?php
+                /**
+                 * Comment 1
+                 * @property bool $bar
+                 * Comment 3
+                 * @property int|\stdClass $foo
+                 * Comment 2
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * Comment 1
+                 * @property int|\stdClass $foo
+                 * Comment 2
+                 * @property bool $bar
+                 * Comment 3
+                 */
+                class Foo {}
+                ',
+            ],
+            'case-insensitive' => [
+                '<?php
+                /**
+                 * @property int|\stdClass $A
+                 * @property bool $b
+                 * @property array|null $C
+                 * @property float $D
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property array|null $C
+                 * @property float $D
+                 * @property bool $b
+                 * @property int|\stdClass $A
+                 */
+                class Foo {}
+                ',
+            ],
+            'with-and-without-types' => [
+                '<?php
+                /**
+                 * @property int|\stdClass $A
+                 * @property $b
+                 * @property array|null $C
+                 * @property $D
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property array|null $C
+                 * @property $D
+                 * @property $b
+                 * @property int|\stdClass $A
+                 */
+                class Foo {}
+                ',
+            ],
+        ];
+    }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixWithPropertyReadCases
+     */
+    public function testFixWithPropertyRead($expected, $input = null)
+    {
+        $this->fixer->configure([
+            'annotations' => [
+                'property-read',
+            ],
+        ]);
+
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixWithPropertyReadCases()
+    {
+        return [
+            'skip on 1 or 0 occurrences' => [
+                '<?php
+                /**
+                 * @property-read int|\stdClass $foo
+                 */
+                class Foo {}
+                ',
+            ],
+            'base case' => [
+                '<?php
+                /**
+                 * @property-read bool $bar
+                 * @property-read array|null $baz
+                 * @property-read int|\stdClass $foo
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property-read int|\stdClass $foo
+                 * @property-read bool $bar
+                 * @property-read array|null $baz
+                 */
+                class Foo {}
+                ',
+            ],
+            'preserve positions if other docblock parts are present' => [
+                '<?php
+                /**
+                 * Comment 1
+                 * @property-read bool $bar
+                 * Comment 3
+                 * @property-read int|\stdClass $foo
+                 * Comment 2
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * Comment 1
+                 * @property-read int|\stdClass $foo
+                 * Comment 2
+                 * @property-read bool $bar
+                 * Comment 3
+                 */
+                class Foo {}
+                ',
+            ],
+            'case-insensitive' => [
+                '<?php
+                /**
+                 * @property-read int|\stdClass $A
+                 * @property-read bool $b
+                 * @property-read array|null $C
+                 * @property-read float $D
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property-read array|null $C
+                 * @property-read float $D
+                 * @property-read bool $b
+                 * @property-read int|\stdClass $A
+                 */
+                class Foo {}
+                ',
+            ],
+            'with-and-without-types' => [
+                '<?php
+                /**
+                 * @property-read int|\stdClass $A
+                 * @property-read $b
+                 * @property-read array|null $C
+                 * @property-read $D
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property-read array|null $C
+                 * @property-read $D
+                 * @property-read $b
+                 * @property-read int|\stdClass $A
+                 */
+                class Foo {}
+                ',
+            ],
+        ];
+    }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixWithPropertyWriteCases
+     */
+    public function testFixWithPropertyWrite($expected, $input = null)
+    {
+        $this->fixer->configure([
+            'annotations' => [
+                'property-write',
+            ],
+        ]);
+
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixWithPropertyWriteCases()
+    {
+        return [
+            'skip on 1 or 0 occurrences' => [
+                '<?php
+                /**
+                 * @property-write int|\stdClass $foo
+                 */
+                class Foo {}
+                ',
+            ],
+            'base case' => [
+                '<?php
+                /**
+                 * @property-write bool $bar
+                 * @property-write array|null $baz
+                 * @property-write int|\stdClass $foo
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property-write int|\stdClass $foo
+                 * @property-write bool $bar
+                 * @property-write array|null $baz
+                 */
+                class Foo {}
+                ',
+            ],
+            'preserve positions if other docblock parts are present' => [
+                '<?php
+                /**
+                 * Comment 1
+                 * @property-write bool $bar
+                 * Comment 3
+                 * @property-write int|\stdClass $foo
+                 * Comment 2
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * Comment 1
+                 * @property-write int|\stdClass $foo
+                 * Comment 2
+                 * @property-write bool $bar
+                 * Comment 3
+                 */
+                class Foo {}
+                ',
+            ],
+            'case-insensitive' => [
+                '<?php
+                /**
+                 * @property-write int|\stdClass $A
+                 * @property-write bool $b
+                 * @property-write array|null $C
+                 * @property-write float $D
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property-write array|null $C
+                 * @property-write float $D
+                 * @property-write bool $b
+                 * @property-write int|\stdClass $A
+                 */
+                class Foo {}
+                ',
+            ],
+            'with-and-without-types' => [
+                '<?php
+                /**
+                 * @property-write int|\stdClass $A
+                 * @property-write $b
+                 * @property-write array|null $C
+                 * @property-write $D
+                 */
+                class Foo {}
+                ',
+                '<?php
+                /**
+                 * @property-write array|null $C
+                 * @property-write $D
+                 * @property-write $b
+                 * @property-write int|\stdClass $A
+                 */
+                class Foo {}
+                ',
+            ],
+        ];
+    }
+
     /**
      * @param string      $expected
      * @param null|string $input