Skip to content
Merged
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
37 changes: 33 additions & 4 deletions PhpCollective/Sniffs/Arrays/ArrayBracketSpacingSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ public function process(File $phpcsFile, $stackPtr): void
// Any extra blank lines should be removed
if ($closerLine - $lastContentLine > 1) {
$error = 'Extra blank lines found before array closing bracket';

// Check if the last content is a comment - these are problematic for auto-fixing
if ($tokens[$lastContentPtr]['code'] === T_COMMENT) {
// Report as non-fixable error when last element is a comment
$phpcsFile->addError($error, $closerPtr, 'ExtraBlankLineBeforeCloser');

return;
}

$fix = $phpcsFile->addFixableError($error, $closerPtr, 'ExtraBlankLineBeforeCloser');

if ($fix === true) {
Expand All @@ -90,13 +99,33 @@ public function process(File $phpcsFile, $stackPtr): void
}
}

// Remove all tokens between last content and closer
// Find whitespace tokens between last content and closer
$whitespaceTokens = [];
for ($i = $lastContentPtr + 1; $i < $closerPtr; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
if ($tokens[$i]['code'] === T_WHITESPACE) {
$whitespaceTokens[] = $i;
}
}

// Add a single newline with proper indentation after the last content
$phpcsFile->fixer->addContent($lastContentPtr, "\n" . $indent);
// Find the position to insert the newline
// If there's already whitespace, replace it; otherwise add new
$nextToken = $lastContentPtr + 1;

if ($nextToken < $closerPtr && $tokens[$nextToken]['code'] === T_WHITESPACE) {
// There's whitespace right after the last content
// Replace it with a single newline and indent
$phpcsFile->fixer->replaceToken($nextToken, "\n" . $indent);

// Remove any additional whitespace tokens
for ($i = $nextToken + 1; $i < $closerPtr; $i++) {
if ($tokens[$i]['code'] === T_WHITESPACE) {
$phpcsFile->fixer->replaceToken($i, '');
}
}
} else {
// No whitespace immediately after, need to add it
$phpcsFile->fixer->addContent($lastContentPtr, "\n" . $indent);
}

$phpcsFile->fixer->endChangeset();
}
Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/Classes/MethodDeclarationSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop
$tokens = $phpcsFile->getTokens();

$methodName = $phpcsFile->getDeclarationName($stackPtr);
if ($methodName === null) {
if (!$methodName) {
// Ignore closures.
return;
}
Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/Classes/MethodTypeHintSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function process(File $phpcsFile, $stackPtr): void
$j = $startIndex;
$extractedUseStatement = '';
while (true) {
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING, T_RETURN_TYPE], $tokens[$j])) {
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING], $tokens[$j])) {
break;
}

Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/Commenting/AttributesSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function process(File $phpCsFile, $stackPointer): void

$tokens = $phpCsFile->getTokens();

if ($tokens[$nextIndex]['code'] === T_NS_SEPARATOR) {
if ($tokens[$nextIndex]['code'] === T_NS_SEPARATOR || $tokens[$nextIndex]['code'] === T_NAME_FULLY_QUALIFIED) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use SlevomatCodingStandard\Helpers\AnnotationHelper;
use SlevomatCodingStandard\Helpers\AnnotationTypeHelper;
use SlevomatCodingStandard\Helpers\DocCommentHelper;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\FunctionHelper;
use SlevomatCodingStandard\Helpers\NamespaceHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
Expand Down Expand Up @@ -143,23 +142,46 @@ public function process(File $phpcsFile, $pointer): void
new IdentifierTypeNode($genericIdentifier),
[$this->fixArrayNode($arrayTypeNode->type)],
);
$this->fixAnnotation($phpcsFile, $annotation, $genericTypeNode);
$this->fixAnnotation($phpcsFile, $annotation, AnnotationTypeHelper::print($genericTypeNode));

continue;
}

$phpcsFile->fixer->beginChangeset();
FixerHelper::change(
$this->applyFixWithoutTabConversion(
$phpcsFile,
$parsedDocComment->getOpenPointer(),
$parsedDocComment->getClosePointer(),
$fixedDocComment,
);
$phpcsFile->fixer->endChangeset();
}
}
}

/**
* Apply a fix without converting spaces to tabs
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $startPointer
* @param int $endPointer
* @param string $content
*
* @return void
*/
protected function applyFixWithoutTabConversion(File $phpcsFile, int $startPointer, int $endPointer, string $content): void
{
$phpcsFile->fixer->beginChangeset();

// Remove all tokens between start and end
for ($i = $startPointer; $i <= $endPointer; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}

// Add the new content without tab conversion
$phpcsFile->fixer->replaceToken($startPointer, $content);

$phpcsFile->fixer->endChangeset();
}

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param \SlevomatCodingStandard\Helpers\Annotation $annotation
Expand All @@ -169,7 +191,7 @@ public function process(File $phpcsFile, $pointer): void
*/
protected function fixAnnotation(File $phpcsFile, Annotation $annotation, string $fixedAnnotation): void
{
/** @var \PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|mixed $value */
/** @var \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode $value */
$value = $annotation->getNode()->value;
$parameterName = $value->parameterName ?? '';
$variableName = $value->variableName ?? '';
Expand Down Expand Up @@ -220,9 +242,9 @@ public function getArrayTypeNodes(Node $node): array
/**
* @param \PHPStan\PhpDocParser\Ast\Node $node
*
* @return \PHPStan\PhpDocParser\Ast\Node|list<\PHPStan\PhpDocParser\Ast\Node>|\PHPStan\PhpDocParser\Ast\NodeTraverser|int|null
* @return int|null
*/
public function enterNode(Node $node): Node|array|\PHPStan\PhpDocParser\Ast\NodeTraverser|int|null
public function enterNode(Node $node): int|null
{
if ($node instanceof ArrayTypeNode) {
$this->nodes[] = $node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ protected function assertTypeHint(File $phpcsFile, int $stackPtr, int $docBlockR
$typehint = '?' . $typehint;
}

if ($documentedReturnType !== 'void' && $typeHintIndex !== 'void') {
if ($documentedReturnType !== 'void' && $typehint !== 'void') {
return;
}
if ($documentedReturnType === $typehint) {
Expand Down
25 changes: 22 additions & 3 deletions PhpCollective/Sniffs/Commenting/DocBlockThrowsSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ public function process(File $phpCsFile, $stackPointer): void
return;
}

if ($phpCsFile->getDeclarationName($stackPointer) === null) {
try {
$name = $phpCsFile->getDeclarationName($stackPointer);
} catch (Exception $e) {
return;
}
if (!$name) {
return;
}

Expand Down Expand Up @@ -139,6 +144,12 @@ protected function extractExceptions(File $phpCsFile, int $stackPointer): array
continue;
}

// Handle the case where there's no namespace separator, string token, or fully qualified name token
// (e.g., for parenthesis after 'new')
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING, T_NAME_FULLY_QUALIFIED], $tokens[$contentIndex])) {
continue;
}

$exceptions[] = $this->extractException($phpCsFile, $contentIndex);

continue;
Expand Down Expand Up @@ -223,9 +234,17 @@ protected function extractException(File $phpCsFile, int $contentIndex): array
$fullClass = '';

$position = $contentIndex;
while ($this->isGivenKind([T_NS_SEPARATOR, T_STRING], $tokens[$position])) {
$fullClass .= $tokens[$position]['content'];

// Handle T_NAME_FULLY_QUALIFIED token (e.g., \BadMethodCallException)
// or separate tokens (T_NS_SEPARATOR and T_STRING)
if ($this->isGivenKind([T_NAME_FULLY_QUALIFIED], $tokens[$position])) {
$fullClass = $tokens[$position]['content'];
++$position;
} else {
while ($this->isGivenKind([T_NS_SEPARATOR, T_STRING], $tokens[$position])) {
$fullClass .= $tokens[$position]['content'];
++$position;
}
}

$class = $fullClass = ltrim($fullClass, '\\');
Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/Commenting/TypeHintSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public function process(File $phpcsFile, $stackPtr): void
continue;
}

/** @phpstan-var \PHPStan\PhpDocParser\Ast\Type\GenericTypeNode|\PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode $valueNode */
/** @phpstan-var \PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode $valueNode */
if ($valueNode->type instanceof UnionTypeNode) {
$types = $valueNode->type->types;
} elseif ($valueNode->type instanceof ArrayTypeNode) {
Expand Down
56 changes: 27 additions & 29 deletions PhpCollective/Sniffs/Formatting/ArrayDeclarationSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,39 +291,37 @@ public function processMultiLineArray(File $phpcsFile, int $stackPtr, int $array
continue;
}

if ($tokens[$nextToken]['code'] === T_DOUBLE_ARROW) {
$currentEntry['arrow'] = $nextToken;
$keyUsed = true;
$currentEntry['arrow'] = $nextToken;
$keyUsed = true;

// Find the start of index that uses this double arrow.
$indexEnd = (int)$phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), $arrayStart, true);
$indexStart = $phpcsFile->findStartOfStatement($indexEnd);
// Find the start of index that uses this double arrow.
$indexEnd = (int)$phpcsFile->findPrevious(T_WHITESPACE, ($nextToken - 1), $arrayStart, true);
$indexStart = $phpcsFile->findStartOfStatement($indexEnd);

if ($indexStart === $indexEnd) {
$currentEntry['index'] = $indexEnd;
$currentEntry['index_content'] = $tokens[$indexEnd]['content'];
} else {
$currentEntry['index'] = $indexStart;
$currentEntry['index_content'] = $phpcsFile->getTokensAsString($indexStart, ($indexEnd - $indexStart + 1));
}

$indexLength = strlen($currentEntry['index_content']);
if ($maxLength < $indexLength) {
$maxLength = $indexLength;
}

// Find the value of this index.
$nextContent = $phpcsFile->findNext(
Tokens::$emptyTokens,
($nextToken + 1),
$arrayEnd,
true,
);
if ($indexStart === $indexEnd) {
$currentEntry['index'] = $indexEnd;
$currentEntry['index_content'] = $tokens[$indexEnd]['content'];
} else {
$currentEntry['index'] = $indexStart;
$currentEntry['index_content'] = $phpcsFile->getTokensAsString($indexStart, ($indexEnd - $indexStart + 1));
}

$currentEntry['value'] = $nextContent;
$indices[] = $currentEntry;
$lastToken = $nextToken;
$indexLength = strlen($currentEntry['index_content']);
if ($maxLength < $indexLength) {
$maxLength = $indexLength;
}

// Find the value of this index.
$nextContent = $phpcsFile->findNext(
Tokens::$emptyTokens,
($nextToken + 1),
$arrayEnd,
true,
);

$currentEntry['value'] = $nextContent;
$indices[] = $currentEntry;
$lastToken = $nextToken;
}

$numValues = count($indices);
Expand Down
79 changes: 51 additions & 28 deletions PhpCollective/Sniffs/Namespaces/UseStatementSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ protected function checkUseForReturnTypeHint(File $phpcsFile, int $stackPtr): vo
$extractedUseStatement = '';
$lastSeparatorIndex = null;
while (true) {
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING, T_RETURN_TYPE], $tokens[$j])) {
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING], $tokens[$j])) {
break;
}

Expand Down Expand Up @@ -639,32 +639,45 @@ protected function checkPropertyForInstanceOf(File $phpcsFile, int $stackPtr): v
return;
}

$lastIndex = null;
$j = $startIndex;
$extractedUseStatement = '';
$lastSeparatorIndex = null;
while (true) {
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING, T_RETURN_TYPE], $tokens[$j])) {
break;
// Handle T_NAME_FULLY_QUALIFIED token (PHP CodeSniffer v4)
$className = '';
if ($tokens[$startIndex]['code'] === T_NAME_FULLY_QUALIFIED) {
$extractedUseStatement = ltrim($tokens[$startIndex]['content'], '\\');
if (strpos($extractedUseStatement, '\\') === false) {
return; // Not a namespaced class
}
$lastSeparatorPos = strrpos($extractedUseStatement, '\\');
$className = substr($extractedUseStatement, $lastSeparatorPos + 1);
$lastIndex = $startIndex;
$lastSeparatorIndex = null; // No separate separator token in v4
} else {
// Handle separate tokens (T_NS_SEPARATOR and T_STRING)
$lastIndex = null;
$j = $startIndex;
$extractedUseStatement = '';
$lastSeparatorIndex = null;
while (true) {
if (!$this->isGivenKind([T_NS_SEPARATOR, T_STRING], $tokens[$j])) {
break;
}

$lastIndex = $j;
$extractedUseStatement .= $tokens[$j]['content'];
if ($this->isGivenKind([T_NS_SEPARATOR], $tokens[$j])) {
$lastSeparatorIndex = $j;
$lastIndex = $j;
$extractedUseStatement .= $tokens[$j]['content'];
if ($this->isGivenKind([T_NS_SEPARATOR], $tokens[$j])) {
$lastSeparatorIndex = $j;
}
++$j;
}
++$j;
}

if ($lastIndex === null || $lastSeparatorIndex === null) {
return;
}

$extractedUseStatement = ltrim($extractedUseStatement, '\\');
if ($lastIndex === null || $lastSeparatorIndex === null) {
return;
}

$className = '';
for ($k = $lastSeparatorIndex + 1; $k <= $lastIndex; ++$k) {
$className .= $tokens[$k]['content'];
$extractedUseStatement = ltrim($extractedUseStatement, '\\');
$className = '';
for ($k = $lastSeparatorIndex + 1; $k <= $lastIndex; ++$k) {
$className .= $tokens[$k]['content'];
}
}

$error = 'Use statement ' . $extractedUseStatement . ' for class ' . $className . ' should be in use block.';
Expand All @@ -677,13 +690,23 @@ protected function checkPropertyForInstanceOf(File $phpcsFile, int $stackPtr): v

$addedUseStatement = $this->addUseStatement($phpcsFile, $className, $extractedUseStatement);

for ($k = $lastSeparatorIndex; $k > $startIndex; --$k) {
$phpcsFile->fixer->replaceToken($k, '');
}
$phpcsFile->fixer->replaceToken($startIndex, '');
if ($lastSeparatorIndex !== null) {
// Legacy: remove individual tokens
for ($k = $lastSeparatorIndex; $k > $startIndex; --$k) {
$phpcsFile->fixer->replaceToken($k, '');
}
$phpcsFile->fixer->replaceToken($startIndex, '');

if ($addedUseStatement['alias'] !== null) {
$phpcsFile->fixer->replaceToken($lastIndex, $addedUseStatement['alias']);
if ($addedUseStatement['alias'] !== null) {
$phpcsFile->fixer->replaceToken($lastIndex, $addedUseStatement['alias']);
}
} else {
// PHP CodeSniffer v4: replace single T_NAME_FULLY_QUALIFIED token
if ($addedUseStatement['alias'] !== null) {
$phpcsFile->fixer->replaceToken($startIndex, $addedUseStatement['alias']);
} else {
$phpcsFile->fixer->replaceToken($startIndex, $className);
}
}

$phpcsFile->fixer->endChangeset();
Expand Down
Loading