From 39514b424968faf2084d53f5d410595faddd46be Mon Sep 17 00:00:00 2001
From: Lars Toomre <ltoomre@23809.no-reply.drupal.org>
Date: Wed, 24 Oct 2012 14:27:57 -0400
Subject: [PATCH] Issue 1805550 by Lars Toomre: Modify sniff for "Missing
 function doc comment" for test classes.

---
 .../Sniffs/Commenting/FunctionCommentSniff.php     | 1108 ++++++++++----------
 1 file changed, 567 insertions(+), 541 deletions(-)

diff --git a/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php b/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php
index b7053d369587e1a85163d63e0361303925299be3..35ecc936159bd299f2e7809d62758ba8c198acc8 100644
--- a/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php
+++ b/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php
@@ -1,541 +1,567 @@
-<?php
-/**
- * Parses and verifies the doc comments for functions.
- *
- * PHP version 5
- *
- * @category PHP
- * @package  PHP_CodeSniffer
- * @link     http://pear.php.net/package/PHP_CodeSniffer
- */
-
-/**
- * Parses and verifies the doc comments for functions. Largely copied from
- * Squiz_Sniffs_Commenting_FunctionCommentSniff.
- *
- * @category PHP
- * @package  PHP_CodeSniffer
- * @link     http://pear.php.net/package/PHP_CodeSniffer
- */
-class Drupal_Sniffs_Commenting_FunctionCommentSniff implements PHP_CodeSniffer_Sniff
-{
-
-    /**
-     * The name of the method that we are currently processing.
-     *
-     * @var string
-     */
-    private $_methodName = '';
-
-    /**
-     * The position in the stack where the fucntion token was found.
-     *
-     * @var int
-     */
-    private $_functionToken = null;
-
-    /**
-     * The position in the stack where the class token was found.
-     *
-     * @var int
-     */
-    private $_classToken = null;
-
-    /**
-     * The function comment parser for the current method.
-     *
-     * @var Drupal_CommentParser_FunctionCommentParser
-     */
-    protected $commentParser = null;
-
-    /**
-     * The current PHP_CodeSniffer_File object we are processing.
-     *
-     * @var PHP_CodeSniffer_File
-     */
-    protected $currentFile = null;
-
-
-    /**
-     * Returns an array of tokens this test wants to listen for.
-     *
-     * @return array
-     */
-    public function register()
-    {
-        return array(T_FUNCTION);
-
-    }//end register()
-
-
-    /**
-     * Processes this test, when one of its tokens is encountered.
-     *
-     * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
-    {
-        $this->currentFile = $phpcsFile;
-
-        $tokens = $phpcsFile->getTokens();
-
-        $find = array(
-                 T_COMMENT,
-                 T_DOC_COMMENT,
-                 T_CLASS,
-                 T_FUNCTION,
-                 T_OPEN_TAG,
-                );
-
-        $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1));
-
-        if ($commentEnd === false) {
-            return;
-        }
-
-        // If the token that we found was a class or a function, then this
-        // function has no doc comment.
-        $code = $tokens[$commentEnd]['code'];
-
-        if ($code === T_COMMENT) {
-            // The function might actually be missing a comment, and this last comment
-            // found is just commenting a bit of code on a line. So if it is not the
-            // only thing on the line, assume we found nothing.
-            $prevContent = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $commentEnd);
-            if ($tokens[$commentEnd]['line'] === $tokens[$commentEnd]['line']) {
-                $error = 'Missing function doc comment';
-                $phpcsFile->addError($error, $stackPtr, 'Missing');
-            } else {
-                $error = 'You must use "/**" style comments for a function comment';
-                $phpcsFile->addError($error, $stackPtr, 'WrongStyle');
-            }
-            return;
-        } else if ($code !== T_DOC_COMMENT) {
-            $error = 'Missing function doc comment';
-            $phpcsFile->addError($error, $stackPtr, 'Missing');
-            return;
-        } else if (trim($tokens[$commentEnd]['content']) !== '*/') {
-            $error = 'Wrong function doc comment end; expected "*/", found "%s"';
-            $phpcsFile->addError($error, $commentEnd, 'WrongEnd', array(trim($tokens[$commentEnd]['content'])));
-            return;
-        }
-
-        // If there is any code between the function keyword and the doc block
-        // then the doc block is not for us.
-        $ignore    = PHP_CodeSniffer_Tokens::$scopeModifiers;
-        $ignore[]  = T_STATIC;
-        $ignore[]  = T_WHITESPACE;
-        $ignore[]  = T_ABSTRACT;
-        $ignore[]  = T_FINAL;
-        $prevToken = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true);
-        if ($prevToken !== $commentEnd) {
-            $phpcsFile->addError('Missing function doc comment', $stackPtr, 'Missing');
-            return;
-        }
-
-        $this->_functionToken = $stackPtr;
-
-        $this->_classToken = null;
-        foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) {
-            if ($condition === T_CLASS || $condition === T_INTERFACE) {
-                $this->_classToken = $condPtr;
-                break;
-            }
-        }
-
-        // If the first T_OPEN_TAG is right before the comment and does not immediately precede the function
-        // probably a file comment.
-        $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1);
-        $prevToken    = $phpcsFile->findPrevious(T_WHITESPACE, ($commentStart - 1), null, true);
-        if ($tokens[$prevToken]['code'] === T_OPEN_TAG) {
-            // Is this the first open tag
-            if (($stackPtr === 0 || $phpcsFile->findPrevious(T_OPEN_TAG, ($prevToken - 1)) === false)
-                && (($tokens[$commentEnd]['line'] + 1) !== $tokens[$stackPtr]['line'])) {
-                $phpcsFile->addError('Missing function doc comment', $stackPtr, 'Missing');
-                return;
-            }
-        }
-
-        $commentString     = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));
-        $this->_methodName = $phpcsFile->getDeclarationName($stackPtr);
-
-        try {
-            $this->commentParser = new Drupal_CommentParser_FunctionCommentParser($commentString, $phpcsFile);
-            $this->commentParser->parse();
-        } catch (PHP_CodeSniffer_CommentParser_ParserException $e) {
-            $line = ($e->getLineWithinComment() + $commentStart);
-            $phpcsFile->addError($e->getMessage(), $line, 'FailedParse');
-            return;
-        }
-
-        $comment = $this->commentParser->getComment();
-        if (is_null($comment) === true) {
-            $error = 'Function doc comment is empty';
-            $phpcsFile->addError($error, $commentStart, 'Empty');
-            return;
-        }
-
-        // The first line of the comment should just be the /** code.
-        $eolPos    = strpos($commentString, $phpcsFile->eolChar);
-        $firstLine = substr($commentString, 0, $eolPos);
-        if ($firstLine !== '/**') {
-            $error = 'The open comment tag must be the only content on the line';
-            $phpcsFile->addError($error, $commentStart, 'ContentAfterOpen');
-        }
-
-        // Check that the comment is imidiately before the function definition.
-        if (($tokens[$commentEnd]['line'] + 1) !== $tokens[$stackPtr]['line']) {
-            $error = 'Function doc comment must end on the line before the function definition';
-            $phpcsFile->addError($error, $commentEnd, 'EmptyLinesAfterDoc');
-        }
-
-        $this->processParams($commentStart);
-        $this->processReturn($commentStart, $commentEnd);
-        $this->processThrows($commentStart);
-        $this->processSees($commentStart);
-
-        // Check if hook implementation doc is formated correctly.
-        if (preg_match('/^[\s]*Implement[^\n]+?hook_[^\n]+/i', $comment->getShortComment(), $matches)) {
-            $formattingIssue = 0;
-            if(!strstr($matches[0], 'Implements ')){
-                $formattingIssue++;
-            }
-            if(!preg_match('/ hook_[a-zA-Z0-9_]+\(\)( for [a-z0-9_]+\(\))?\.$/', $matches[0])){
-                $formattingIssue++;
-            }
-            if($formattingIssue){
-                $phpcsFile->addWarning('Format should be "* Implements hook_foo()." or "Implements hook_foo_BAR_ID_bar() for xyz_bar()."', $commentStart + 1);
-            }
-        }
-
-        // Check for a comment description.
-        $short = $comment->getShortComment();
-        if (trim($short) === '') {
-            $error = 'Missing short description in function doc comment';
-            $phpcsFile->addError($error, $commentStart, 'MissingShort');
-            return;
-        }
-
-        // No extra newline before short description.
-        $newlineCount = 0;
-        $newlineSpan  = strspn($short, $phpcsFile->eolChar);
-        if ($short !== '' && $newlineSpan > 0) {
-            $line  = ($newlineSpan > 1) ? 'newlines' : 'newline';
-            $error = "Extra $line found before function comment short description";
-            $phpcsFile->addError($error, ($commentStart + 1));
-        }
-
-        $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1);
-
-        // Exactly one blank line between short and long description.
-        $long = $comment->getLongComment();
-        if (empty($long) === false) {
-            $between        = $comment->getWhiteSpaceBetween();
-            $newlineBetween = substr_count($between, $phpcsFile->eolChar);
-            if ($newlineBetween !== 2) {
-                $error = 'There must be exactly one blank line between descriptions in function comment';
-                $phpcsFile->addError($error, ($commentStart + $newlineCount + 1), 'SpacingAfterShort');
-            }
-
-            $newlineCount += $newlineBetween;
-        }
-
-        // Short description must be single line and end with a full stop.
-        $testShort = trim($short);
-        $lastChar  = $testShort[(strlen($testShort) - 1)];
-        if (substr_count($testShort, $phpcsFile->eolChar) !== 0) {
-            $error = 'Function comment short description must be on a single line';
-            $phpcsFile->addError($error, ($commentStart + 1), 'ShortSingleLine');
-        }
-
-        if (preg_match('|[A-Z]|', $testShort[0]) === 0) {
-            $error = 'Function comment short description must start with a capital letter';
-            $phpcsFile->addError($error, ($commentStart + 1), 'ShortNotCapital');
-        }
-
-        if ($lastChar !== '.') {
-            $error = 'Function comment short description must end with a full stop';
-            $phpcsFile->addError($error, ($commentStart + 1), 'ShortFullStop');
-        }
-
-    }//end process()
-
-
-    /**
-     * Process any throw tags that this function comment has.
-     *
-     * @param int $commentStart The position in the stack where the
-     *                          comment started.
-     *
-     * @return void
-     */
-    protected function processThrows($commentStart)
-    {
-        if (count($this->commentParser->getThrows()) === 0) {
-            return;
-        }
-
-        foreach ($this->commentParser->getThrows() as $throw) {
-
-            $exception = $throw->getValue();
-            $errorPos  = ($commentStart + $throw->getLine());
-
-            if ($exception === '') {
-                $error = '@throws tag must contain the exception class name';
-                $this->currentFile->addError($error, $errorPos, 'EmptyThrows');
-            }
-        }
-
-    }//end processThrows()
-
-
-    /**
-     * Process the return comment of this function comment.
-     *
-     * @param int $commentStart The position in the stack where the comment started.
-     * @param int $commentEnd   The position in the stack where the comment ended.
-     *
-     * @return void
-     */
-    protected function processReturn($commentStart, $commentEnd)
-    {
-        // Skip constructor and destructor.
-        $className = '';
-        if ($this->_classToken !== null) {
-            $className = $this->currentFile->getDeclarationName($this->_classToken);
-            $className = strtolower(ltrim($className, '_'));
-        }
-
-        $methodName      = strtolower(ltrim($this->_methodName, '_'));
-        $isSpecialMethod = ($this->_methodName === '__construct' || $this->_methodName === '__destruct');
-
-        if ($isSpecialMethod === false && $methodName !== $className) {
-            $return = $this->commentParser->getReturn();
-            if ($return !== null) {
-                $errorPos = ($commentStart + $this->commentParser->getReturn()->getLine());
-                if (trim($return->getRawContent()) === '') {
-                    $error = '@return tag is empty in function comment';
-                    $this->currentFile->addError($error, $errorPos, 'EmptyReturn');
-                    return;
-                }
-
-                $comment = $return->getComment();
-                $commentWhitespace = $return->getWhitespaceBeforeComment();
-                if (substr_count($return->getWhitespaceBeforeValue(), $this->currentFile->eolChar) > 0) {
-                    $error = 'Data type of return value is missing';
-                    $this->currentFile->addError($error, $errorPos, 'MissingReturnType');
-                    // Treat the value as part of the comment.
-                    $comment = $return->getValue().' '.$comment;
-                    $commentWhitespace = $return->getWhitespaceBeforeValue();
-                } else if ($return->getWhitespaceBeforeValue() !== ' ') {
-                    $error = 'Expected 1 space before return type';
-                    $this->currentFile->addError($error, $errorPos, 'SpacingBeforeReturnType');
-                }
-
-                if (strpos($return->getValue(), '$') !== false) {
-                    $error = '@return data type must not contain "$"';
-                    $this->currentFile->addError($error, $errorPos, '$InReturnType');
-                }
-
-                if (in_array($return->getValue(), array('unknown_type', '<type>', 'type')) === true) {
-                    $error = 'Expected a valid @return data type, but found %s';
-                    $data = array($return->getValue());
-                    $this->currentFile->addError($error, $errorPos, 'InvalidReturnType', $data);
-                }
-
-                if (trim($comment) === '') {
-                    $error = 'Missing comment for @return statement';
-                    $this->currentFile->addError($error, $errorPos, 'MissingReturnComment');
-                } else if (substr_count($commentWhitespace, $this->currentFile->eolChar) !== 1) {
-                    $error = 'Return comment must be on the next line';
-                    $this->currentFile->addError($error, $errorPos, 'ReturnCommentNewLine');
-                } else if (substr_count($commentWhitespace, ' ') !== 3) {
-                    $error = 'Return comment indentation must be 2 additional spaces';
-                    $this->currentFile->addError($error, $errorPos + 1, 'ParamCommentIndentation');
-                }
-            }
-        }
-
-    }//end processReturn()
-
-
-    /**
-     * Process the function parameter comments.
-     *
-     * @param int $commentStart The position in the stack where
-     *                          the comment started.
-     *
-     * @return void
-     */
-    protected function processParams($commentStart)
-    {
-        $realParams = $this->currentFile->getMethodParameters($this->_functionToken);
-
-        $params      = $this->commentParser->getParams();
-        $foundParams = array();
-
-        if (empty($params) === false) {
-            // There must be an empty line before the parameter block.
-            if (substr_count($params[0]->getWhitespaceBefore(), $this->currentFile->eolChar) < 2) {
-                $error    = 'There must be an empty line before the parameter block';
-                $errorPos = ($params[0]->getLine() + $commentStart);
-                $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParams');
-            }
-
-            $lastParm = (count($params) - 1);
-            if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) {
-                $error    = 'Last parameter comment requires a blank newline after it';
-                $errorPos = ($params[$lastParm]->getLine() + $commentStart);
-                $this->currentFile->addError($error, $errorPos, 'SpacingAfterParams');
-            }
-
-            $checkPos = 0;
-            foreach ($params as $param) {
-                $paramComment = trim($param->getComment());
-                $errorPos     = ($param->getLine() + $commentStart);
-
-                // Make sure that there is only one space before the var type.
-                if ($param->getWhitespaceBeforeType() !== ' ') {
-                    $error = 'Expected 1 space before variable type';
-                    $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamType');
-                }
-
-                // Make sure they are in the correct order,
-                // and have the correct name.
-                $pos = $param->getPosition();
-
-                $paramName = '[ UNKNOWN ]';
-                if ($param->getVarName() !== '') {
-                    $paramName = $param->getVarName();
-                }
-
-                // Make sure the names of the parameter comment matches the
-                // actual parameter.
-                $matched = false;
-                // Parameter documentation can be ommitted for some parameters, so
-                // we have to search the rest for a match.
-                while (isset($realParams[($checkPos)]) === true) {
-                    $realName          = $realParams[($checkPos)]['name'];
-                    $expectedParamName = $realName;
-                    $isReference       = $realParams[($checkPos)]['pass_by_reference'];
-
-                    // Append ampersand to name if passing by reference.
-                    if ($isReference === true && substr($paramName, 0, 1) === '&') {
-                        $expectedParamName = '&'.$realName;
-                    }
-
-                    if ($expectedParamName === $paramName) {
-                        $matched = true;
-                        break;
-                    }
-
-                    $checkPos++;
-                }
-
-                if ($matched === false && $paramName !== '...') {
-                    if ($checkPos >= $pos) {
-                        $code  = 'ParamNameNoMatch';
-                        $data  = array(
-                                  $paramName,
-                                  $realParams[($pos - 1)]['name'],
-                                  $pos,
-                                 );
-                        $error = 'Doc comment for var %s does not match ';
-                        if (strtolower($paramName) === strtolower($realParams[($pos - 1)]['name'])) {
-                            $error .= 'case of ';
-                            $code   = 'ParamNameNoCaseMatch';
-                        }
-
-                        $error .= 'actual variable name %s at position %s';
-                        $this->currentFile->addError($error, $errorPos, $code, $data);
-                        // Reset the parameter position to check for following
-                        // parameters.
-                        $checkPos = ($pos - 1);
-                    } else {
-                        // We must have an extra parameter comment.
-                        $error = 'Superfluous doc comment at position '.$pos;
-                        $this->currentFile->addError($error, $errorPos, 'ExtraParamComment');
-                    }//end if
-                }//end if
-
-                $checkPos++;
-
-                if ($param->getVarName() === '') {
-                    $error = 'Missing parameter name at position '.$pos;
-                    $this->currentFile->addError($error, $errorPos, 'MissingParamName');
-                }
-
-                if ($param->getType() === '') {
-                    $error = 'Missing parameter type at position '.$pos;
-                    $this->currentFile->addError($error, $errorPos, 'MissingParamType');
-                }
-
-                if (in_array($param->getType(), array('unknown_type', '<type>', 'type')) === true) {
-                    $error = 'Expected a valid @param data type, but found %s';
-                    $data = array($param->getType());
-                    $this->currentFile->addError($error, $errorPos, 'InvalidParamType', $data);
-                }
-
-                if ($paramComment === '') {
-                    $error = 'Missing comment for param "%s" at position %s';
-                    $data  = array(
-                              $paramName,
-                              $pos,
-                             );
-                    $this->currentFile->addError($error, $errorPos, 'MissingParamComment', $data);
-                } else if (substr_count($param->getWhitespaceBeforeComment(), $this->currentFile->eolChar) !== 1) {
-                    $error = 'Parameter comment must be on the next line at position '.$pos;
-                    $this->currentFile->addError($error, $errorPos, 'ParamCommentNewLine');
-                } else if (substr_count($param->getWhitespaceBeforeComment(), ' ') !== 3) {
-                    $error = 'Parameter comment indentation must be 2 additional spaces at position '.$pos;
-                    $this->currentFile->addError($error, ($errorPos + 1), 'ParamCommentIndentation');
-                }
-            }//end foreach
-        }//end if
-
-        $realNames = array();
-        foreach ($realParams as $realParam) {
-            $realNames[] = $realParam['name'];
-        }
-
-    }//end processParams()
-
-
-    /**
-     * Process the function "see" comments.
-     *
-     * @return void
-     */
-    protected function processSees($commentStart)
-    {
-        $sees = $this->commentParser->getSees();
-        foreach ($sees as $see) {
-            $errorPos = $see->getLine() + $commentStart;
-            if ($see->getWhitespaceBeforeContent() !== ' ') {
-                $error = 'Expected 1 space before see reference';
-                $this->currentFile->addError($error, $errorPos, 'SpacingBeforeSee');
-            }
-
-            $comment = trim($see->getContent());
-            if (strpos($comment, ' ') !== false) {
-                $error = 'The @see reference should not contain any additional text';
-                $this->currentFile->addError($error, $errorPos, 'SeeAdditionalText');
-                continue;
-            }
-            if (preg_match('/[\.!\?]$/', $comment) === 1) {
-                $error = 'Trailing punctuation for @see references is not allowed.';
-                $this->currentFile->addError($error, $errorPos, 'SeePunctuation');
-            }
-
-        }
-
-    }//end processSees()
-
-
-}//end class
-
-?>
+<?php
+/**
+ * Parses and verifies the doc comments for functions.
+ *
+ * PHP version 5
+ *
+ * @category PHP
+ * @package  PHP_CodeSniffer
+ * @link     http://pear.php.net/package/PHP_CodeSniffer
+ */
+
+/**
+ * Parses and verifies the doc comments for functions. Largely copied from
+ * Squiz_Sniffs_Commenting_FunctionCommentSniff.
+ *
+ * @category PHP
+ * @package  PHP_CodeSniffer
+ * @link     http://pear.php.net/package/PHP_CodeSniffer
+ */
+class Drupal_Sniffs_Commenting_FunctionCommentSniff implements PHP_CodeSniffer_Sniff
+{
+
+    /**
+     * The name of the method that we are currently processing.
+     *
+     * @var string
+     */
+    private $_methodName = '';
+
+    /**
+     * The position in the stack where the fucntion token was found.
+     *
+     * @var int
+     */
+    private $_functionToken = null;
+
+    /**
+     * The position in the stack where the class token was found.
+     *
+     * @var int
+     */
+    private $_classToken = null;
+
+    /**
+     * The function comment parser for the current method.
+     *
+     * @var Drupal_CommentParser_FunctionCommentParser
+     */
+    protected $commentParser = null;
+
+    /**
+     * The current PHP_CodeSniffer_File object we are processing.
+     *
+     * @var PHP_CodeSniffer_File
+     */
+    protected $currentFile = null;
+
+    /**
+     * The list of Test class methods that are NOT supposed to have docblocks.
+     *
+     * @var array
+     */
+    protected $noTestClassDocs = array(
+      'getInfo',
+      'setUp',
+      'tearDown',
+    );
+
+    /**
+     * Returns an array of tokens this test wants to listen for.
+     *
+     * @return array
+     */
+    public function register()
+    {
+        return array(T_FUNCTION);
+
+    }//end register()
+
+
+    /**
+     * Processes this test, when one of its tokens is encountered.
+     *
+     * @param PHP_CodeSniffer_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(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
+    {
+        $this->currentFile = $phpcsFile;
+
+        $tokens = $phpcsFile->getTokens();
+
+        $find = array(
+                 T_COMMENT,
+                 T_DOC_COMMENT,
+                 T_CLASS,
+                 T_FUNCTION,
+                 T_OPEN_TAG,
+                );
+
+        $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1));
+
+        if ($commentEnd === false) {
+            return;
+        }
+
+        // If the token that we found was a class or a function, then this
+        // function has no docblock comment.
+        $code = $tokens[$commentEnd]['code'];
+
+        // We can return with no error message if this is one of the Test class
+        // methods that, according to documentation standards, are not supposed
+        // to have a docblock.
+
+        // How does one get filename out of $this->currentFile object?
+        $filename = $this->currentFile->  ??
+
+        if (in_array($this->_methodName, $this->$noTestClassDocs) &&
+          // Is this a test class? Needs to have 'Test' in filename and ends in
+          // '.php'.
+          strpos($filename, 'Test') != FALSE && strpos($filename, '.php') != FALSE) {
+          return;
+        }
+
+        if ($code === T_COMMENT) {
+            // The function might actually be missing a comment, and this last
+            // comment found is just commenting a bit of code on a line. So if
+            // it is not the only thing on the line, assume we found nothing.
+            $prevContent = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, $commentEnd);
+            // @todo This comparison will always be TRUE.  Hence, else cannot be
+            //   reached. Is that really the intent of this comparison?
+            if ($tokens[$commentEnd]['line'] === $tokens[$commentEnd]['line']) {
+                $error = 'Missing function doc comment (1)';
+                $phpcsFile->addError($error, $stackPtr, 'Missing');
+            } else {
+                $error = 'You must use "/**" style comments for a function comment';
+                $phpcsFile->addError($error, $stackPtr, 'WrongStyle');
+            }
+            return;
+        } else if ($code !== T_DOC_COMMENT) {
+            $error = 'Missing function doc comment (2)';
+            $phpcsFile->addError($error, $stackPtr, 'Missing');
+            return;
+        } else if (trim($tokens[$commentEnd]['content']) !== '*/') {
+            $error = 'Wrong function doc comment end; expected "*/", found "%s"';
+            $phpcsFile->addError($error, $commentEnd, 'WrongEnd', array(trim($tokens[$commentEnd]['content'])));
+            return;
+        }
+
+        // If there is any code between the function keyword and the doc block
+        // then the doc block is not for us.
+        $ignore    = PHP_CodeSniffer_Tokens::$scopeModifiers;
+        $ignore[]  = T_STATIC;
+        $ignore[]  = T_WHITESPACE;
+        $ignore[]  = T_ABSTRACT;
+        $ignore[]  = T_FINAL;
+        $prevToken = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true);
+        if ($prevToken !== $commentEnd) {
+            $phpcsFile->addError('Missing function doc comment (3)', $stackPtr, 'Missing');
+            return;
+        }
+
+        $this->_functionToken = $stackPtr;
+
+        $this->_classToken = null;
+        foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) {
+            if ($condition === T_CLASS || $condition === T_INTERFACE) {
+                $this->_classToken = $condPtr;
+                break;
+            }
+        }
+
+        // If the first T_OPEN_TAG is right before the comment and does not
+        // immediately precede the function probably a file comment.
+        $commentStart = ($phpcsFile->findPrevious(T_DOC_COMMENT, ($commentEnd - 1), null, true) + 1);
+        $prevToken    = $phpcsFile->findPrevious(T_WHITESPACE, ($commentStart - 1), null, true);
+        if ($tokens[$prevToken]['code'] === T_OPEN_TAG) {
+            // Is this the first open tag
+            if (($stackPtr === 0 || $phpcsFile->findPrevious(T_OPEN_TAG, ($prevToken - 1)) === false)
+                && (($tokens[$commentEnd]['line'] + 1) !== $tokens[$stackPtr]['line'])) {
+                $phpcsFile->addError('Missing function doc comment (4)', $stackPtr, 'Missing');
+                return;
+            }
+        }
+
+        $commentString     = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));
+        $this->_methodName = $phpcsFile->getDeclarationName($stackPtr);
+
+        try {
+            $this->commentParser = new Drupal_CommentParser_FunctionCommentParser($commentString, $phpcsFile);
+            $this->commentParser->parse();
+        } catch (PHP_CodeSniffer_CommentParser_ParserException $e) {
+            $line = ($e->getLineWithinComment() + $commentStart);
+            $phpcsFile->addError($e->getMessage(), $line, 'FailedParse');
+            return;
+        }
+
+        $comment = $this->commentParser->getComment();
+        if (is_null($comment) === true) {
+            $error = 'Function doc comment is empty';
+            $phpcsFile->addError($error, $commentStart, 'Empty');
+            return;
+        }
+
+        // The first line of the comment should just be the /** code.
+        $eolPos    = strpos($commentString, $phpcsFile->eolChar);
+        $firstLine = substr($commentString, 0, $eolPos);
+        if ($firstLine !== '/**') {
+            $error = 'The open comment tag must be the only content on the line';
+            $phpcsFile->addError($error, $commentStart, 'ContentAfterOpen');
+        }
+
+        // Check that the comment is imidiately before the function definition.
+        if (($tokens[$commentEnd]['line'] + 1) !== $tokens[$stackPtr]['line']) {
+            $error = 'Function doc comment must end on the line before the function definition';
+            $phpcsFile->addError($error, $commentEnd, 'EmptyLinesAfterDoc');
+        }
+
+        $this->processParams($commentStart);
+        $this->processReturn($commentStart, $commentEnd);
+        $this->processThrows($commentStart);
+        $this->processSees($commentStart);
+
+        // Check if hook implementation doc is formated correctly.
+        if (preg_match('/^[\s]*Implement[^\n]+?hook_[^\n]+/i', $comment->getShortComment(), $matches)) {
+            $formattingIssue = 0;
+            if(!strstr($matches[0], 'Implements ')){
+                $formattingIssue++;
+            }
+            if(!preg_match('/ hook_[a-zA-Z0-9_]+\(\)( for [a-z0-9_]+\(\))?\.$/', $matches[0])){
+                $formattingIssue++;
+            }
+            if($formattingIssue){
+                $phpcsFile->addWarning('Format should be "* Implements hook_foo()." or "Implements hook_foo_BAR_ID_bar() for xyz_bar()."', $commentStart + 1);
+            }
+        }
+
+        // Check for a comment description.
+        $short = $comment->getShortComment();
+        if (trim($short) === '') {
+            $error = 'Missing short description in function doc comment';
+            $phpcsFile->addError($error, $commentStart, 'MissingShort');
+            return;
+        }
+
+        // No extra newline before short description.
+        $newlineCount = 0;
+        $newlineSpan  = strspn($short, $phpcsFile->eolChar);
+        if ($short !== '' && $newlineSpan > 0) {
+            $line  = ($newlineSpan > 1) ? 'newlines' : 'newline';
+            $error = "Extra $line found before function comment short description";
+            $phpcsFile->addError($error, ($commentStart + 1));
+        }
+
+        $newlineCount = (substr_count($short, $phpcsFile->eolChar) + 1);
+
+        // Exactly one blank line between short and long description.
+        $long = $comment->getLongComment();
+        if (empty($long) === false) {
+            $between        = $comment->getWhiteSpaceBetween();
+            $newlineBetween = substr_count($between, $phpcsFile->eolChar);
+            if ($newlineBetween !== 2) {
+                $error = 'There must be exactly one blank line between descriptions in function comment';
+                $phpcsFile->addError($error, ($commentStart + $newlineCount + 1), 'SpacingAfterShort');
+            }
+
+            $newlineCount += $newlineBetween;
+        }
+
+        // Short description must be single line and end with a full stop.
+        $testShort = trim($short);
+        $lastChar  = $testShort[(strlen($testShort) - 1)];
+        if (substr_count($testShort, $phpcsFile->eolChar) !== 0) {
+            $error = 'Function comment short description must be on a single line';
+            $phpcsFile->addError($error, ($commentStart + 1), 'ShortSingleLine');
+        }
+
+        if (preg_match('|[A-Z]|', $testShort[0]) === 0) {
+            $error = 'Function comment short description must start with a capital letter';
+            $phpcsFile->addError($error, ($commentStart + 1), 'ShortNotCapital');
+        }
+
+        if ($lastChar !== '.') {
+            $error = 'Function comment short description must end with a full stop';
+            $phpcsFile->addError($error, ($commentStart + 1), 'ShortFullStop');
+        }
+
+    }//end process()
+
+
+    /**
+     * Process any throw tags that this function comment has.
+     *
+     * @param int $commentStart The position in the stack where the
+     *                          comment started.
+     *
+     * @return void
+     */
+    protected function processThrows($commentStart)
+    {
+        if (count($this->commentParser->getThrows()) === 0) {
+            return;
+        }
+
+        foreach ($this->commentParser->getThrows() as $throw) {
+
+            $exception = $throw->getValue();
+            $errorPos  = ($commentStart + $throw->getLine());
+
+            if ($exception === '') {
+                $error = '@throws tag must contain the exception class name';
+                $this->currentFile->addError($error, $errorPos, 'EmptyThrows');
+            }
+        }
+
+    }//end processThrows()
+
+
+    /**
+     * Process the return comment of this function comment.
+     *
+     * @param int $commentStart The position in the stack where the comment started.
+     * @param int $commentEnd   The position in the stack where the comment ended.
+     *
+     * @return void
+     */
+    protected function processReturn($commentStart, $commentEnd)
+    {
+        // Skip constructor and destructor.
+        $className = '';
+        if ($this->_classToken !== null) {
+            $className = $this->currentFile->getDeclarationName($this->_classToken);
+            $className = strtolower(ltrim($className, '_'));
+        }
+
+        $methodName      = strtolower(ltrim($this->_methodName, '_'));
+        $isSpecialMethod = ($this->_methodName === '__construct' || $this->_methodName === '__destruct');
+
+        if ($isSpecialMethod === false && $methodName !== $className) {
+            $return = $this->commentParser->getReturn();
+            if ($return !== null) {
+                $errorPos = ($commentStart + $this->commentParser->getReturn()->getLine());
+                if (trim($return->getRawContent()) === '') {
+                    $error = '@return tag is empty in function comment';
+                    $this->currentFile->addError($error, $errorPos, 'EmptyReturn');
+                    return;
+                }
+
+                $comment = $return->getComment();
+                $commentWhitespace = $return->getWhitespaceBeforeComment();
+                if (substr_count($return->getWhitespaceBeforeValue(), $this->currentFile->eolChar) > 0) {
+                    $error = 'Data type of return value is missing';
+                    $this->currentFile->addError($error, $errorPos, 'MissingReturnType');
+                    // Treat the value as part of the comment.
+                    $comment = $return->getValue().' '.$comment;
+                    $commentWhitespace = $return->getWhitespaceBeforeValue();
+                } else if ($return->getWhitespaceBeforeValue() !== ' ') {
+                    $error = 'Expected 1 space before return type';
+                    $this->currentFile->addError($error, $errorPos, 'SpacingBeforeReturnType');
+                }
+
+                if (strpos($return->getValue(), '$') !== false) {
+                    $error = '@return data type must not contain "$"';
+                    $this->currentFile->addError($error, $errorPos, '$InReturnType');
+                }
+
+                if (in_array($return->getValue(), array('unknown_type', '<type>', 'type')) === true) {
+                    $error = 'Expected a valid @return data type, but found %s';
+                    $data = array($return->getValue());
+                    $this->currentFile->addError($error, $errorPos, 'InvalidReturnType', $data);
+                }
+
+                if (trim($comment) === '') {
+                    $error = 'Missing comment for @return statement';
+                    $this->currentFile->addError($error, $errorPos, 'MissingReturnComment');
+                } else if (substr_count($commentWhitespace, $this->currentFile->eolChar) !== 1) {
+                    $error = 'Return comment must be on the next line';
+                    $this->currentFile->addError($error, $errorPos, 'ReturnCommentNewLine');
+                } else if (substr_count($commentWhitespace, ' ') !== 3) {
+                    $error = 'Return comment indentation must be 2 additional spaces';
+                    $this->currentFile->addError($error, $errorPos + 1, 'ParamCommentIndentation');
+                }
+            }
+        }
+
+    }//end processReturn()
+
+
+    /**
+     * Process the function parameter comments.
+     *
+     * @param int $commentStart The position in the stack where
+     *                          the comment started.
+     *
+     * @return void
+     */
+    protected function processParams($commentStart)
+    {
+        $realParams = $this->currentFile->getMethodParameters($this->_functionToken);
+
+        $params      = $this->commentParser->getParams();
+        $foundParams = array();
+
+        if (empty($params) === false) {
+            // There must be an empty line before the parameter block.
+            if (substr_count($params[0]->getWhitespaceBefore(), $this->currentFile->eolChar) < 2) {
+                $error    = 'There must be an empty line before the parameter block';
+                $errorPos = ($params[0]->getLine() + $commentStart);
+                $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParams');
+            }
+
+            $lastParm = (count($params) - 1);
+            if (substr_count($params[$lastParm]->getWhitespaceAfter(), $this->currentFile->eolChar) !== 2) {
+                $error    = 'Last parameter comment requires a blank newline after it';
+                $errorPos = ($params[$lastParm]->getLine() + $commentStart);
+                $this->currentFile->addError($error, $errorPos, 'SpacingAfterParams');
+            }
+
+            $checkPos = 0;
+            foreach ($params as $param) {
+                $paramComment = trim($param->getComment());
+                $errorPos     = ($param->getLine() + $commentStart);
+
+                // Make sure that there is only one space before the var type.
+                if ($param->getWhitespaceBeforeType() !== ' ') {
+                    $error = 'Expected 1 space before variable type';
+                    $this->currentFile->addError($error, $errorPos, 'SpacingBeforeParamType');
+                }
+
+                // Make sure they are in the correct order,
+                // and have the correct name.
+                $pos = $param->getPosition();
+
+                $paramName = '[ UNKNOWN ]';
+                if ($param->getVarName() !== '') {
+                    $paramName = $param->getVarName();
+                }
+
+                // Make sure the names of the parameter comment matches the
+                // actual parameter.
+                $matched = false;
+                // Parameter documentation can be ommitted for some parameters, so
+                // we have to search the rest for a match.
+                while (isset($realParams[($checkPos)]) === true) {
+                    $realName          = $realParams[($checkPos)]['name'];
+                    $expectedParamName = $realName;
+                    $isReference       = $realParams[($checkPos)]['pass_by_reference'];
+
+                    // Append ampersand to name if passing by reference.
+                    if ($isReference === true && substr($paramName, 0, 1) === '&') {
+                        $expectedParamName = '&'.$realName;
+                    }
+
+                    if ($expectedParamName === $paramName) {
+                        $matched = true;
+                        break;
+                    }
+
+                    $checkPos++;
+                }
+
+                if ($matched === false && $paramName !== '...') {
+                    if ($checkPos >= $pos) {
+                        $code  = 'ParamNameNoMatch';
+                        $data  = array(
+                                  $paramName,
+                                  $realParams[($pos - 1)]['name'],
+                                  $pos,
+                                 );
+                        $error = 'Doc comment for var %s does not match ';
+                        if (strtolower($paramName) === strtolower($realParams[($pos - 1)]['name'])) {
+                            $error .= 'case of ';
+                            $code   = 'ParamNameNoCaseMatch';
+                        }
+
+                        $error .= 'actual variable name %s at position %s';
+                        $this->currentFile->addError($error, $errorPos, $code, $data);
+                        // Reset the parameter position to check for following
+                        // parameters.
+                        $checkPos = ($pos - 1);
+                    } else {
+                        // We must have an extra parameter comment.
+                        $error = 'Superfluous doc comment at position '.$pos;
+                        $this->currentFile->addError($error, $errorPos, 'ExtraParamComment');
+                    }//end if
+                }//end if
+
+                $checkPos++;
+
+                if ($param->getVarName() === '') {
+                    $error = 'Missing parameter name at position '.$pos;
+                    $this->currentFile->addError($error, $errorPos, 'MissingParamName');
+                }
+
+                if ($param->getType() === '') {
+                    $error = 'Missing parameter type at position '.$pos;
+                    $this->currentFile->addError($error, $errorPos, 'MissingParamType');
+                }
+
+                if (in_array($param->getType(), array('unknown_type', '<type>', 'type')) === true) {
+                    $error = 'Expected a valid @param data type, but found %s';
+                    $data = array($param->getType());
+                    $this->currentFile->addError($error, $errorPos, 'InvalidParamType', $data);
+                }
+
+                if ($paramComment === '') {
+                    $error = 'Missing comment for param "%s" at position %s';
+                    $data  = array(
+                              $paramName,
+                              $pos,
+                             );
+                    $this->currentFile->addError($error, $errorPos, 'MissingParamComment', $data);
+                } else if (substr_count($param->getWhitespaceBeforeComment(), $this->currentFile->eolChar) !== 1) {
+                    $error = 'Parameter comment must be on the next line at position '.$pos;
+                    $this->currentFile->addError($error, $errorPos, 'ParamCommentNewLine');
+                } else if (substr_count($param->getWhitespaceBeforeComment(), ' ') !== 3) {
+                    $error = 'Parameter comment indentation must be 2 additional spaces at position '.$pos;
+                    $this->currentFile->addError($error, ($errorPos + 1), 'ParamCommentIndentation');
+                }
+            }//end foreach
+        }//end if
+
+        $realNames = array();
+        foreach ($realParams as $realParam) {
+            $realNames[] = $realParam['name'];
+        }
+
+    }//end processParams()
+
+
+    /**
+     * Process the function "see" comments.
+     *
+     * @return void
+     */
+    protected function processSees($commentStart)
+    {
+        $sees = $this->commentParser->getSees();
+        foreach ($sees as $see) {
+            $errorPos = $see->getLine() + $commentStart;
+            if ($see->getWhitespaceBeforeContent() !== ' ') {
+                $error = 'Expected 1 space before see reference';
+                $this->currentFile->addError($error, $errorPos, 'SpacingBeforeSee');
+            }
+
+            $comment = trim($see->getContent());
+            if (strpos($comment, ' ') !== false) {
+                $error = 'The @see reference should not contain any additional text';
+                $this->currentFile->addError($error, $errorPos, 'SeeAdditionalText');
+                continue;
+            }
+            if (preg_match('/[\.!\?]$/', $comment) === 1) {
+                $error = 'Trailing punctuation for @see references is not allowed.';
+                $this->currentFile->addError($error, $errorPos, 'SeePunctuation');
+            }
+
+        }
+
+    }//end processSees()
+
+
+}//end class
+
+?>
-- 
1.7.11.msysgit.1

