*/ public function register() { return [\T_CLOSURE]; } /** * Processes this test, when one of its tokens is encountered. * * @since 1.1.0 * * @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) { $this->recommendedLines = (int) $this->recommendedLines; $this->maxLines = (int) $this->maxLines; $tokens = $phpcsFile->getTokens(); if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) { // Live coding/parse error. Shouldn't be possible as in that case tokenizer won't retokenize to T_CLOSURE. return; // @codeCoverageIgnore } $opener = $tokens[$stackPtr]['scope_opener']; $closer = $tokens[$stackPtr]['scope_closer']; $currentLine = $tokens[$opener]['line']; $closerLine = $tokens[$closer]['line']; $codeLines = 0; $commentLines = 0; $blankLines = 0; // Check whether the line of the scope opener needs to be counted, but ignore trailing comments on that line. $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $closer, true); if ($firstNonEmpty !== false && $tokens[$firstNonEmpty]['line'] === $currentLine) { ++$codeLines; } // Check whether the line of the scope closer needs to be counted. if ($closerLine !== $currentLine) { $hasCommentTokens = false; $hasCodeTokens = false; for ($i = ($closer - 1); $tokens[$i]['line'] === $closerLine && $i > $opener; $i--) { if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { $hasCodeTokens = true; } elseif (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { $hasCommentTokens = true; } } if ($hasCodeTokens === true) { ++$codeLines; } elseif ($hasCommentTokens === true) { ++$commentLines; } } // We've already examined the opener line, so move to the next line. for ($i = ($opener + 1); $tokens[$i]['line'] === $currentLine && $i < $closer; $i++); $currentLine = $tokens[$i]['line']; // Walk tokens. while ($currentLine !== $closerLine) { $hasCommentTokens = false; $hasCodeTokens = false; while ($tokens[$i]['line'] === $currentLine) { if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { $hasCodeTokens = true; } elseif (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { $hasCommentTokens = true; } ++$i; } if ($hasCodeTokens === true) { ++$codeLines; } elseif ($hasCommentTokens === true) { ++$commentLines; } else { // Only option left is that this is an empty line. ++$blankLines; } $currentLine = $tokens[$i]['line']; } $nonBlankLines = ($codeLines + $commentLines); $totalLines = ($codeLines + $commentLines + $blankLines); $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_CODE, $codeLines . ' lines'); $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_COMMENTS, $nonBlankLines . ' lines'); $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME_ALL, $totalLines . ' lines'); $lines = $codeLines; if ($this->ignoreCommentLines === false) { $lines += $commentLines; } if ($this->ignoreEmptyLines === false) { $lines += $blankLines; } $errorSuffix = ' Declare a named function instead. Found closure containing %s lines'; if ($lines > $this->maxLines) { $phpcsFile->addError( 'Closures which are longer than %s lines are forbidden.' . $errorSuffix, $stackPtr, 'ExceedsMaximum', [$this->maxLines, $lines] ); return; } if ($lines > $this->recommendedLines) { $phpcsFile->addWarning( 'It is recommended for closures to contain %s lines or less.' . $errorSuffix, $stackPtr, 'ExceedsRecommended', [$this->recommendedLines, $lines] ); } } }