|
4 | 4 |
|
5 | 5 | use Generator; |
6 | 6 | use PHP_CodeSniffer\Files\File; |
| 7 | +use PHP_CodeSniffer\Util\Tokens; |
7 | 8 | use SlevomatCodingStandard\Helpers\Annotation\ParameterAnnotation; |
8 | 9 | use SlevomatCodingStandard\Helpers\Annotation\ReturnAnnotation; |
9 | 10 | use function array_filter; |
|
17 | 18 | use function preg_match; |
18 | 19 | use function preg_replace; |
19 | 20 | use function sprintf; |
| 21 | +use function substr_count; |
20 | 22 | use const T_ANON_CLASS; |
21 | 23 | use const T_BITWISE_AND; |
22 | 24 | use const T_CLASS; |
|
32 | 34 | use const T_TRAIT; |
33 | 35 | use const T_USE; |
34 | 36 | use const T_VARIABLE; |
| 37 | +use const T_WHITESPACE; |
35 | 38 | use const T_YIELD; |
36 | 39 | use const T_YIELD_FROM; |
37 | 40 |
|
|
41 | 44 | class FunctionHelper |
42 | 45 | { |
43 | 46 |
|
| 47 | + public const LINE_INCLUDE_COMMENT = 1; |
| 48 | + public const LINE_INCLUDE_WHITESPACE = 2; |
| 49 | + |
44 | 50 | public const SPECIAL_FUNCTIONS = [ |
45 | 51 | 'array_key_exists', |
46 | 52 | 'array_slice', |
@@ -428,19 +434,88 @@ static function (int $functionOrMethodPointer) use ($phpcsFile): bool { |
428 | 434 | ); |
429 | 435 | } |
430 | 436 |
|
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 |
432 | 443 | { |
433 | | - $tokens = $file->getTokens(); |
434 | | - $token = $tokens[$position]; |
435 | | - |
436 | | - if (self::isAbstract($file, $position)) { |
| 444 | + if (self::isAbstract($file, $functionPosition)) { |
437 | 445 | return 0; |
438 | 446 | } |
439 | 447 |
|
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; |
442 | 450 |
|
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; |
444 | 519 | } |
445 | 520 |
|
446 | 521 | /** |
|
0 commit comments