201 lines
5.4 KiB
PHP
201 lines
5.4 KiB
PHP
<?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;
|
|
}
|
|
|
|
}
|