class ForbiddenClassesSniff
Hierarchy
- class \SlevomatCodingStandard\Sniffs\PHP\ForbiddenClassesSniff implements \PHP_CodeSniffer\Sniffs\Sniff
Expanded class hierarchy of ForbiddenClassesSniff
File
-
vendor/
slevomat/ coding-standard/ SlevomatCodingStandard/ Sniffs/ PHP/ ForbiddenClassesSniff.php, line 32
Namespace
SlevomatCodingStandard\Sniffs\PHPView source
class ForbiddenClassesSniff implements Sniff {
public const CODE_FORBIDDEN_CLASS = 'ForbiddenClass';
public const CODE_FORBIDDEN_PARENT_CLASS = 'ForbiddenParentClass';
public const CODE_FORBIDDEN_INTERFACE = 'ForbiddenInterface';
public const CODE_FORBIDDEN_TRAIT = 'ForbiddenTrait';
/** @var array<string, (string|null)> */
public $forbiddenClasses = [];
/** @var array<string, (string|null)> */
public $forbiddenExtends = [];
/** @var array<string, (string|null)> */
public $forbiddenInterfaces = [];
/** @var array<string, (string|null)> */
public $forbiddenTraits = [];
/** @var list<string> */
private static $keywordReferences = [
'self',
'parent',
'static',
];
/**
* @return array<int, (int|string)>
*/
public function register() : array {
$searchTokens = [];
if (count($this->forbiddenClasses) > 0) {
$this->forbiddenClasses = self::normalizeInputOption($this->forbiddenClasses);
$searchTokens[] = T_NEW;
$searchTokens[] = T_DOUBLE_COLON;
}
if (count($this->forbiddenExtends) > 0) {
$this->forbiddenExtends = self::normalizeInputOption($this->forbiddenExtends);
$searchTokens[] = T_EXTENDS;
}
if (count($this->forbiddenInterfaces) > 0) {
$this->forbiddenInterfaces = self::normalizeInputOption($this->forbiddenInterfaces);
$searchTokens[] = T_IMPLEMENTS;
}
if (count($this->forbiddenTraits) > 0) {
$this->forbiddenTraits = self::normalizeInputOption($this->forbiddenTraits);
$searchTokens[] = T_USE;
}
return $searchTokens;
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $tokenPointer
*/
public function process(File $phpcsFile, $tokenPointer) : void {
$tokens = $phpcsFile->getTokens();
$token = $tokens[$tokenPointer];
$nameTokens = array_merge(TokenHelper::getNameTokenCodes(), TokenHelper::$ineffectiveTokenCodes);
if ($token['code'] === T_IMPLEMENTS || $token['code'] === T_USE && UseStatementHelper::isTraitUse($phpcsFile, $tokenPointer)) {
$endTokenPointer = TokenHelper::findNext($phpcsFile, [
T_SEMICOLON,
T_OPEN_CURLY_BRACKET,
], $tokenPointer);
$references = $this->getAllReferences($phpcsFile, $tokenPointer, $endTokenPointer);
if ($token['code'] === T_IMPLEMENTS) {
$this->checkReferences($phpcsFile, $tokenPointer, $references, $this->forbiddenInterfaces);
}
else {
// Fixer does not work when traits contains aliases
$this->checkReferences($phpcsFile, $tokenPointer, $references, $this->forbiddenTraits, $tokens[$endTokenPointer]['code'] !== T_OPEN_CURLY_BRACKET);
}
}
elseif (in_array($token['code'], [
T_NEW,
T_EXTENDS,
], true)) {
$endTokenPointer = TokenHelper::findNextExcluding($phpcsFile, $nameTokens, $tokenPointer + 1);
$references = $this->getAllReferences($phpcsFile, $tokenPointer, $endTokenPointer);
$this->checkReferences($phpcsFile, $tokenPointer, $references, $token['code'] === T_NEW ? $this->forbiddenClasses : $this->forbiddenExtends);
}
elseif ($token['code'] === T_DOUBLE_COLON && !$this->isTraitsConflictResolutionToken($token)) {
$startTokenPointer = TokenHelper::findPreviousExcluding($phpcsFile, $nameTokens, $tokenPointer - 1);
$references = $this->getAllReferences($phpcsFile, $startTokenPointer, $tokenPointer);
$this->checkReferences($phpcsFile, $tokenPointer, $references, $this->forbiddenClasses);
}
}
/**
* @param list<array{fullyQualifiedName: string, startPointer: int|null, endPointer: int|null}> $references
* @param array<string, (string|null)> $forbiddenNames
*/
private function checkReferences(File $phpcsFile, int $tokenPointer, array $references, array $forbiddenNames, bool $isFixable = true) : void {
$token = $phpcsFile->getTokens()[$tokenPointer];
$details = [
T_NEW => [
'class',
self::CODE_FORBIDDEN_CLASS,
],
T_DOUBLE_COLON => [
'class',
self::CODE_FORBIDDEN_CLASS,
],
T_EXTENDS => [
'as a parent class',
self::CODE_FORBIDDEN_PARENT_CLASS,
],
T_IMPLEMENTS => [
'interface',
self::CODE_FORBIDDEN_INTERFACE,
],
T_USE => [
'trait',
self::CODE_FORBIDDEN_TRAIT,
],
];
foreach ($references as $reference) {
if (!array_key_exists($reference['fullyQualifiedName'], $forbiddenNames)) {
continue;
}
$alternative = $forbiddenNames[$reference['fullyQualifiedName']];
[
$nameType,
$code,
] = $details[$token['code']];
if ($alternative === null) {
$phpcsFile->addError(sprintf('Usage of %s %s is forbidden.', $reference['fullyQualifiedName'], $nameType), $reference['startPointer'], $code);
}
elseif (!$isFixable) {
$phpcsFile->addError(sprintf('Usage of %s %s is forbidden, use %s instead.', $reference['fullyQualifiedName'], $nameType, $alternative), $reference['startPointer'], $code);
}
else {
$fix = $phpcsFile->addFixableError(sprintf('Usage of %s %s is forbidden, use %s instead.', $reference['fullyQualifiedName'], $nameType, $alternative), $reference['startPointer'], $code);
if (!$fix) {
continue;
}
$phpcsFile->fixer
->beginChangeset();
FixerHelper::change($phpcsFile, $reference['startPointer'], $reference['endPointer'], $alternative);
$phpcsFile->fixer
->endChangeset();
}
}
}
/**
* @param array<string, array<int, int|string>|int|string> $token
*/
private function isTraitsConflictResolutionToken(array $token) : bool {
return is_array($token['conditions']) && array_pop($token['conditions']) === T_USE;
}
/**
* @return list<array{fullyQualifiedName: string, startPointer: int|null, endPointer: int|null}>
*/
private function getAllReferences(File $phpcsFile, int $startPointer, int $endPointer) : array {
// Always ignore first token
$startPointer++;
$references = [];
while ($startPointer < $endPointer) {
$nextComma = TokenHelper::findNext($phpcsFile, [
T_COMMA,
], $startPointer + 1);
$nextSeparator = min($endPointer, $nextComma ?? PHP_INT_MAX);
$reference = ReferencedNameHelper::getReferenceName($phpcsFile, $startPointer, $nextSeparator - 1);
if (strlen($reference) !== 0 && !in_array(strtolower($reference), self::$keywordReferences, true)) {
$references[] = [
'fullyQualifiedName' => NamespaceHelper::resolveClassName($phpcsFile, $reference, $startPointer),
'startPointer' => TokenHelper::findNextEffective($phpcsFile, $startPointer, $endPointer),
'endPointer' => TokenHelper::findPreviousEffective($phpcsFile, $nextSeparator - 1, $startPointer),
];
}
$startPointer = $nextSeparator + 1;
}
return $references;
}
/**
* @param array<string, (string|null)> $option
* @return array<string, (string|null)>
*/
private static function normalizeInputOption(array $option) : array {
$forbiddenClasses = [];
foreach ($option as $forbiddenClass => $alternative) {
$forbiddenClasses[self::normalizeClassName($forbiddenClass)] = self::normalizeClassName($alternative);
}
return $forbiddenClasses;
}
private static function normalizeClassName(?string $typeName) : ?string {
if ($typeName === null || strlen($typeName) === 0 || strtolower($typeName) === 'null') {
return null;
}
return NamespaceHelper::getFullyQualifiedTypeName($typeName);
}
}