Browse Source

Add PhpdocVarAnnotationCorrectOrderFixer

Kuba Werłos 6 years ago
parent
commit
8e031b2845

+ 5 - 0
README.rst

@@ -1549,6 +1549,11 @@ Choose from the list of available rules:
   - ``sort_algorithm`` (``'alpha'``, ``'none'``): the sorting algorithm to apply;
     defaults to ``'alpha'``
 
+* **phpdoc_var_annotation_correct_order** [@PhpCsFixer]
+
+  ``@var`` and ``@type`` annotations must have type and name in the correct
+  order.
+
 * **phpdoc_var_without_name** [@Symfony, @PhpCsFixer]
 
   ``@var`` and ``@type`` annotations should not contain the variable name.

+ 67 - 0
src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php

@@ -0,0 +1,67 @@
+<?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\Fixer\Phpdoc;
+
+use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\FixerDefinition\CodeSample;
+use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\Preg;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @author Kuba Werłos <werlos@gmail.com>
+ */
+final class PhpdocVarAnnotationCorrectOrderFixer extends AbstractFixer
+{
+    public function getDefinition()
+    {
+        return new FixerDefinition(
+            '`@var` and `@type` annotations must have type and name in the correct order.',
+            [new CodeSample('<?php
+/** @var $foo int */
+$foo = 2 + 2;
+')]
+        );
+    }
+
+    public function isCandidate(Tokens $tokens)
+    {
+        return $tokens->isTokenKindFound(T_DOC_COMMENT);
+    }
+
+    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
+    {
+        foreach ($tokens as $index => $token) {
+            if (!$token->isGivenKind(T_DOC_COMMENT)) {
+                continue;
+            }
+
+            if (false === stripos($token->getContent(), '@var') && false === stripos($token->getContent(), '@type')) {
+                continue;
+            }
+
+            $newContent = Preg::replace(
+                '/(@(?:type|var)\s*)(\$\S+)(\s+)([^\$](?:[^<\s]|<[^>]*>)*)(\s|\*)/i',
+                '$1$4$3$2$5',
+                $token->getContent()
+            );
+
+            if ($newContent === $token->getContent()) {
+                continue;
+            }
+
+            $tokens[$index] = new Token([$token->getId(), $newContent]);
+        }
+    }
+}

+ 1 - 0
src/RuleSet.php

@@ -248,6 +248,7 @@ final class RuleSet implements RuleSetInterface
             'phpdoc_order' => true,
             'phpdoc_trim_consecutive_blank_line_separation' => true,
             'phpdoc_types_order' => true,
+            'phpdoc_var_annotation_correct_order' => true,
             'return_assignment' => true,
             'single_line_comment_style' => true,
         ],

+ 1 - 1
tests/Fixer/Alias/NoAliasFunctionsFixerTest.php

@@ -40,7 +40,7 @@ final class NoAliasFunctionsFixerTest extends AbstractFixerTestCase
         $cases = [];
 
         foreach (['internalSet', 'imapSet'] as $setStaticAttributeName) {
-            /** @var $aliases string[] */
+            /** @var string[] $aliases */
             $aliases = $this->getStaticAttribute(\PhpCsFixer\Fixer\Alias\NoAliasFunctionsFixer::class, $setStaticAttributeName);
 
             foreach ($aliases as $alias => $master) {

+ 165 - 0
tests/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixerTest.php

@@ -0,0 +1,165 @@
+<?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\Fixer\Phpdoc;
+
+use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
+
+/**
+ * @internal
+ *
+ * @author Kuba Werłos <werlos@gmail.com>
+ *
+ * @covers \PhpCsFixer\Fixer\Phpdoc\PhpdocVarAnnotationCorrectOrderFixer
+ */
+final class PhpdocVarAnnotationCorrectOrderFixerTest extends AbstractFixerTestCase
+{
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixCases
+     */
+    public function testFix($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixCases()
+    {
+        yield [ // It's @param, we care only about @var
+            '<?php /** @param $foo Foo */',
+        ];
+
+        yield [ // This is already fine
+            '<?php /** @var Foo $foo */ ',
+        ];
+
+        yield [ // What? Two variables, I'm not touching this
+            '<?php /** @var $foo $bar */',
+        ];
+
+        yield [ // Two classes are not to touch either
+            '<?php /** @var Foo Bar */',
+        ];
+
+        yield ['<?php /** @var */'];
+
+        yield ['<?php /** @var $foo */'];
+
+        yield ['<?php /** @var Bar */'];
+
+        yield [
+            '<?php
+/**
+ * @var Foo $foo
+ * @var Bar $bar
+ */
+',
+            '<?php
+/**
+ * @var $foo Foo
+ * @var $bar Bar
+ */
+',
+        ];
+
+        yield [
+            '<?php
+/**
+ * @var Foo $foo Some description
+ */
+',
+            '<?php
+/**
+ * @var $foo Foo Some description
+ */
+',
+        ];
+
+        yield [
+            '<?php /** @var Foo $foo */',
+            '<?php /** @var $foo Foo */',
+        ];
+
+        yield [
+            '<?php /** @type Foo $foo */',
+            '<?php /** @type $foo Foo */',
+        ];
+
+        yield [
+            '<?php /** @var Foo $foo*/',
+            '<?php /** @var $foo Foo*/',
+        ];
+
+        yield [
+            '<?php /** @var Foo[] $foos */',
+            '<?php /** @var $foos Foo[] */',
+        ];
+
+        yield [
+            '<?php /** @Var Foo $foo */',
+            '<?php /** @Var $foo Foo */',
+        ];
+
+        yield [
+            '<?php
+/** @var Foo|Bar|mixed|int $someWeirdLongNAME__123 */
+',
+            '<?php
+/** @var $someWeirdLongNAME__123 Foo|Bar|mixed|int */
+',
+        ];
+
+        yield [
+            '<?php
+/**
+ * @var Foo $bar long description
+ *               goes here
+ */
+',
+            '<?php
+/**
+ * @var $bar Foo long description
+ *               goes here
+ */
+',
+        ];
+
+        yield [
+            '<?php
+/** @var array<int, int> $foo */
+',
+            '<?php
+/** @var $foo array<int, int> */
+',
+        ];
+
+        yield [
+            '<?php
+/** @var array<int, int> $foo Array of something */
+',
+            '<?php
+/** @var $foo array<int, int> Array of something */
+',
+        ];
+
+        yield [
+            '<?php
+/** @var Foo|array<int, int>|null $foo */
+',
+            '<?php
+/** @var $foo Foo|array<int, int>|null */
+',
+        ];
+    }
+}