From e1d71c6bb75b94f9ed00dceb2f4f6cb7e044723d Mon Sep 17 00:00:00 2001
From: Mitch Portier <mitch.portier@gmail.com>
Date: Thu, 3 Sep 2020 21:59:53 +0200
Subject: [PATCH] feat(InlineVariableComment): Add sniff for @var inline
 variable type declarations (#3163106 by Arkener)

---
 .../Commenting/InlineVariableCommentSniff.php | 139 ++++++++++++++++++
 .../Commenting/InlineCommentUnitTest.inc      |   4 +-
 .../InlineCommentUnitTest.inc.fixed           |   4 +-
 .../InlineVariableCommentUnitTest.inc         |  20 +++
 .../InlineVariableCommentUnitTest.inc.fixed   |  20 +++
 .../InlineVariableCommentUnitTest.php         |  53 +++++++
 6 files changed, 236 insertions(+), 4 deletions(-)
 create mode 100644 coder_sniffer/Drupal/Sniffs/Commenting/InlineVariableCommentSniff.php
 create mode 100644 tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc
 create mode 100644 tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc.fixed
 create mode 100644 tests/Drupal/Commenting/InlineVariableCommentUnitTest.php

diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/InlineVariableCommentSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/InlineVariableCommentSniff.php
new file mode 100644
index 00000000..82c5b5c4
--- /dev/null
+++ b/coder_sniffer/Drupal/Sniffs/Commenting/InlineVariableCommentSniff.php
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * \Drupal\Sniffs\Commenting\InlineVariableCommentSniff
+ *
+ * @category PHP
+ * @package  PHP_CodeSniffer
+ * @link     http://pear.php.net/package/PHP_CodeSniffer
+ */
+
+namespace Drupal\Sniffs\Commenting;
+
+use PHP_CodeSniffer\Files\File;
+use PHP_CodeSniffer\Sniffs\Sniff;
+use PHP_CodeSniffer\Util\Tokens;
+
+/**
+ * Checks for the correct usage of inline variable type declarations.
+ *
+ * @category PHP
+ * @package  PHP_CodeSniffer
+ * @link     http://pear.php.net/package/PHP_CodeSniffer
+ */
+class InlineVariableCommentSniff implements Sniff
+{
+
+
+    /**
+     * Returns an array of tokens this test wants to listen for.
+     *
+     * @return array<int|string>
+     */
+    public function register()
+    {
+        return [
+            T_COMMENT,
+            T_DOC_COMMENT_TAG,
+        ];
+
+    }//end register()
+
+
+    /**
+     * Processes this test, when one of its tokens is encountered.
+     *
+     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
+     * @param int                         $stackPtr  The position of the current token
+     *                                               in the stack passed in $tokens.
+     *
+     * @return void
+     */
+    public function process(File $phpcsFile, $stackPtr)
+    {
+        $tokens = $phpcsFile->getTokens();
+        $ignore = [
+            T_CLASS,
+            T_INTERFACE,
+            T_TRAIT,
+            T_FUNCTION,
+            T_CLOSURE,
+            T_PUBLIC,
+            T_PRIVATE,
+            T_PROTECTED,
+            T_FINAL,
+            T_STATIC,
+            T_ABSTRACT,
+            T_CONST,
+            T_PROPERTY,
+            T_INCLUDE,
+            T_INCLUDE_ONCE,
+            T_REQUIRE,
+            T_REQUIRE_ONCE,
+            T_VAR,
+        ];
+
+        // If this is a function/class/interface doc block comment, skip it.
+        $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
+        if (in_array($tokens[$nextToken]['code'], $ignore, true) === true) {
+            return;
+        }
+
+        if ($tokens[$stackPtr]['code'] === T_COMMENT) {
+            if (strpos($tokens[$stackPtr]['content'], '@var') !== false) {
+                $warning = 'Inline @var declarations should use the /** */ delimiters';
+
+                if ($phpcsFile->addFixableWarning($warning, $stackPtr, 'VarInline') === true) {
+                    if (strpos($tokens[$stackPtr]['content'], '#') === 0) {
+                        $varComment = substr_replace(rtrim($tokens[$stackPtr]['content']), '/**', 0, 1)." */\n";
+                    } else if (strpos($tokens[$stackPtr]['content'], '//') === 0) {
+                        $varComment = substr_replace(rtrim($tokens[$stackPtr]['content']), '/**', 0, 2)." */\n";
+                    } else {
+                        $varComment = substr_replace($tokens[$stackPtr]['content'], '/**', 0, 2);
+                    }
+
+                    $phpcsFile->fixer->replaceToken($stackPtr, $varComment);
+                }
+            }
+
+            return;
+        }//end if
+
+        // Skip if it's not a variable declaration.
+        if ($tokens[$stackPtr]['content'] !== '@var') {
+            return;
+        }
+
+        // Get the content of the @var tag to determine the order.
+        $varContent    = '';
+        $varContentPtr = $phpcsFile->findNext(T_DOC_COMMENT_STRING, ($stackPtr + 1));
+        if ($varContentPtr !== false) {
+            $varContent = $tokens[$varContentPtr]['content'];
+        }
+
+        if (strpos($varContent, '$') === 0) {
+            $warning = 'The variable name should be defined after the type';
+
+            $parts = explode(' ', $varContent, 3);
+            if (isset($parts[1]) === true) {
+                if ($phpcsFile->addFixableWarning($warning, $varContentPtr, 'VarInlineOrder') === true) {
+                    // Switch type and variable name.
+                    $replace = [
+                        $parts[1],
+                        $parts[0],
+                    ];
+                    if (isset($parts[2]) === true) {
+                        $replace[] = $parts[2];
+                    }
+
+                    $phpcsFile->fixer->replaceToken($varContentPtr, implode(' ', $replace));
+                }
+            } else {
+                $phpcsFile->addWarning($warning, $varContentPtr, 'VarInlineOrder');
+            }
+        }//end if
+
+    }//end process()
+
+
+}//end class
diff --git a/tests/Drupal/Commenting/InlineCommentUnitTest.inc b/tests/Drupal/Commenting/InlineCommentUnitTest.inc
index 758cb4f7..1ed73360 100644
--- a/tests/Drupal/Commenting/InlineCommentUnitTest.inc
+++ b/tests/Drupal/Commenting/InlineCommentUnitTest.inc
@@ -102,11 +102,11 @@ function test2() {
   // Date formats cannot be loaded during install or update.
   if (!defined('MAINTENANCE_MODE')) {
     if ($date_format_entity = DateFormat::load('html_date')) {
-      /** @var $date_format_entity \Drupal\Core\Datetime\DateFormatInterface */
+      /** @var \Drupal\Core\Datetime\DateFormatInterface $date_format_entity */
       $date_format = $date_format_entity->getPattern();
     }
     if ($time_format_entity = DateFormat::load('html_time')) {
-      /** @var $time_format_entity \Drupal\Core\Datetime\DateFormatInterface */
+      /** @var \Drupal\Core\Datetime\DateFormatInterface $time_format_entity */
       $time_format = $time_format_entity->getPattern();
     }
   }
diff --git a/tests/Drupal/Commenting/InlineCommentUnitTest.inc.fixed b/tests/Drupal/Commenting/InlineCommentUnitTest.inc.fixed
index 23f410d4..4dd627a8 100644
--- a/tests/Drupal/Commenting/InlineCommentUnitTest.inc.fixed
+++ b/tests/Drupal/Commenting/InlineCommentUnitTest.inc.fixed
@@ -101,11 +101,11 @@ function test2() {
   // Date formats cannot be loaded during install or update.
   if (!defined('MAINTENANCE_MODE')) {
     if ($date_format_entity = DateFormat::load('html_date')) {
-      /** @var $date_format_entity \Drupal\Core\Datetime\DateFormatInterface */
+      /** @var \Drupal\Core\Datetime\DateFormatInterface $date_format_entity */
       $date_format = $date_format_entity->getPattern();
     }
     if ($time_format_entity = DateFormat::load('html_time')) {
-      /** @var $time_format_entity \Drupal\Core\Datetime\DateFormatInterface */
+      /** @var \Drupal\Core\Datetime\DateFormatInterface $time_format_entity */
       $time_format = $time_format_entity->getPattern();
     }
   }
diff --git a/tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc b/tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc
new file mode 100644
index 00000000..05831df5
--- /dev/null
+++ b/tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Ipsum.
+ */
+function lorem() {
+  /* @var \Drupal\node\NodeInterface $node */
+  /* @var $node \Drupal\node\NodeInterface */
+  /** @var $node \Drupal\node\NodeInterface */
+  /** @var \Drupal\node\NodeInterface $node */
+  // @var \Drupal\node\NodeInterface $node
+  // @var $node \Drupal\node\NodeInterface
+  # @var \Drupal\node\NodeInterface $node
+  # @var $node \Drupal\node\NodeInterface
+  $node = Node::load(1);
+}
diff --git a/tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc.fixed b/tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc.fixed
new file mode 100644
index 00000000..141270b3
--- /dev/null
+++ b/tests/Drupal/Commenting/InlineVariableCommentUnitTest.inc.fixed
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Ipsum.
+ */
+function lorem() {
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  /** @var \Drupal\node\NodeInterface $node */
+  $node = Node::load(1);
+}
diff --git a/tests/Drupal/Commenting/InlineVariableCommentUnitTest.php b/tests/Drupal/Commenting/InlineVariableCommentUnitTest.php
new file mode 100644
index 00000000..12e1987a
--- /dev/null
+++ b/tests/Drupal/Commenting/InlineVariableCommentUnitTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\Test\Commenting;
+
+use Drupal\Test\CoderSniffUnitTest;
+
+class InlineVariableCommentUnitTest extends CoderSniffUnitTest
+{
+
+
+    /**
+     * Returns the lines where errors should occur.
+     *
+     * The key of the array should represent the line number and the value
+     * should represent the number of errors that should occur on that line.
+     *
+     * @param string $testFile The name of the file being tested.
+     *
+     * @return array<int, int>
+     */
+    protected function getErrorList(string $testFile): array
+    {
+        return [];
+
+    }//end getErrorList()
+
+
+    /**
+     * Returns the lines where warnings should occur.
+     *
+     * The key of the array should represent the line number and the value
+     * should represent the number of warnings that should occur on that line.
+     *
+     * @param string $testFile The name of the file being tested.
+     *
+     * @return array<int, int>
+     */
+    protected function getWarningList(string $testFile): array
+    {
+        return [
+            11 => 1,
+            12 => 1,
+            13 => 1,
+            15 => 1,
+            16 => 1,
+            17 => 1,
+            18 => 1,
+        ];
+
+    }//end getWarningList()
+
+
+}//end class
-- 
GitLab