Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

namespace SlevomatCodingStandard\Sniffs\Functions;

use Exception;
use PHP_CodeSniffer\Files\File;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\IndentationHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use UnexpectedValueException;
use function array_unique;
use function count;
use function in_array;
use function ltrim;
use function preg_match;
use function sprintf;
use function strlen;
use function trim;
Expand All @@ -27,12 +30,31 @@ class RequireMultiLineCallSniff extends AbstractLineCall
{

public const CODE_REQUIRED_MULTI_LINE_CALL = 'RequiredMultiLineCall';
private const DEFAULT_MIN_LINE_LENGTH = 121;

public int $minLineLength = 121;
public ?int $minLineLength = null;

public ?int $minParametersCount = null;

/** @var list<string> */
public array $excludedCallPatterns = [];

/** @var list<string>|null */
public ?array $excludedCallNormalizedPatterns = null;

public function process(File $phpcsFile, int $stringPointer): void
{
$this->minLineLength = SniffSettingsHelper::normalizeInteger($this->minLineLength);
$this->minLineLength = SniffSettingsHelper::normalizeNullableInteger($this->minLineLength);
$this->minParametersCount = SniffSettingsHelper::normalizeNullableInteger($this->minParametersCount);

if ($this->minLineLength !== null && $this->minParametersCount !== null) {
throw new UnexpectedValueException('Either minLineLength or minParametersCount can be set.');
}

// Maintain backward compatibility if no configuration provided
if ($this->minLineLength === null && $this->minParametersCount === null) {
$this->minLineLength = self::DEFAULT_MIN_LINE_LENGTH;
}

if (!$this->isCall($phpcsFile, $stringPointer)) {
return;
Expand Down Expand Up @@ -125,6 +147,13 @@ public function process(File $phpcsFile, int $stringPointer): void

$name = ltrim($tokens[$stringPointer]['content'], '\\');

if (
count($this->excludedCallPatterns) !== 0
&& $this->isCallNameInPatterns($name, $this->getExcludedCallNormalizedPatterns())
) {
return;
}

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) {
Expand Down Expand Up @@ -228,7 +257,11 @@ private function shouldReportError(
return true;
}

if ($lineLength < $this->minLineLength) {
if ($this->minLineLength !== null && $lineLength < $this->minLineLength) {
return false;
}

if ($this->minParametersCount !== null && $parametersCount < $this->minParametersCount) {
return false;
}

Expand All @@ -239,4 +272,31 @@ private function shouldReportError(
return strlen(trim($lineStart) . trim($lineEnd)) > $indentationLength;
}

/**
* @param list<string> $normalizedPatterns
*/
private function isCallNameInPatterns(string $callName, array $normalizedPatterns): bool
{
foreach ($normalizedPatterns as $pattern) {
if (!SniffSettingsHelper::isValidRegularExpression($pattern)) {
throw new Exception(sprintf('%s is not valid PCRE pattern.', $pattern));
}

if (preg_match($pattern, $callName) !== 0) {
return true;
}
}

return false;
}

/**
* @return list<string>
*/
private function getExcludedCallNormalizedPatterns(): array
{
$this->excludedCallNormalizedPatterns ??= SniffSettingsHelper::normalizeArray($this->excludedCallPatterns);
return $this->excludedCallNormalizedPatterns;
}

}
57 changes: 57 additions & 0 deletions tests/Sniffs/Functions/RequireMultiLineCallSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace SlevomatCodingStandard\Sniffs\Functions;

use SlevomatCodingStandard\Sniffs\TestCase;
use Throwable;

class RequireMultiLineCallSniffTest extends TestCase
{
Expand Down Expand Up @@ -135,4 +136,60 @@ public function testAllCallsErrors(): void
self::assertAllFixedInFile($report);
}

public function testThrowExceptionOnInvalidSetup(): void
{
$this->expectException(Throwable::class);

self::checkFile(
__DIR__ . '/data/requireMultiLineCallAllCallsNoErrors.php',
['minLineLength' => 100, 'minParametersCount' => 2],
);
}

public function testErrorsBasedOnParamCount(): void
{
$report = self::checkFile(
__DIR__ . '/data/requireMultiLineCallParamCountErrors.php',
[
'minParametersCount' => 2,
],
);
self::assertSame(2, $report->getErrorCount());

self::assertSniffError($report, 8, RequireMultiLineCallSniff::CODE_REQUIRED_MULTI_LINE_CALL);
self::assertSniffError($report, 14, RequireMultiLineCallSniff::CODE_REQUIRED_MULTI_LINE_CALL);

self::assertAllFixedInFile($report);
}

public function testThrowExceptionForInvalidPattern(): void
{
$this->expectException(Throwable::class);

self::checkFile(
__DIR__ . '/data/requireMultiLineCallErrors.php',
['includedCallPatterns' => ['invalidPattern']],
);

self::checkFile(
__DIR__ . '/data/requireMultiLineCallErrors.php',
['excludedCallPatterns' => ['invalidPattern']],
);
}

public function testExcludedCallPatterns(): void
{
$report = self::checkFile(__DIR__ . '/data/requireMultiLineCallExcludedCallsErrors.php', [
'minLineLength' => 0,
'excludedCallPatterns' => ['/dontReportError/'],
], [RequireMultiLineCallSniff::CODE_REQUIRED_MULTI_LINE_CALL]);

self::assertSame(2, $report->getErrorCount());

self::assertSniffError($report, 7, RequireMultiLineCallSniff::CODE_REQUIRED_MULTI_LINE_CALL);
self::assertSniffError($report, 13, RequireMultiLineCallSniff::CODE_REQUIRED_MULTI_LINE_CALL);

self::assertAllFixedInFile($report);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

class Whatever
{
public function __construct()
{
$this->reportError(
'false',
true
);
$this->dontReportError('true', false);
}
}

function () {
reportError(
'false',
true
);
dontReportError('true', false);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

class Whatever
{
public function __construct()
{
$this->reportError('false', true);
$this->dontReportError('true', false);
}
}

function () {
reportError('false', true);
dontReportError('true', false);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

class Whatever
{
public function __construct()
{
$this->doAnything('false');
$this->doAnything(
'true',
false
);
}
}

function ($text) {
sprintf(_('one parameter'));
return sprintf(
_('one parameter'),
$text
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

class Whatever
{
public function __construct()
{
$this->doAnything('false');
$this->doAnything('true', false);
}
}

function ($text) {
sprintf(_('one parameter'));
return sprintf(_('one parameter'), $text);
};