Skip to content

Commit 9da58ae

Browse files
bkdotcomkukulich
authored andcommitted
SlevomatCodingStandard.Functions.FunctionLengthSniff: New options "includeComments" and "includeWhitespace"
1 parent a0b5814 commit 9da58ae

File tree

7 files changed

+175
-10
lines changed

7 files changed

+175
-10
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ Reports closures not using `$this` that are not declared `static`.
297297

298298
Disallows long functions. This sniff provides the following setting:
299299

300+
* `includeComments`: should comments be included in the count (default value is false).
301+
* `includeWhitespace`: shoud empty lines be included in the count (default value is false).
300302
* `maxLinesLength`: specifies max allowed function lines length (default value is 20).
301303

302304
#### SlevomatCodingStandard.PHP.DisallowDirectMagicInvokeCall 🔧

SlevomatCodingStandard/Helpers/FunctionHelper.php

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Generator;
66
use PHP_CodeSniffer\Files\File;
7+
use PHP_CodeSniffer\Util\Tokens;
78
use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation;
89
use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation;
910
use function array_filter;
@@ -17,6 +18,7 @@
1718
use function preg_match;
1819
use function preg_replace;
1920
use function sprintf;
21+
use function substr_count;
2022
use const T_ANON_CLASS;
2123
use const T_BITWISE_AND;
2224
use const T_CLASS;
@@ -32,6 +34,7 @@
3234
use const T_TRAIT;
3335
use const T_USE;
3436
use const T_VARIABLE;
37+
use const T_WHITESPACE;
3538
use const T_YIELD;
3639
use const T_YIELD_FROM;
3740

@@ -41,6 +44,9 @@
4144
class FunctionHelper
4245
{
4346

47+
public const LINE_INCLUDE_COMMENT = 1;
48+
public const LINE_INCLUDE_WHITESPACE = 2;
49+
4450
public const SPECIAL_FUNCTIONS = [
4551
'array_key_exists',
4652
'array_slice',
@@ -428,19 +434,88 @@ static function (int $functionOrMethodPointer) use ($phpcsFile): bool {
428434
);
429435
}
430436

431-
public static function getFunctionLengthInLines(File $file, int $position): int
437+
/**
438+
* @param File $file
439+
* @param int $functionPosition
440+
* @param int $flags optional bitmask of self::LINE_INCLUDE_* constants
441+
*/
442+
public static function getFunctionLengthInLines(File $file, int $functionPosition, int $flags = 0): int
432443
{
433-
$tokens = $file->getTokens();
434-
$token = $tokens[$position];
435-
436-
if (self::isAbstract($file, $position)) {
444+
if (self::isAbstract($file, $functionPosition)) {
437445
return 0;
438446
}
439447

440-
$firstToken = $tokens[$token['scope_opener']];
441-
$lastToken = $tokens[$token['scope_closer']];
448+
$includeWhitespace = ($flags & self::LINE_INCLUDE_WHITESPACE) === self::LINE_INCLUDE_WHITESPACE;
449+
$includeComments = ($flags & self::LINE_INCLUDE_COMMENT) === self::LINE_INCLUDE_COMMENT;
442450

443-
return $lastToken['line'] - $firstToken['line'] - 1;
451+
$tokens = $file->getTokens();
452+
$token = $tokens[$functionPosition];
453+
454+
$tokenOpenerPosition = $token['scope_opener'];
455+
$tokenCloserPosition = $token['scope_closer'];
456+
$tokenOpenerLine = $tokens[$tokenOpenerPosition]['line'];
457+
$tokenCloserLine = $tokens[$tokenCloserPosition]['line'];
458+
459+
$lineCount = 0;
460+
$lastCommentLine = null;
461+
$previousIncludedPosition = null;
462+
463+
for ($position = $tokenOpenerPosition; $position <= $tokenCloserPosition - 1; $position++) {
464+
$token = $tokens[$position];
465+
if ($includeComments === false) {
466+
if (in_array($token['code'], Tokens::$commentTokens, true)) {
467+
if (
468+
$previousIncludedPosition !== null &&
469+
substr_count($token['content'], $file->eolChar) > 0 &&
470+
$token['line'] === $tokens[$previousIncludedPosition]['line']
471+
) {
472+
// Comment with linebreak starting on same line as included Token
473+
$lineCount++;
474+
}
475+
// Don't include comment
476+
$lastCommentLine = $token['line'];
477+
continue;
478+
}
479+
if (
480+
$previousIncludedPosition !== null &&
481+
$token['code'] === T_WHITESPACE &&
482+
$token['line'] === $lastCommentLine &&
483+
$token['line'] !== $tokens[$previousIncludedPosition]['line']
484+
) {
485+
// Whitespace after block comment... still on comment line...
486+
// Ignore along with the comment
487+
continue;
488+
}
489+
}
490+
if ($token['code'] === T_WHITESPACE) {
491+
$nextNonWhitespacePosition = $file->findNext(T_WHITESPACE, $position + 1, $tokenCloserPosition + 1, true);
492+
if (
493+
$includeWhitespace === false &&
494+
$token['column'] === 1 &&
495+
$nextNonWhitespacePosition !== false &&
496+
$tokens[$nextNonWhitespacePosition]['line'] !== $token['line']
497+
) {
498+
// This line is nothing but whitepace
499+
$position = $nextNonWhitespacePosition - 1;
500+
continue;
501+
}
502+
if ($previousIncludedPosition === $tokenOpenerPosition && $token['line'] === $tokenOpenerLine) {
503+
// Don't linclude line break after opening "{"
504+
// Unless there was code or an (included) comment following the "{"
505+
continue;
506+
}
507+
}
508+
if ($token['code'] !== T_WHITESPACE) {
509+
$previousIncludedPosition = $position;
510+
}
511+
$newLineFoundCount = substr_count($token['content'], $file->eolChar);
512+
$lineCount += $newLineFoundCount;
513+
}
514+
if ($tokens[$previousIncludedPosition]['line'] === $tokenCloserLine) {
515+
// There is code or comment on the closing "}" line...
516+
$lineCount++;
517+
}
518+
return $lineCount;
444519
}
445520

446521
/**

SlevomatCodingStandard/Sniffs/Functions/FunctionLengthSniff.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use PHP_CodeSniffer\Sniffs\Sniff;
77
use SlevomatCodingStandard\Helpers\FunctionHelper;
88
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
9+
use function array_filter;
10+
use function array_keys;
11+
use function array_reduce;
912
use function sprintf;
1013
use const T_FUNCTION;
1114

@@ -17,6 +20,12 @@ class FunctionLengthSniff implements Sniff
1720
/** @var int */
1821
public $maxLinesLength = 20;
1922

23+
/** @var bool */
24+
public $includeComments = false;
25+
26+
/** @var bool */
27+
public $includeWhitespace = false;
28+
2029
/**
2130
* @return array<int, (int|string)>
2231
*/
@@ -32,7 +41,15 @@ public function register(): array
3241
*/
3342
public function process(File $file, $functionPointer): void
3443
{
35-
$length = FunctionHelper::getFunctionLengthInLines($file, $functionPointer);
44+
$flags = array_keys(array_filter([
45+
FunctionHelper::LINE_INCLUDE_COMMENT => $this->includeComments,
46+
FunctionHelper::LINE_INCLUDE_WHITESPACE => $this->includeWhitespace,
47+
]));
48+
$flags = array_reduce($flags, static function ($carry, $flag): int {
49+
return $carry | $flag;
50+
}, 0);
51+
52+
$length = FunctionHelper::getFunctionLengthInLines($file, $functionPointer, $flags);
3653

3754
if ($length <= SniffSettingsHelper::normalizeInteger($this->maxLinesLength)) {
3855
return;

tests/Helpers/FunctionHelperTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,29 @@ public function testGetAllFunctionNames(): void
440440
self::assertSame(['foo', 'boo'], FunctionHelper::getAllFunctionNames($phpcsFile));
441441
}
442442

443+
public function testGetFunctionLengthInLines(): void
444+
{
445+
$phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionLength.php');
446+
$functionPointer = $this->findFunctionPointerByName($phpcsFile, 'countMe');
447+
448+
self::assertSame(2, FunctionHelper::getFunctionLengthInLines($phpcsFile, $functionPointer));
449+
self::assertSame(7, FunctionHelper::getFunctionLengthInLines(
450+
$phpcsFile,
451+
$functionPointer,
452+
FunctionHelper::LINE_INCLUDE_COMMENT
453+
));
454+
self::assertSame(3, FunctionHelper::getFunctionLengthInLines(
455+
$phpcsFile,
456+
$functionPointer,
457+
FunctionHelper::LINE_INCLUDE_WHITESPACE
458+
));
459+
self::assertSame(8, FunctionHelper::getFunctionLengthInLines(
460+
$phpcsFile,
461+
$functionPointer,
462+
FunctionHelper::LINE_INCLUDE_COMMENT | FunctionHelper::LINE_INCLUDE_WHITESPACE
463+
));
464+
}
465+
443466
public function testFindClassPointer(): void
444467
{
445468
$phpcsFile = $this->getCodeSnifferFile(__DIR__ . '/data/functionNames.php');
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
class FooClass
4+
{
5+
6+
public function countMe(): string
7+
{
8+
/*
9+
block comment
10+
*/
11+
1 + 1; // slash comment
12+
# hash comment
13+
14+
return 'This is the only line that matters (by default)'; /* inline comment */
15+
/* inline comment */ }
16+
}

tests/Sniffs/Functions/FunctionLengthSniffTest.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,35 @@ public function testErrors(): void
1919

2020
self::assertSame(1, $report->getErrorCount());
2121

22-
self::assertSniffError($report, 3, FunctionLengthSniff::CODE_FUNCTION_LENGTH);
22+
self::assertSniffError($report, 3, FunctionLengthSniff::CODE_FUNCTION_LENGTH, 'Currently using 21 lines');
23+
}
24+
25+
public function testWithComments(): void
26+
{
27+
$report = self::checkFile(
28+
__DIR__ . '/data/functionLengthErrors.php',
29+
[
30+
'includeComments' => true,
31+
]
32+
);
33+
34+
self::assertSame(1, $report->getErrorCount());
35+
36+
self::assertSniffError($report, 3, FunctionLengthSniff::CODE_FUNCTION_LENGTH, 'Currently using 24 lines');
37+
}
38+
39+
public function testWithWhitespace(): void
40+
{
41+
$report = self::checkFile(
42+
__DIR__ . '/data/functionLengthErrors.php',
43+
[
44+
'includeWhitespace' => true,
45+
]
46+
);
47+
48+
self::assertSame(1, $report->getErrorCount());
49+
50+
self::assertSniffError($report, 3, FunctionLengthSniff::CODE_FUNCTION_LENGTH, 'Currently using 22 lines');
2351
}
2452

2553
}

tests/Sniffs/Functions/data/functionLengthErrors.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
function dummyFunctionWithTooManyLines()
44
{
5+
/*
6+
Block comment
7+
*/
8+
59
echo 'line 1';
610
echo 'line 2';
711
echo 'line 3';

0 commit comments

Comments
 (0)