wishthis/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php
2023-09-20 13:52:46 +02:00

190 lines
6.4 KiB
PHP

<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/
namespace PHPCSExtra\Universal\Sniffs\Files;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Tokens\Collections;
/**
* A file should either declare (global/namespaced) functions or declare OO structures, but not both.
*
* Nested function declarations, i.e. functions declared within a function/method will be disregarded
* for the purposes of this sniff.
* The same goes for anonymous classes, closures and arrow functions.
*
* Notes:
* - This sniff has no opinion on side effects. If you want to sniff for those, use the PHPCS
* native `PSR1.Files.SideEffects` sniff.
* - This sniff has no opinion on multiple OO structures being declared in one file.
* If you want to sniff for that, use the PHPCS native `Generic.Files.OneObjectStructurePerFile` sniff.
*
* @since 1.0.0
*/
final class SeparateFunctionsFromOOSniff implements Sniff
{
/**
* Name of the metric.
*
* @since 1.0.0
*
* @var string
*/
const METRIC_NAME = 'Functions or OO declarations ?';
/**
* Tokens this sniff searches for.
*
* Enhanced from within the register() methods.
*
* @since 1.0.0
*
* @var array<int|string>
*/
private $search = [
// Some tokens to help skip over structures we're not interested in.
\T_START_HEREDOC => \T_START_HEREDOC,
\T_START_NOWDOC => \T_START_NOWDOC,
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 1.0.0
*
* @return array<int|string>
*/
public function register()
{
$this->search += Tokens::$ooScopeTokens;
$this->search += Collections::functionDeclarationTokens();
return Collections::phpOpenTags();
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 1.0.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 int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$firstOO = null;
$firstFunction = null;
$functionCount = 0;
$OOCount = 0;
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
// Ignore anything within square brackets.
if ($tokens[$i]['code'] !== \T_OPEN_CURLY_BRACKET
&& isset($tokens[$i]['bracket_opener'], $tokens[$i]['bracket_closer'])
&& $i === $tokens[$i]['bracket_opener']
) {
$i = $tokens[$i]['bracket_closer'];
continue;
}
// Skip past nested arrays, function calls and arbitrary groupings.
if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS
&& isset($tokens[$i]['parenthesis_closer'])
) {
$i = $tokens[$i]['parenthesis_closer'];
continue;
}
// Skip over potentially large docblocks.
if ($tokens[$i]['code'] === \T_DOC_COMMENT_OPEN_TAG
&& isset($tokens[$i]['comment_closer'])
) {
$i = $tokens[$i]['comment_closer'];
continue;
}
// Ignore everything else we're not interested in.
if (isset($this->search[$tokens[$i]['code']]) === false) {
continue;
}
// Skip over structures which won't contain anything we're interested in.
if (($tokens[$i]['code'] === \T_START_HEREDOC
|| $tokens[$i]['code'] === \T_START_NOWDOC
|| $tokens[$i]['code'] === \T_ANON_CLASS
|| $tokens[$i]['code'] === \T_CLOSURE
|| $tokens[$i]['code'] === \T_FN)
&& isset($tokens[$i]['scope_condition'], $tokens[$i]['scope_closer'])
&& $tokens[$i]['scope_condition'] === $i
) {
$i = $tokens[$i]['scope_closer'];
continue;
}
// This will be either a function declaration or an OO declaration token.
if ($tokens[$i]['code'] === \T_FUNCTION) {
if (isset($firstFunction) === false) {
$firstFunction = $i;
}
++$functionCount;
} else {
if (isset($firstOO) === false) {
$firstOO = $i;
}
++$OOCount;
}
if (isset($tokens[$i]['scope_closer']) === true) {
$i = $tokens[$i]['scope_closer'];
}
}
if ($functionCount > 0 && $OOCount > 0) {
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Both function and OO declarations');
$reportToken = \max($firstFunction, $firstOO);
$phpcsFile->addError(
'A file should either contain function declarations or OO structure declarations, but not both.'
. ' Found %d function declaration(s) and %d OO structure declaration(s).'
. ' The first function declaration was found on line %d;'
. ' the first OO declaration was found on line %d',
$reportToken,
'Mixed',
[
$functionCount,
$OOCount,
$tokens[$firstFunction]['line'],
$tokens[$firstOO]['line'],
]
);
} elseif ($functionCount > 0) {
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Only function(s)');
} elseif ($OOCount > 0) {
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Only OO structure(s)');
} else {
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Neither');
}
// Ignore the rest of the file.
return ($phpcsFile->numTokens + 1);
}
}