clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\IndentationHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_merge;
use function rtrim;
use function trim;
use const T_CLOSE_PARENTHESIS;
use const T_COMMA;
use const T_FUNCTION;
use const T_OPEN_PARENTHESIS;
use const T_PARENT;
use const T_SELF;
use const T_STATIC;
use const T_WHITESPACE;
abstract class AbstractLineCall implements Sniff
{
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return array_merge(TokenHelper::getOnlyNameTokenCodes(), [T_SELF, T_STATIC, T_PARENT]);
}
protected function isCall(File $phpcsFile, int $stringPointer): bool
{
$tokens = $phpcsFile->getTokens();
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $stringPointer + 1);
if ($tokens[$nextPointer]['code'] !== T_OPEN_PARENTHESIS) {
return false;
}
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $stringPointer - 1);
return $tokens[$previousPointer]['code'] !== T_FUNCTION;
}
protected function getLineStart(File $phpcsFile, int $pointer): string
{
$firstPointerOnLine = TokenHelper::findFirstTokenOnLine($phpcsFile, $pointer);
return IndentationHelper::convertTabsToSpaces($phpcsFile, TokenHelper::getContent($phpcsFile, $firstPointerOnLine, $pointer));
}
protected function getCall(File $phpcsFile, int $parenthesisOpenerPointer, int $parenthesisCloserPointer): string
{
$tokens = $phpcsFile->getTokens();
$pointerBeforeParenthesisCloser = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisCloserPointer - 1);
$endPointer = $tokens[$pointerBeforeParenthesisCloser]['code'] === T_COMMA
? $pointerBeforeParenthesisCloser
: $parenthesisCloserPointer;
$call = '';
for ($i = $parenthesisOpenerPointer + 1; $i < $endPointer; $i++) {
if ($tokens[$i]['code'] === T_COMMA) {
$nextPointer = TokenHelper::findNextEffective($phpcsFile, $i + 1);
if ($tokens[$nextPointer]['code'] === T_CLOSE_PARENTHESIS) {
$i = $nextPointer - 1;
continue;
}
}
if ($tokens[$i]['code'] === T_WHITESPACE) {
if ($tokens[$i]['content'] === $phpcsFile->eolChar) {
if ($tokens[$i - 1]['code'] === T_COMMA) {
$call .= ' ';
}
continue;
} if ($tokens[$i]['column'] === 1) {
// Nothing
continue;
}
}
$call .= $tokens[$i]['content'];
}
return trim($call);
}
protected function getLineEnd(File $phpcsFile, int $pointer): string
{
$firstPointerOnNextLine = TokenHelper::findFirstTokenOnNextLine($phpcsFile, $pointer);
return rtrim(TokenHelper::getContent($phpcsFile, $pointer, $firstPointerOnNextLine - 1));
}
}

View File

@@ -0,0 +1,181 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function preg_match;
use function sprintf;
use function str_repeat;
use function strlen;
use function strpos;
use const T_FN;
use const T_FN_ARROW;
class ArrowFunctionDeclarationSniff implements Sniff
{
public const CODE_INCORRECT_SPACES_AFTER_KEYWORD = 'IncorrectSpacesAfterKeyword';
public const CODE_INCORRECT_SPACES_BEFORE_ARROW = 'IncorrectSpacesBeforeArrow';
public const CODE_INCORRECT_SPACES_AFTER_ARROW = 'IncorrectSpacesAfterArrow';
/** @var int */
public $spacesCountAfterKeyword = 1;
/** @var int */
public $spacesCountBeforeArrow = 1;
/** @var int */
public $spacesCountAfterArrow = 1;
/** @var bool */
public $allowMultiLine = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_FN,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $arrowFunctionPointer
*/
public function process(File $phpcsFile, $arrowFunctionPointer): void
{
$this->spacesCountAfterKeyword = SniffSettingsHelper::normalizeInteger($this->spacesCountAfterKeyword);
$this->spacesCountBeforeArrow = SniffSettingsHelper::normalizeInteger($this->spacesCountBeforeArrow);
$this->spacesCountAfterArrow = SniffSettingsHelper::normalizeInteger($this->spacesCountAfterArrow);
$this->checkSpacesAfterKeyword($phpcsFile, $arrowFunctionPointer);
$arrowPointer = TokenHelper::findNext($phpcsFile, T_FN_ARROW, $arrowFunctionPointer);
$this->checkSpacesBeforeArrow($phpcsFile, $arrowPointer);
$this->checkSpacesAfterArrow($phpcsFile, $arrowPointer);
}
private function checkSpacesAfterKeyword(File $phpcsFile, int $arrowFunctionPointer): void
{
$pointerAfter = TokenHelper::findNextNonWhitespace($phpcsFile, $arrowFunctionPointer + 1);
$spaces = TokenHelper::getContent($phpcsFile, $arrowFunctionPointer + 1, $pointerAfter - 1);
if ($this->allowMultiLine && strpos($spaces, $phpcsFile->eolChar) === 0) {
return;
}
$actualSpaces = strlen($spaces);
if (
$actualSpaces === $this->spacesCountAfterKeyword
&& (
$this->spacesCountAfterKeyword === 0
|| preg_match('~^ +$~', $spaces) === 1
)
) {
return;
}
$fix = $phpcsFile->addFixableError(
$this->formatErrorMessage('after "fn" keyword', $this->spacesCountAfterKeyword),
$arrowFunctionPointer,
self::CODE_INCORRECT_SPACES_AFTER_KEYWORD
);
if (!$fix) {
return;
}
$this->fixSpaces($phpcsFile, $arrowFunctionPointer, $pointerAfter, $this->spacesCountAfterKeyword);
}
private function checkSpacesBeforeArrow(File $phpcsFile, int $arrowPointer): void
{
$pointerBefore = TokenHelper::findPreviousNonWhitespace($phpcsFile, $arrowPointer - 1);
$spaces = TokenHelper::getContent($phpcsFile, $pointerBefore + 1, $arrowPointer - 1);
if ($this->allowMultiLine && strpos($spaces, $phpcsFile->eolChar) === 0) {
return;
}
$actualSpaces = strlen($spaces);
if (
$actualSpaces === $this->spacesCountBeforeArrow
&& (
$this->spacesCountBeforeArrow === 0
|| preg_match('~^ +$~', $spaces) === 1
)
) {
return;
}
$fix = $phpcsFile->addFixableError(
$this->formatErrorMessage('before =>', $this->spacesCountBeforeArrow),
$arrowPointer,
self::CODE_INCORRECT_SPACES_BEFORE_ARROW
);
if (!$fix) {
return;
}
$this->fixSpaces($phpcsFile, $pointerBefore, $arrowPointer, $this->spacesCountBeforeArrow);
}
private function checkSpacesAfterArrow(File $phpcsFile, int $arrowPointer): void
{
$pointerAfter = TokenHelper::findNextNonWhitespace($phpcsFile, $arrowPointer + 1);
$spaces = TokenHelper::getContent($phpcsFile, $arrowPointer + 1, $pointerAfter - 1);
if ($this->allowMultiLine && strpos($spaces, $phpcsFile->eolChar) === 0) {
return;
}
$actualSpaces = strlen($spaces);
if ($actualSpaces === $this->spacesCountAfterArrow && ($this->spacesCountAfterArrow === 0 || preg_match('~^ +$~', $spaces) === 1)) {
return;
}
$fix = $phpcsFile->addFixableError(
$this->formatErrorMessage('after =>', $this->spacesCountAfterArrow),
$arrowPointer,
self::CODE_INCORRECT_SPACES_AFTER_ARROW
);
if (!$fix) {
return;
}
$this->fixSpaces($phpcsFile, $arrowPointer, $pointerAfter, $this->spacesCountAfterArrow);
}
private function formatErrorMessage(string $suffix, int $requiredSpaces): string
{
return $requiredSpaces === 0
? sprintf('There must be no whitespace %s.', $suffix)
: sprintf('There must be exactly %d whitespace%s %s.', $requiredSpaces, $requiredSpaces !== 1 ? 's' : '', $suffix);
}
private function fixSpaces(File $phpcsFile, int $pointerBefore, int $pointerAfter, int $requiredSpaces): void
{
$phpcsFile->fixer->beginChangeset();
if ($requiredSpaces > 0) {
$phpcsFile->fixer->addContent($pointerBefore, str_repeat(' ', $requiredSpaces));
}
FixerHelper::removeBetween($phpcsFile, $pointerBefore, $pointerAfter);
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use const T_FN;
class DisallowArrowFunctionSniff implements Sniff
{
public const CODE_DISALLOWED_ARROW_FUNCTION = 'DisallowedArrowFunction';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_FN,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $arrowFunctionPointer
*/
public function process(File $phpcsFile, $arrowFunctionPointer): void
{
$phpcsFile->addError('Use of arrow function is disallowed.', $arrowFunctionPointer, self::CODE_DISALLOWED_ARROW_FUNCTION);
}
}

View File

@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use SlevomatCodingStandard\Helpers\FunctionHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_FUNCTION;
use const T_WHITESPACE;
class DisallowEmptyFunctionSniff implements Sniff
{
public const CODE_EMPTY_FUNCTION = 'EmptyFunction';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [T_FUNCTION];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
$tokens = $phpcsFile->getTokens();
if (FunctionHelper::isAbstract($phpcsFile, $functionPointer)) {
return;
}
if (FunctionHelper::getName($phpcsFile, $functionPointer) === '__construct') {
$propertyPromotion = TokenHelper::findNext(
$phpcsFile,
Tokens::$scopeModifiers,
$tokens[$functionPointer]['parenthesis_opener'] + 1,
$tokens[$functionPointer]['parenthesis_closer']
);
if ($propertyPromotion !== null) {
return;
}
}
$firstContent = TokenHelper::findNextExcluding(
$phpcsFile,
T_WHITESPACE,
$tokens[$functionPointer]['scope_opener'] + 1,
$tokens[$functionPointer]['scope_closer']
);
if ($firstContent !== null) {
return;
}
$phpcsFile->addError(
'Empty function body must have at least a comment to explain why is empty.',
$functionPointer,
self::CODE_EMPTY_FUNCTION
);
}
}

View File

@@ -0,0 +1,40 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use function sprintf;
use const T_PARAM_NAME;
class DisallowNamedArgumentsSniff implements Sniff
{
public const CODE_DISALLOWED_NAMED_ARGUMENT = 'DisallowedNamedArgument';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_PARAM_NAME,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $argumentNamePointer
*/
public function process(File $phpcsFile, $argumentNamePointer): void
{
$tokens = $phpcsFile->getTokens();
$phpcsFile->addError(
sprintf('Named arguments are disallowed, usage of named argument "%s" found.', $tokens[$argumentNamePointer]['content']),
$argumentNamePointer,
self::CODE_DISALLOWED_NAMED_ARGUMENT
);
}
}

View File

@@ -0,0 +1,95 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_key_exists;
use function array_merge;
use function in_array;
use const T_CLOSE_PARENTHESIS;
use const T_COMMA;
use const T_ISSET;
use const T_OPEN_PARENTHESIS;
use const T_PARENT;
use const T_SELF;
use const T_STATIC;
use const T_UNSET;
use const T_VARIABLE;
class DisallowTrailingCommaInCallSniff implements Sniff
{
public const CODE_DISALLOWED_TRAILING_COMMA = 'DisallowedTrailingComma';
/** @var bool */
public $onlySingleLine = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_OPEN_PARENTHESIS,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $parenthesisOpenerPointer
*/
public function process(File $phpcsFile, $parenthesisOpenerPointer): void
{
$tokens = $phpcsFile->getTokens();
if (array_key_exists('parenthesis_owner', $tokens[$parenthesisOpenerPointer])) {
return;
}
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if (!in_array(
$tokens[$pointerBeforeParenthesisOpener]['code'],
array_merge(
TokenHelper::getOnlyNameTokenCodes(),
[T_VARIABLE, T_ISSET, T_UNSET, T_CLOSE_PARENTHESIS, T_SELF, T_STATIC, T_PARENT]
),
true
)) {
return;
}
$parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
$pointerBeforeParenthesisCloser = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisCloserPointer - 1);
if ($tokens[$pointerBeforeParenthesisCloser]['code'] !== T_COMMA) {
return;
}
if ($this->onlySingleLine && $tokens[$parenthesisOpenerPointer]['line'] !== $tokens[$parenthesisCloserPointer]['line']) {
return;
}
$fix = $phpcsFile->addFixableError(
'Trailing comma after the last parameter in function call is disallowed.',
$pointerBeforeParenthesisCloser,
self::CODE_DISALLOWED_TRAILING_COMMA
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($pointerBeforeParenthesisCloser, '');
if ($tokens[$pointerBeforeParenthesisCloser]['line'] === $tokens[$parenthesisCloserPointer]['line']) {
FixerHelper::removeBetween($phpcsFile, $pointerBeforeParenthesisCloser, $parenthesisCloserPointer);
}
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,86 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_CLOSURE;
use const T_COMMA;
use const T_USE;
use const T_WHITESPACE;
class DisallowTrailingCommaInClosureUseSniff implements Sniff
{
public const CODE_DISALLOWED_TRAILING_COMMA = 'DisallowedTrailingComma';
/** @var bool */
public $onlySingleLine = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_CLOSURE,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
$tokens = $phpcsFile->getTokens();
$parenthesisCloserPointer = $tokens[$functionPointer]['parenthesis_closer'];
$usePointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisCloserPointer + 1);
if ($tokens[$usePointer]['code'] !== T_USE) {
return;
}
$useParenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $usePointer + 1);
$useParenthesisCloserPointer = $tokens[$useParenthesisOpenerPointer]['parenthesis_closer'];
$pointerBeforeUseParenthesisCloser = TokenHelper::findPreviousExcluding(
$phpcsFile,
T_WHITESPACE,
$tokens[$useParenthesisOpenerPointer]['parenthesis_closer'] - 1,
$useParenthesisOpenerPointer
);
if ($tokens[$pointerBeforeUseParenthesisCloser]['code'] !== T_COMMA) {
return;
}
if ($this->onlySingleLine && $tokens[$useParenthesisOpenerPointer]['line'] !== $tokens[$useParenthesisCloserPointer]['line']) {
return;
}
$fix = $phpcsFile->addFixableError(
'Trailing comma after the last inherited variable in "use" of closure declaration is disallowed.',
$pointerBeforeUseParenthesisCloser,
self::CODE_DISALLOWED_TRAILING_COMMA
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($pointerBeforeUseParenthesisCloser, '');
if ($tokens[$pointerBeforeUseParenthesisCloser]['line'] === $tokens[$useParenthesisCloserPointer]['line']) {
FixerHelper::removeBetween($phpcsFile, $pointerBeforeUseParenthesisCloser, $useParenthesisCloserPointer);
}
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,74 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_COMMA;
use const T_WHITESPACE;
class DisallowTrailingCommaInDeclarationSniff implements Sniff
{
public const CODE_DISALLOWED_TRAILING_COMMA = 'DisallowedTrailingComma';
/** @var bool */
public $onlySingleLine = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return TokenHelper::$functionTokenCodes;
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = $tokens[$functionPointer]['parenthesis_opener'];
$parenthesisCloserPointer = $tokens[$functionPointer]['parenthesis_closer'];
$pointerBeforeParenthesisCloser = TokenHelper::findPreviousExcluding(
$phpcsFile,
T_WHITESPACE,
$parenthesisCloserPointer - 1,
$parenthesisOpenerPointer
);
if ($tokens[$pointerBeforeParenthesisCloser]['code'] !== T_COMMA) {
return;
}
if ($this->onlySingleLine && $tokens[$parenthesisOpenerPointer]['line'] !== $tokens[$parenthesisCloserPointer]['line']) {
return;
}
$fix = $phpcsFile->addFixableError(
'Trailing comma after the last parameter in function declaration is disallowed.',
$pointerBeforeParenthesisCloser,
self::CODE_DISALLOWED_TRAILING_COMMA
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($pointerBeforeParenthesisCloser, '');
if ($tokens[$pointerBeforeParenthesisCloser]['line'] === $tokens[$parenthesisCloserPointer]['line']) {
FixerHelper::removeBetween($phpcsFile, $pointerBeforeParenthesisCloser, $parenthesisCloserPointer);
}
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,68 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FunctionHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use function array_filter;
use function array_keys;
use function array_reduce;
use function sprintf;
use const T_FUNCTION;
class FunctionLengthSniff implements Sniff
{
public const CODE_FUNCTION_LENGTH = 'FunctionLength';
/** @var int */
public $maxLinesLength = 20;
/** @var bool */
public $includeComments = false;
/** @var bool */
public $includeWhitespace = false;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [T_FUNCTION];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $file, $functionPointer): void
{
$this->maxLinesLength = SniffSettingsHelper::normalizeInteger($this->maxLinesLength);
$flags = array_keys(array_filter([
FunctionHelper::LINE_INCLUDE_COMMENT => $this->includeComments,
FunctionHelper::LINE_INCLUDE_WHITESPACE => $this->includeWhitespace,
]));
$flags = array_reduce($flags, static function ($carry, $flag): int {
return $carry | $flag;
}, 0);
$length = FunctionHelper::getFunctionLengthInLines($file, $functionPointer, $flags);
if ($length <= $this->maxLinesLength) {
return;
}
$errorMessage = sprintf(
'Your function is too long. Currently using %d lines. Can be up to %d lines.',
$length,
$this->maxLinesLength
);
$file->addError($errorMessage, $functionPointer, self::CODE_FUNCTION_LENGTH);
}
}

View File

@@ -0,0 +1,79 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function sprintf;
use const T_COLON;
use const T_PARAM_NAME;
use const T_WHITESPACE;
class NamedArgumentSpacingSniff implements Sniff
{
public const CODE_WHITESPACE_BEFORE_COLON = 'WhitespaceBeforeColon';
public const CODE_NO_WHITESPACE_AFTER_COLON = 'NoWhitespaceAfterColon';
/**
* @return list<string>
*/
public function register(): array
{
return [
T_PARAM_NAME,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $pointer
*/
public function process(File $phpcsFile, $pointer): void
{
$tokens = $phpcsFile->getTokens();
/** @var int $colonPointer */
$colonPointer = TokenHelper::findNext($phpcsFile, T_COLON, $pointer + 1);
$parameterName = $tokens[$pointer]['content'];
if ($colonPointer !== $pointer + 1) {
$fix = $phpcsFile->addFixableError(
sprintf('There must be no whitespace between named argument "%s" and colon.', $parameterName),
$colonPointer,
self::CODE_WHITESPACE_BEFORE_COLON
);
if ($fix) {
$phpcsFile->fixer->replaceToken($colonPointer - 1, '');
}
}
$whitespacePointer = $colonPointer + 1;
if (
$tokens[$whitespacePointer]['code'] === T_WHITESPACE
&& $tokens[$whitespacePointer]['content'] === ' '
) {
return;
}
$fix = $phpcsFile->addFixableError(
sprintf('There must be exactly one space after colon in named argument "%s".', $parameterName),
$colonPointer,
self::CODE_NO_WHITESPACE_AFTER_COLON
);
if (!$fix) {
return;
}
if ($tokens[$whitespacePointer]['code'] === T_WHITESPACE) {
$phpcsFile->fixer->replaceToken($whitespacePointer, ' ');
} else {
$phpcsFile->fixer->addContent($colonPointer, ' ');
}
}
}

View File

@@ -0,0 +1,158 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\ScopeHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function count;
use const T_BITWISE_AND;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSURE;
use const T_FN;
use const T_RETURN;
use const T_SEMICOLON;
use const T_USE;
use const T_WHITESPACE;
class RequireArrowFunctionSniff implements Sniff
{
public const CODE_REQUIRED_ARROW_FUNCTION = 'RequiredArrowFunction';
/** @var bool */
public $allowNested = true;
/** @var bool|null */
public $enable = null;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_CLOSURE,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $closurePointer
*/
public function process(File $phpcsFile, $closurePointer): void
{
$this->enable = SniffSettingsHelper::isEnabledByPhpVersion($this->enable, 70400);
if (!$this->enable) {
return;
}
$tokens = $phpcsFile->getTokens();
$returnPointer = TokenHelper::findNextEffective($phpcsFile, $tokens[$closurePointer]['scope_opener'] + 1);
if ($tokens[$returnPointer]['code'] !== T_RETURN) {
return;
}
$usePointer = TokenHelper::findNextEffective($phpcsFile, $tokens[$closurePointer]['parenthesis_closer'] + 1);
if ($tokens[$usePointer]['code'] === T_USE) {
$useOpenParenthesisPointer = TokenHelper::findNextEffective($phpcsFile, $usePointer + 1);
if (TokenHelper::findNext(
$phpcsFile,
T_BITWISE_AND,
$useOpenParenthesisPointer + 1,
$tokens[$useOpenParenthesisPointer]['parenthesis_closer']
) !== null) {
return;
}
}
if (!$this->allowNested) {
$closureOrArrowFunctionPointer = TokenHelper::findNext(
$phpcsFile,
[T_CLOSURE, T_FN],
$tokens[$closurePointer]['scope_opener'] + 1,
$tokens[$closurePointer]['scope_closer']
);
if ($closureOrArrowFunctionPointer !== null) {
return;
}
}
$fix = $phpcsFile->addFixableError('Use arrow function.', $closurePointer, self::CODE_REQUIRED_ARROW_FUNCTION);
if (!$fix) {
return;
}
$pointerAfterReturn = TokenHelper::findNextNonWhitespace($phpcsFile, $returnPointer + 1);
$semicolonAfterReturn = $this->findSemicolon($phpcsFile, $returnPointer);
$usePointer = TokenHelper::findNext(
$phpcsFile,
T_USE,
$tokens[$closurePointer]['parenthesis_closer'] + 1,
$tokens[$closurePointer]['scope_opener']
);
$nonWhitespacePointerBeforeScopeOpener = TokenHelper::findPreviousExcluding(
$phpcsFile,
T_WHITESPACE,
$tokens[$closurePointer]['scope_opener'] - 1
);
$nonWhitespacePointerAfterUseParenthesisCloser = null;
if ($usePointer !== null) {
$useParenthesiCloserPointer = TokenHelper::findNext($phpcsFile, T_CLOSE_PARENTHESIS, $usePointer + 1);
$nonWhitespacePointerAfterUseParenthesisCloser = TokenHelper::findNextExcluding(
$phpcsFile,
T_WHITESPACE,
$useParenthesiCloserPointer + 1
);
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($closurePointer, 'fn');
if ($nonWhitespacePointerAfterUseParenthesisCloser !== null) {
FixerHelper::removeBetween(
$phpcsFile,
$tokens[$closurePointer]['parenthesis_closer'],
$nonWhitespacePointerAfterUseParenthesisCloser
);
}
FixerHelper::removeBetween($phpcsFile, $nonWhitespacePointerBeforeScopeOpener, $pointerAfterReturn);
$phpcsFile->fixer->addContent($nonWhitespacePointerBeforeScopeOpener, ' => ');
FixerHelper::removeBetweenIncluding($phpcsFile, $semicolonAfterReturn, $tokens[$closurePointer]['scope_closer']);
$phpcsFile->fixer->endChangeset();
}
private function findSemicolon(File $phpcsFile, int $pointer): int
{
$tokens = $phpcsFile->getTokens();
$semicolonPointer = null;
for ($i = $pointer + 1; $i < count($tokens) - 1; $i++) {
if ($tokens[$i]['code'] !== T_SEMICOLON) {
continue;
}
if (!ScopeHelper::isInSameScope($phpcsFile, $pointer, $i)) {
continue;
}
$semicolonPointer = $i;
break;
}
/** @var int $semicolonPointer */
$semicolonPointer = $semicolonPointer;
return $semicolonPointer;
}
}

View File

@@ -0,0 +1,243 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\IndentationHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_unique;
use function count;
use function in_array;
use function ltrim;
use function sprintf;
use function strlen;
use function trim;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSE_SHORT_ARRAY;
use const T_COMMA;
use const T_DOUBLE_COLON;
use const T_NEW;
use const T_OBJECT_OPERATOR;
use const T_OPEN_PARENTHESIS;
use const T_OPEN_SHORT_ARRAY;
class RequireMultiLineCallSniff extends AbstractLineCall
{
public const CODE_REQUIRED_MULTI_LINE_CALL = 'RequiredMultiLineCall';
/** @var int */
public $minLineLength = 121;
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $stringPointer
*/
public function process(File $phpcsFile, $stringPointer): void
{
$this->minLineLength = SniffSettingsHelper::normalizeInteger($this->minLineLength);
if (!$this->isCall($phpcsFile, $stringPointer)) {
return;
}
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $stringPointer + 1);
$parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
// No parameters
$effectivePointerAfterParenthesisOpener = TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1);
if ($effectivePointerAfterParenthesisOpener === $parenthesisCloserPointer) {
return;
}
$parametersPointers = [TokenHelper::findNextEffective($phpcsFile, $parenthesisOpenerPointer + 1)];
$level = 0;
$pointers = TokenHelper::findNextAll(
$phpcsFile,
[T_COMMA, T_OPEN_PARENTHESIS, T_CLOSE_PARENTHESIS, T_OPEN_SHORT_ARRAY, T_CLOSE_SHORT_ARRAY],
$parenthesisOpenerPointer + 1,
$parenthesisCloserPointer
);
foreach ($pointers as $pointer) {
if (in_array($tokens[$pointer]['code'], [T_OPEN_PARENTHESIS, T_OPEN_SHORT_ARRAY], true)) {
$level++;
continue;
}
if (in_array($tokens[$pointer]['code'], [T_CLOSE_PARENTHESIS, T_CLOSE_SHORT_ARRAY], true)) {
$level--;
continue;
}
if ($level !== 0) {
continue;
}
$parameterPointer = TokenHelper::findNextEffective($phpcsFile, $pointer + 1, $parenthesisCloserPointer);
if ($parameterPointer !== null) {
$parametersPointers[] = $parameterPointer;
}
}
$lines = [
$tokens[$parenthesisOpenerPointer]['line'],
$tokens[$parenthesisCloserPointer]['line'],
];
foreach ($parametersPointers as $parameterPointer) {
$lines[] = $tokens[$parameterPointer]['line'];
}
// Each parameter on its line
if (count(array_unique($lines)) - 2 >= count($parametersPointers)) {
return;
}
if ($this->shouldBeSkipped($phpcsFile, $stringPointer, $parenthesisCloserPointer)) {
return;
}
$lineStart = $this->getLineStart($phpcsFile, $parenthesisOpenerPointer);
if ($tokens[$parenthesisCloserPointer]['line'] === $tokens[$stringPointer]['line']) {
$call = $this->getCall($phpcsFile, $parenthesisOpenerPointer, $parenthesisCloserPointer);
$lineEnd = $this->getLineEnd($phpcsFile, $parenthesisCloserPointer);
$lineLength = strlen($lineStart . $call . $lineEnd);
} else {
$lineEnd = $this->getLineEnd($phpcsFile, $parenthesisOpenerPointer);
$lineLength = strlen($lineStart . $lineEnd);
}
$firstNonWhitespaceOnLine = TokenHelper::findFirstNonWhitespaceOnLine($phpcsFile, $stringPointer);
$indentation = IndentationHelper::getIndentation($phpcsFile, $firstNonWhitespaceOnLine);
$oneIndentation = IndentationHelper::getOneIndentationLevel($indentation);
if (!$this->shouldReportError(
$lineLength,
$lineStart,
$lineEnd,
count($parametersPointers),
strlen(IndentationHelper::convertTabsToSpaces($phpcsFile, $oneIndentation))
)) {
return;
}
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $stringPointer - 1);
$name = ltrim($tokens[$stringPointer]['content'], '\\');
if (in_array($tokens[$previousPointer]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
$error = sprintf('Call of method %s() should be split to more lines.', $name);
} elseif ($tokens[$previousPointer]['code'] === T_NEW) {
$error = 'Constructor call should be split to more lines.';
} else {
$error = sprintf('Call of function %s() should be split to more lines.', $name);
}
$fix = $phpcsFile->addFixableError($error, $stringPointer, self::CODE_REQUIRED_MULTI_LINE_CALL);
if (!$fix) {
return;
}
$parametersIndentation = IndentationHelper::addIndentation($indentation);
$phpcsFile->fixer->beginChangeset();
for ($i = $parenthesisOpenerPointer + 1; $i < $parenthesisCloserPointer; $i++) {
if (in_array($i, $parametersPointers, true)) {
FixerHelper::removeWhitespaceBefore($phpcsFile, $i);
$phpcsFile->fixer->addContentBefore($i, $phpcsFile->eolChar . $parametersIndentation);
} elseif ($tokens[$i]['content'] === $phpcsFile->eolChar) {
$phpcsFile->fixer->addContent($i, $oneIndentation);
} else {
// Create conflict so inner calls are fixed in next loop
$phpcsFile->fixer->replaceToken($i, $tokens[$i]['content']);
}
}
$phpcsFile->fixer->addContentBefore($parenthesisCloserPointer, $phpcsFile->eolChar . $indentation);
$phpcsFile->fixer->endChangeset();
}
private function shouldBeSkipped(File $phpcsFile, int $stringPointer, int $parenthesisCloserPointer): bool
{
$tokens = $phpcsFile->getTokens();
$nameTokenCodes = TokenHelper::getOnlyNameTokenCodes();
$searchStartPointer = TokenHelper::findFirstNonWhitespaceOnLine($phpcsFile, $stringPointer);
while (true) {
$stringPointerBefore = TokenHelper::findNext($phpcsFile, $nameTokenCodes, $searchStartPointer, $stringPointer);
if ($stringPointerBefore === null) {
break;
}
$pointerAfterStringPointerBefore = TokenHelper::findNextEffective($phpcsFile, $stringPointerBefore + 1);
if (
$tokens[$pointerAfterStringPointerBefore]['code'] === T_OPEN_PARENTHESIS
&& $tokens[$pointerAfterStringPointerBefore]['parenthesis_closer'] > $stringPointer
) {
return true;
}
$searchStartPointer = $stringPointerBefore + 1;
}
$lastPointerOnLine = TokenHelper::findLastTokenOnLine($phpcsFile, $parenthesisCloserPointer);
$searchStartPointer = $parenthesisCloserPointer + 1;
while (true) {
$stringPointerAfter = TokenHelper::findNext($phpcsFile, $nameTokenCodes, $searchStartPointer, $lastPointerOnLine + 1);
if ($stringPointerAfter === null) {
break;
}
$pointerAfterStringPointerAfter = TokenHelper::findNextEffective($phpcsFile, $stringPointerAfter + 1);
if (
$pointerAfterStringPointerAfter !== null
&& $tokens[$pointerAfterStringPointerAfter]['code'] === T_OPEN_PARENTHESIS
&& $tokens[$tokens[$pointerAfterStringPointerAfter]['parenthesis_closer']]['line'] === $tokens[$stringPointer]['line']
&& $tokens[$pointerAfterStringPointerAfter]['parenthesis_closer'] !== TokenHelper::findNextEffective(
$phpcsFile,
$pointerAfterStringPointerAfter + 1
)
) {
return true;
}
$searchStartPointer = $stringPointerAfter + 1;
}
return false;
}
private function shouldReportError(
int $lineLength,
string $lineStart,
string $lineEnd,
int $parametersCount,
int $indentationLength
): bool
{
if ($this->minLineLength === 0) {
return true;
}
if ($lineLength < $this->minLineLength) {
return false;
}
if ($parametersCount > 1) {
return true;
}
return strlen(trim($lineStart) . trim($lineEnd)) > $indentationLength;
}
}

View File

@@ -0,0 +1,200 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_key_exists;
use function array_merge;
use function array_reverse;
use function in_array;
use function ltrim;
use function sprintf;
use function strlen;
use function strpos;
use const T_CLOSURE;
use const T_CONSTANT_ENCAPSED_STRING;
use const T_DOUBLE_COLON;
use const T_DOUBLE_QUOTED_STRING;
use const T_FN;
use const T_FUNCTION;
use const T_NEW;
use const T_OBJECT_OPERATOR;
use const T_OPEN_PARENTHESIS;
use const T_OPEN_SHORT_ARRAY;
use const T_STRING;
class RequireSingleLineCallSniff extends AbstractLineCall
{
public const CODE_REQUIRED_SINGLE_LINE_CALL = 'RequiredSingleLineCall';
/** @var int */
public $maxLineLength = 120;
/** @var bool */
public $ignoreWithComplexParameter = true;
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $stringPointer
*/
public function process(File $phpcsFile, $stringPointer): void
{
$this->maxLineLength = SniffSettingsHelper::normalizeInteger($this->maxLineLength);
if (!$this->isCall($phpcsFile, $stringPointer)) {
return;
}
if ($this->shouldBeSkipped($phpcsFile, $stringPointer)) {
return;
}
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $stringPointer + 1);
$parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
if ($tokens[$parenthesisOpenerPointer]['line'] === $tokens[$parenthesisCloserPointer]['line']) {
return;
}
if (TokenHelper::findNext(
$phpcsFile,
array_merge(TokenHelper::$inlineCommentTokenCodes, Tokens::$heredocTokens),
$parenthesisOpenerPointer + 1,
$parenthesisCloserPointer
) !== null) {
return;
}
for ($i = $parenthesisOpenerPointer + 1; $i < $parenthesisCloserPointer; $i++) {
if ($tokens[$i]['code'] !== T_CONSTANT_ENCAPSED_STRING && $tokens[$i]['code'] !== T_DOUBLE_QUOTED_STRING) {
continue;
}
if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) !== false) {
return;
}
}
if ($this->ignoreWithComplexParameter) {
if (
TokenHelper::findNext(
$phpcsFile,
[T_CLOSURE, T_FN, T_OPEN_SHORT_ARRAY],
$parenthesisOpenerPointer + 1,
$parenthesisCloserPointer
) !== null
) {
return;
}
// Contains inner call
$callSearchStartPointer = $parenthesisOpenerPointer + 1;
$nameTokenCodes = TokenHelper::getOnlyNameTokenCodes();
while (true) {
$innerStringPointer = TokenHelper::findNext(
$phpcsFile,
$nameTokenCodes,
$callSearchStartPointer,
$parenthesisCloserPointer
);
if ($innerStringPointer === null) {
break;
}
$pointerAfterInnerString = TokenHelper::findNextEffective($phpcsFile, $innerStringPointer + 1);
if (
$pointerAfterInnerString !== null
&& $tokens[$pointerAfterInnerString]['code'] === T_OPEN_PARENTHESIS
) {
return;
}
$callSearchStartPointer = $innerStringPointer + 1;
}
}
$lineStart = $this->getLineStart($phpcsFile, $parenthesisOpenerPointer);
$call = $this->getCall($phpcsFile, $parenthesisOpenerPointer, $parenthesisCloserPointer);
$lineEnd = $this->getLineEnd($phpcsFile, $parenthesisCloserPointer);
$lineLength = strlen($lineStart . $call . $lineEnd);
if (!$this->shouldReportError($lineLength)) {
return;
}
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $stringPointer - 1);
$name = ltrim($tokens[$stringPointer]['content'], '\\');
if (in_array($tokens[$previousPointer]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {
$error = sprintf('Call of method %s() should be placed on a single line.', $name);
} elseif ($tokens[$previousPointer]['code'] === T_NEW) {
$error = 'Constructor call should be placed on a single line.';
} else {
$error = sprintf('Call of function %s() should be placed on a single line.', $name);
}
$fix = $phpcsFile->addFixableError($error, $stringPointer, self::CODE_REQUIRED_SINGLE_LINE_CALL);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContent($parenthesisOpenerPointer, $call);
FixerHelper::removeBetween($phpcsFile, $parenthesisOpenerPointer, $parenthesisCloserPointer);
$phpcsFile->fixer->endChangeset();
}
private function shouldBeSkipped(File $phpcsFile, int $stringPointer): bool
{
$tokens = $phpcsFile->getTokens();
foreach (array_reverse(TokenHelper::findNextAll($phpcsFile, [T_OPEN_PARENTHESIS, T_FUNCTION], 0, $stringPointer)) as $pointer) {
if ($tokens[$pointer]['code'] === T_FUNCTION) {
if (array_key_exists('scope_closer', $tokens[$pointer]) && $tokens[$pointer]['scope_closer'] > $stringPointer) {
return false;
}
continue;
}
if ($tokens[$pointer]['parenthesis_closer'] < $stringPointer) {
continue;
}
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $pointer - 1);
if (
$pointerBeforeParenthesisOpener === null
|| $tokens[$pointerBeforeParenthesisOpener]['code'] !== T_STRING
) {
continue;
}
return true;
}
return false;
}
private function shouldReportError(int $lineLength): bool
{
if ($this->maxLineLength === 0) {
return true;
}
return $lineLength <= $this->maxLineLength;
}
}

View File

@@ -0,0 +1,104 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_key_exists;
use function array_merge;
use function in_array;
use const T_CLOSE_PARENTHESIS;
use const T_COMMA;
use const T_ISSET;
use const T_OPEN_PARENTHESIS;
use const T_PARENT;
use const T_SELF;
use const T_STATIC;
use const T_UNSET;
use const T_VARIABLE;
class RequireTrailingCommaInCallSniff implements Sniff
{
public const CODE_MISSING_TRAILING_COMMA = 'MissingTrailingComma';
/** @var bool|null */
public $enable = null;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_OPEN_PARENTHESIS,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $parenthesisOpenerPointer
*/
public function process(File $phpcsFile, $parenthesisOpenerPointer): void
{
$this->enable = SniffSettingsHelper::isEnabledByPhpVersion($this->enable, 70300);
if (!$this->enable) {
return;
}
$tokens = $phpcsFile->getTokens();
if (array_key_exists('parenthesis_owner', $tokens[$parenthesisOpenerPointer])) {
return;
}
$pointerBeforeParenthesisOpener = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisOpenerPointer - 1);
if (!in_array(
$tokens[$pointerBeforeParenthesisOpener]['code'],
array_merge(
TokenHelper::getOnlyNameTokenCodes(),
[T_VARIABLE, T_ISSET, T_UNSET, T_CLOSE_PARENTHESIS, T_SELF, T_STATIC, T_PARENT]
),
true
)) {
return;
}
$parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
if ($tokens[$parenthesisOpenerPointer]['line'] === $tokens[$parenthesisCloserPointer]['line']) {
return;
}
$pointerBeforeParenthesisCloser = TokenHelper::findPreviousEffective($phpcsFile, $parenthesisCloserPointer - 1);
if ($pointerBeforeParenthesisCloser === $parenthesisOpenerPointer) {
return;
}
if ($tokens[$parenthesisCloserPointer]['line'] === $tokens[$pointerBeforeParenthesisCloser]['line']) {
return;
}
if ($tokens[$pointerBeforeParenthesisCloser]['code'] === T_COMMA) {
return;
}
$fix = $phpcsFile->addFixableError(
'Multi-line function calls must have a trailing comma after the last parameter.',
$pointerBeforeParenthesisCloser,
self::CODE_MISSING_TRAILING_COMMA
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContent($pointerBeforeParenthesisCloser, ',');
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,85 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_CLOSURE;
use const T_COMMA;
use const T_USE;
use const T_WHITESPACE;
class RequireTrailingCommaInClosureUseSniff implements Sniff
{
public const CODE_MISSING_TRAILING_COMMA = 'MissingTrailingComma';
/** @var bool|null */
public $enable = null;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [T_CLOSURE];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
$this->enable = SniffSettingsHelper::isEnabledByPhpVersion($this->enable, 80000);
if (!$this->enable) {
return;
}
$tokens = $phpcsFile->getTokens();
$parenthesisCloserPointer = $tokens[$functionPointer]['parenthesis_closer'];
$usePointer = TokenHelper::findNextEffective($phpcsFile, $parenthesisCloserPointer + 1);
if ($tokens[$usePointer]['code'] !== T_USE) {
return;
}
$useParenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $usePointer + 1);
$useParenthesisCloserPointer = $tokens[$useParenthesisOpenerPointer]['parenthesis_closer'];
if ($tokens[$useParenthesisOpenerPointer]['line'] === $tokens[$useParenthesisCloserPointer]['line']) {
return;
}
$pointerBeforeUseParenthesisCloser = TokenHelper::findPreviousExcluding(
$phpcsFile,
T_WHITESPACE,
$useParenthesisCloserPointer - 1,
$useParenthesisOpenerPointer
);
if ($tokens[$pointerBeforeUseParenthesisCloser]['code'] === T_COMMA) {
return;
}
$fix = $phpcsFile->addFixableError(
'Multi-line "use" of closure declaration must have a trailing comma after the last inherited variable.',
$pointerBeforeUseParenthesisCloser,
self::CODE_MISSING_TRAILING_COMMA
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContent($pointerBeforeUseParenthesisCloser, ',');
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,77 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_COMMA;
class RequireTrailingCommaInDeclarationSniff implements Sniff
{
public const CODE_MISSING_TRAILING_COMMA = 'MissingTrailingComma';
/** @var bool|null */
public $enable = null;
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return TokenHelper::$functionTokenCodes;
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
$this->enable = SniffSettingsHelper::isEnabledByPhpVersion($this->enable, 80000);
if (!$this->enable) {
return;
}
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = $tokens[$functionPointer]['parenthesis_opener'];
$parenthesisCloserPointer = $tokens[$functionPointer]['parenthesis_closer'];
if ($tokens[$parenthesisOpenerPointer]['line'] === $tokens[$parenthesisCloserPointer]['line']) {
return;
}
$pointerBeforeParenthesisCloser = TokenHelper::findPreviousEffective(
$phpcsFile,
$parenthesisCloserPointer - 1,
$parenthesisOpenerPointer
);
if ($pointerBeforeParenthesisCloser === $parenthesisOpenerPointer) {
return;
}
if ($tokens[$pointerBeforeParenthesisCloser]['code'] === T_COMMA) {
return;
}
$fix = $phpcsFile->addFixableError(
'Multi-line function declaration must have a trailing comma after the last parameter.',
$pointerBeforeParenthesisCloser,
self::CODE_MISSING_TRAILING_COMMA
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContent($pointerBeforeParenthesisCloser, ',');
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,103 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use SlevomatCodingStandard\Helpers\VariableHelper;
use const T_CLOSURE;
use const T_DOUBLE_QUOTED_STRING;
use const T_FN;
use const T_OPEN_PARENTHESIS;
use const T_PARENT;
use const T_STATIC;
use const T_STRING;
use const T_VARIABLE;
class StaticClosureSniff implements Sniff
{
public const CODE_CLOSURE_NOT_STATIC = 'ClosureNotStatic';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_CLOSURE,
T_FN,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $closurePointer
*/
public function process(File $phpcsFile, $closurePointer): void
{
$tokens = $phpcsFile->getTokens();
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $closurePointer - 1);
if ($tokens[$previousPointer]['code'] === T_STATIC) {
return;
}
if ($tokens[$previousPointer]['code'] === T_OPEN_PARENTHESIS) {
$pointerBeforeParenthesis = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
if (
$tokens[$pointerBeforeParenthesis]['code'] === T_STRING
&& $tokens[$pointerBeforeParenthesis]['content'] === 'bind'
) {
return;
}
}
$closureScopeOpenerPointer = $tokens[$closurePointer]['scope_opener'];
$closureScopeCloserPointer = $tokens[$closurePointer]['scope_closer'];
$thisPointer = TokenHelper::findNextContent(
$phpcsFile,
T_VARIABLE,
'$this',
$closureScopeOpenerPointer + 1,
$closureScopeCloserPointer
);
if ($thisPointer !== null) {
return;
}
$stringPointers = TokenHelper::findNextAll(
$phpcsFile,
T_DOUBLE_QUOTED_STRING,
$closureScopeOpenerPointer + 1,
$closureScopeCloserPointer
);
foreach ($stringPointers as $stringPointer) {
if (VariableHelper::isUsedInScopeInString($phpcsFile, '$this', $stringPointer)) {
return;
}
}
$parentPointer = TokenHelper::findNext($phpcsFile, T_PARENT, $closureScopeOpenerPointer + 1, $closureScopeCloserPointer);
if ($parentPointer !== null) {
return;
}
$fix = $phpcsFile->addFixableError(
'Closure not using "$this" should be declared static.',
$closurePointer,
self::CODE_CLOSURE_NOT_STATIC
);
if (!$fix) {
return;
}
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContentBefore($closurePointer, 'static ');
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,127 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_key_exists;
use function count;
use function in_array;
use function ltrim;
use function sprintf;
use function strtolower;
use function trim;
use const T_COMMA;
use const T_DOUBLE_COLON;
use const T_FUNCTION;
use const T_OBJECT_OPERATOR;
use const T_OPEN_PARENTHESIS;
use const T_OPEN_SHORT_ARRAY;
class StrictCallSniff implements Sniff
{
public const CODE_STRICT_PARAMETER_MISSING = 'StrictParameterMissing';
public const CODE_NON_STRICT_COMPARISON = 'NonStrictComparison';
private const FUNCTIONS = [
'in_array' => 3,
'array_search' => 3,
'base64_decode' => 2,
'array_keys' => 3,
];
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return TokenHelper::getOnlyNameTokenCodes();
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $stringPointer
*/
public function process(File $phpcsFile, $stringPointer): void
{
$tokens = $phpcsFile->getTokens();
$parenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $stringPointer + 1);
if ($tokens[$parenthesisOpenerPointer]['code'] !== T_OPEN_PARENTHESIS) {
return;
}
$parenthesisCloserPointer = $tokens[$parenthesisOpenerPointer]['parenthesis_closer'];
$functionName = ltrim(strtolower($tokens[$stringPointer]['content']), '\\');
if (!array_key_exists($functionName, self::FUNCTIONS)) {
return;
}
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $stringPointer - 1);
if (in_array($tokens[$previousPointer]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_FUNCTION], true)) {
return;
}
$commaPointers = [];
for ($i = $parenthesisOpenerPointer + 1; $i < $parenthesisCloserPointer; $i++) {
if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
$i = $tokens[$i]['parenthesis_closer'];
continue;
}
if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
$i = $tokens[$i]['bracket_closer'];
continue;
}
if ($tokens[$i]['code'] === T_COMMA) {
$commaPointers[] = $i;
}
}
$commaPointersCount = count($commaPointers);
$parametersCount = $commaPointersCount + 1;
$lastCommaPointer = $commaPointersCount > 0 ? $commaPointers[$commaPointersCount - 1] : null;
$hasTrailingComma = false;
if (
$lastCommaPointer !== null
&& TokenHelper::findNextEffective($phpcsFile, $lastCommaPointer + 1, $parenthesisCloserPointer) === null
) {
$hasTrailingComma = true;
$parametersCount--;
}
if ($parametersCount === self::FUNCTIONS[$functionName]) {
$strictParameterValue = TokenHelper::getContent(
$phpcsFile,
$commaPointers[self::FUNCTIONS[$functionName] - 2] + 1,
($hasTrailingComma ? $lastCommaPointer : $parenthesisCloserPointer) - 1
);
if (strtolower(trim($strictParameterValue)) !== 'false') {
return;
}
$phpcsFile->addError(
sprintf('Strict parameter should be set to true in %s() call.', $functionName),
$stringPointer,
self::CODE_NON_STRICT_COMPARISON
);
} elseif ($parametersCount === self::FUNCTIONS[$functionName] - 1) {
$phpcsFile->addError(
sprintf('Strict parameter missing in %s() call.', $functionName),
$stringPointer,
self::CODE_STRICT_PARAMETER_MISSING
);
}
}
}

View File

@@ -0,0 +1,150 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use SlevomatCodingStandard\Helpers\VariableHelper;
use function in_array;
use function sprintf;
use const T_BITWISE_AND;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSURE;
use const T_COMMA;
use const T_OPEN_PARENTHESIS;
use const T_USE;
use const T_VARIABLE;
class UnusedInheritedVariablePassedToClosureSniff implements Sniff
{
public const CODE_UNUSED_INHERITED_VARIABLE = 'UnusedInheritedVariable';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_USE,
];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $usePointer
*/
public function process(File $phpcsFile, $usePointer): void
{
$tokens = $phpcsFile->getTokens();
/** @var int $parenthesisOpenerPointer */
$parenthesisOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $usePointer + 1);
if ($tokens[$parenthesisOpenerPointer]['code'] !== T_OPEN_PARENTHESIS) {
return;
}
/** @var int $closurePointer */
$closurePointer = TokenHelper::findPrevious($phpcsFile, T_CLOSURE, $usePointer - 1);
$currentPointer = $parenthesisOpenerPointer + 1;
do {
$variablePointer = TokenHelper::findNext(
$phpcsFile,
T_VARIABLE,
$currentPointer,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer']
);
if ($variablePointer === null) {
break;
}
$this->checkVariableUsage(
$phpcsFile,
$usePointer,
$parenthesisOpenerPointer,
$tokens[$parenthesisOpenerPointer]['parenthesis_closer'],
$variablePointer,
$closurePointer
);
$currentPointer = $variablePointer + 1;
} while (true);
}
private function checkVariableUsage(
File $phpcsFile,
int $usePointer,
int $useParenthesisOpenerPointer,
int $useParenthesisCloserPointer,
int $variablePointer,
int $scopeOwnerPointer
): void
{
$tokens = $phpcsFile->getTokens();
if (VariableHelper::isUsedInScope($phpcsFile, $scopeOwnerPointer, $variablePointer)) {
return;
}
$fix = $phpcsFile->addFixableError(
sprintf('Unused inherited variable %s passed to closure.', $tokens[$variablePointer]['content']),
$variablePointer,
self::CODE_UNUSED_INHERITED_VARIABLE
);
if (!$fix) {
return;
}
$fixStartPointer = $variablePointer;
do {
if ($tokens[$fixStartPointer - 1]['code'] === T_OPEN_PARENTHESIS) {
break;
}
$fixStartPointer--;
if ($tokens[$fixStartPointer]['code'] === T_COMMA) {
break;
}
} while (true);
$fixEndPointer = $variablePointer;
do {
if ($tokens[$fixEndPointer + 1]['code'] === T_CLOSE_PARENTHESIS) {
break;
}
if ($tokens[$fixEndPointer + 1]['code'] === T_COMMA && $tokens[$fixStartPointer]['code'] === T_COMMA) {
break;
}
if (in_array($tokens[$fixEndPointer + 1]['code'], [T_VARIABLE, T_BITWISE_AND], true)) {
break;
}
$fixEndPointer++;
} while (true);
$phpcsFile->fixer->beginChangeset();
FixerHelper::removeBetweenIncluding($phpcsFile, $fixStartPointer, $fixEndPointer);
$emptyUse = true;
for ($i = $useParenthesisOpenerPointer + 1; $i < $useParenthesisCloserPointer; $i++) {
if ($phpcsFile->fixer->getTokenContent($i) !== '') {
$emptyUse = false;
break;
}
}
if ($emptyUse) {
FixerHelper::removeBetweenIncluding($phpcsFile, $usePointer, $useParenthesisCloserPointer);
}
$phpcsFile->fixer->endChangeset();
}
}

View File

@@ -0,0 +1,107 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use SlevomatCodingStandard\Helpers\FunctionHelper;
use SlevomatCodingStandard\Helpers\SuppressHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use SlevomatCodingStandard\Helpers\VariableHelper;
use function array_merge;
use function in_array;
use function sprintf;
use const T_COMMA;
use const T_VARIABLE;
class UnusedParameterSniff implements Sniff
{
public const CODE_UNUSED_PARAMETER = 'UnusedParameter';
public const CODE_USELESS_SUPPRESS = 'UselessSuppress';
private const NAME = 'SlevomatCodingStandard.Functions.UnusedParameter';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return TokenHelper::$functionTokenCodes;
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
if (FunctionHelper::isAbstract($phpcsFile, $functionPointer)) {
return;
}
$isSuppressed = SuppressHelper::isSniffSuppressed($phpcsFile, $functionPointer, $this->getSniffName(self::CODE_UNUSED_PARAMETER));
$suppressUseless = true;
$tokens = $phpcsFile->getTokens();
$currentPointer = $tokens[$functionPointer]['parenthesis_opener'] + 1;
while (true) {
$parameterPointer = TokenHelper::findNext(
$phpcsFile,
T_VARIABLE,
$currentPointer,
$tokens[$functionPointer]['parenthesis_closer']
);
if ($parameterPointer === null) {
break;
}
$previousPointer = TokenHelper::findPrevious(
$phpcsFile,
array_merge([T_COMMA], Tokens::$scopeModifiers),
$parameterPointer - 1,
$tokens[$functionPointer]['parenthesis_opener']
);
if ($previousPointer !== null && in_array($tokens[$previousPointer]['code'], Tokens::$scopeModifiers, true)) {
$currentPointer = $parameterPointer + 1;
continue;
}
if (VariableHelper::isUsedInScope($phpcsFile, $functionPointer, $parameterPointer)) {
$currentPointer = $parameterPointer + 1;
continue;
}
if (!$isSuppressed) {
$phpcsFile->addError(
sprintf('Unused parameter %s.', $tokens[$parameterPointer]['content']),
$parameterPointer,
self::CODE_UNUSED_PARAMETER
);
} else {
$suppressUseless = false;
}
$currentPointer = $parameterPointer + 1;
}
if (!$isSuppressed || !$suppressUseless) {
return;
}
$phpcsFile->addError(
sprintf('Useless %s %s', SuppressHelper::ANNOTATION, self::NAME),
$functionPointer,
self::CODE_USELESS_SUPPRESS
);
}
private function getSniffName(string $sniffName): string
{
return sprintf('%s.%s', self::NAME, $sniffName);
}
}

View File

@@ -0,0 +1,88 @@
<?php declare(strict_types = 1);
namespace SlevomatCodingStandard\Sniffs\Functions;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function array_key_exists;
use function count;
use function sprintf;
use function strtolower;
use const T_COMMA;
class UselessParameterDefaultValueSniff implements Sniff
{
public const CODE_USELESS_PARAMETER_DEFAULT_VALUE = 'UselessParameterDefaultValue';
/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return TokenHelper::$functionTokenCodes;
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $functionPointer
*/
public function process(File $phpcsFile, $functionPointer): void
{
$parameters = $phpcsFile->getMethodParameters($functionPointer);
$parametersCount = count($parameters);
if ($parametersCount === 0) {
return;
}
for ($i = 0; $i < $parametersCount; $i++) {
$parameter = $parameters[$i];
if (!array_key_exists('default', $parameter)) {
continue;
}
$defaultValue = strtolower($parameter['default']);
if ($defaultValue === 'null' && !$parameter['nullable_type']) {
continue;
}
for ($j = $i + 1; $j < $parametersCount; $j++) {
$nextParameter = $parameters[$j];
if (array_key_exists('default', $nextParameter)) {
continue;
}
if ($nextParameter['variable_length']) {
break;
}
$fix = $phpcsFile->addFixableError(
sprintf('Useless default value of parameter %s.', $parameter['name']),
$parameter['token'],
self::CODE_USELESS_PARAMETER_DEFAULT_VALUE
);
if (!$fix) {
continue;
}
$commaPointer = TokenHelper::findPrevious($phpcsFile, T_COMMA, $parameters[$i + 1]['token'] - 1);
/** @var int $parameterPointer */
$parameterPointer = $parameter['token'];
$phpcsFile->fixer->beginChangeset();
for ($k = $parameterPointer + 1; $k < $commaPointer; $k++) {
$phpcsFile->fixer->replaceToken($k, '');
}
$phpcsFile->fixer->endChangeset();
break;
}
}
}
}