*/ public function register(): array { return TokenHelper::$typeWithAnonymousClassKeywordTokenCodes; } /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @param int $classPointer */ public function process(File $phpcsFile, $classPointer): void { $this->linesCountBetweenMembers = SniffSettingsHelper::normalizeInteger($this->linesCountBetweenMembers); $tokens = $phpcsFile->getTokens(); $memberPointer = null; $previousMemberPointer = null; do { $previousMemberPointer = $memberPointer; $memberPointer = $this->findNextMember( $phpcsFile, $classPointer, $previousMemberPointer ?? $tokens[$classPointer]['scope_opener'] ); if ($memberPointer === null) { break; } if ($previousMemberPointer === null) { continue; } if ($tokens[$previousMemberPointer]['code'] === $tokens[$memberPointer]['code']) { continue; } $previousMemberEndPointer = $this->getMemberEndPointer($phpcsFile, $previousMemberPointer); $hasCommentWithNewLineAfterPreviousMember = false; $commentPointerAfterPreviousMember = TokenHelper::findNextNonWhitespace($phpcsFile, $previousMemberEndPointer + 1); if ( in_array($tokens[$commentPointerAfterPreviousMember]['code'], TokenHelper::$inlineCommentTokenCodes, true) && ( $tokens[$previousMemberEndPointer]['line'] === $tokens[$commentPointerAfterPreviousMember]['line'] || $tokens[$previousMemberEndPointer]['line'] + 1 === $tokens[$commentPointerAfterPreviousMember]['line'] ) ) { $previousMemberEndPointer = CommentHelper::getCommentEndPointer($phpcsFile, $commentPointerAfterPreviousMember); if (StringHelper::endsWith($tokens[$commentPointerAfterPreviousMember]['content'], $phpcsFile->eolChar)) { $hasCommentWithNewLineAfterPreviousMember = true; } } $memberStartPointer = $this->getMemberStartPointer($phpcsFile, $memberPointer); $actualLinesCount = $tokens[$memberStartPointer]['line'] - $tokens[$previousMemberEndPointer]['line'] - 1; if ($actualLinesCount === $this->linesCountBetweenMembers) { continue; } $errorMessage = $this->linesCountBetweenMembers === 1 ? 'Expected 1 blank line between class members, found %2$d.' : 'Expected %1$d blank lines between class members, found %2$d.'; $fix = $phpcsFile->addFixableError( sprintf($errorMessage, $this->linesCountBetweenMembers, $actualLinesCount), $memberPointer, self::CODE_INCORRECT_COUNT_OF_BLANK_LINES_BETWEEN_MEMBERS ); if (!$fix) { continue; } $newLines = str_repeat( $phpcsFile->eolChar, $this->linesCountBetweenMembers + ($hasCommentWithNewLineAfterPreviousMember ? 0 : 1) ); $firstPointerOnMemberLine = TokenHelper::findFirstTokenOnLine($phpcsFile, $memberStartPointer); $phpcsFile->fixer->beginChangeset(); $phpcsFile->fixer->addContent($previousMemberEndPointer, $newLines); FixerHelper::removeBetween($phpcsFile, $previousMemberEndPointer, $firstPointerOnMemberLine); $phpcsFile->fixer->endChangeset(); } while (true); } private function findNextMember(File $phpcsFile, int $classPointer, int $previousMemberPointer): ?int { $tokens = $phpcsFile->getTokens(); $memberPointer = $previousMemberPointer; do { $memberPointer = TokenHelper::findNext( $phpcsFile, [T_USE, T_CONST, T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_READONLY, T_STATIC, T_FUNCTION, T_ENUM_CASE], $memberPointer + 1, $tokens[$classPointer]['scope_closer'] ); if ($memberPointer === null) { return null; } if ($tokens[$memberPointer]['code'] === T_USE) { if (!UseStatementHelper::isTraitUse($phpcsFile, $memberPointer)) { continue; } } elseif (in_array($tokens[$memberPointer]['code'], [T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_READONLY, T_STATIC], true)) { $asPointer = TokenHelper::findPreviousEffective($phpcsFile, $memberPointer - 1); if ($tokens[$asPointer]['code'] === T_AS) { continue; } $propertyPointer = TokenHelper::findNext($phpcsFile, [T_VARIABLE, T_FUNCTION, T_CONST, T_ENUM_CASE], $memberPointer + 1); if ( $propertyPointer === null || $tokens[$propertyPointer]['code'] !== T_VARIABLE || !PropertyHelper::isProperty($phpcsFile, $propertyPointer) ) { continue; } $memberPointer = $propertyPointer; } if (ScopeHelper::isInSameScope($phpcsFile, $memberPointer, $previousMemberPointer)) { break; } } while (true); return $memberPointer; } private function getMemberStartPointer(File $phpcsFile, int $memberPointer): int { $tokens = $phpcsFile->getTokens(); $memberFirstCodePointer = $this->getMemberFirstCodePointer($phpcsFile, $memberPointer); do { $pointerBefore = TokenHelper::findPreviousNonWhitespace($phpcsFile, $memberFirstCodePointer - 1); if ($tokens[$pointerBefore]['code'] === T_ATTRIBUTE_END) { $memberFirstCodePointer = $tokens[$pointerBefore]['attribute_opener']; continue; } if ( in_array($tokens[$pointerBefore]['code'], Tokens::$commentTokens, true) && $tokens[$pointerBefore]['line'] + 1 === $tokens[$memberFirstCodePointer]['line'] ) { $pointerBeforeComment = TokenHelper::findPreviousEffective($phpcsFile, $pointerBefore - 1); if ($tokens[$pointerBeforeComment]['line'] !== $tokens[$pointerBefore]['line']) { $memberFirstCodePointer = array_key_exists('comment_opener', $tokens[$pointerBefore]) ? $tokens[$pointerBefore]['comment_opener'] : CommentHelper::getMultilineCommentStartPointer($phpcsFile, $pointerBefore); continue; } } break; } while (true); return $memberFirstCodePointer; } private function getMemberFirstCodePointer(File $phpcsFile, int $memberPointer): int { $tokens = $phpcsFile->getTokens(); if ($tokens[$memberPointer]['code'] === T_USE) { return $memberPointer; } $firstCodePointer = $memberPointer; $previousFirstCodePointer = $memberPointer; do { /** @var int $firstCodePointer */ $firstCodePointer = TokenHelper::findPrevious( $phpcsFile, [T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_ABSTRACT, T_FINAL, T_SEMICOLON, T_CLOSE_CURLY_BRACKET], $firstCodePointer - 1 ); if (in_array($tokens[$firstCodePointer]['code'], [T_SEMICOLON, T_CLOSE_CURLY_BRACKET], true)) { break; } $previousFirstCodePointer = $firstCodePointer; } while (true); return $previousFirstCodePointer; } private function getMemberEndPointer(File $phpcsFile, int $memberPointer): int { $tokens = $phpcsFile->getTokens(); if ($tokens[$memberPointer]['code'] === T_USE) { $pointer = TokenHelper::findNextLocal($phpcsFile, [T_SEMICOLON, T_OPEN_CURLY_BRACKET], $memberPointer + 1); return $tokens[$pointer]['code'] === T_OPEN_CURLY_BRACKET ? $tokens[$pointer]['bracket_closer'] : $pointer; } if ($tokens[$memberPointer]['code'] === T_FUNCTION && !FunctionHelper::isAbstract($phpcsFile, $memberPointer)) { return $tokens[$memberPointer]['scope_closer']; } return TokenHelper::findNext($phpcsFile, T_SEMICOLON, $memberPointer + 1); } }