Skip to content

Commit

Permalink
Added TraitUseDeclarationSniff
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Jul 20, 2018
1 parent 4cb6ebe commit d7347f9
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 29 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -196,6 +196,10 @@ Reports use of superfluous prefix or suffix "Interface" for interfaces.

Reports use of superfluous suffix "Exception" for exceptions.

#### SlevomatCodingStandard.Classes.TraitUseDeclaration 🔧

Prohibits multiple traits separated by commas in one `use` statement.

#### SlevomatCodingStandard.Classes.TraitUseSpacing 🔧

Enforces configurable number of lines before first `use`, after last `use` and between two `use` statements.
Expand Down
28 changes: 28 additions & 0 deletions SlevomatCodingStandard/Helpers/ClassHelper.php
Expand Up @@ -6,6 +6,7 @@
use PHP_CodeSniffer\Files\File;
use const T_ANON_CLASS;
use const T_STRING;
use const T_USE;
use function array_map;
use function iterator_to_array;
use function sprintf;
Expand Down Expand Up @@ -67,4 +68,31 @@ private static function getAllClassPointers(File $codeSnifferFile, int &$previou
} while (true);
}

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $classPointer
* @return int[]
*/
public static function getTraitUsePointers(File $phpcsFile, int $classPointer): array
{
$useStatements = [];

$tokens = $phpcsFile->getTokens();

$scopeLevel = $tokens[$classPointer]['level'] + 1;
for ($i = $tokens[$classPointer]['scope_opener'] + 1; $i < $tokens[$classPointer]['scope_closer']; $i++) {
if ($tokens[$i]['code'] !== T_USE) {
continue;
}

if ($tokens[$i]['level'] !== $scopeLevel) {
continue;
}

$useStatements[] = $i;
}

return $useStatements;
}

}
91 changes: 91 additions & 0 deletions SlevomatCodingStandard/Sniffs/Classes/TraitUseDeclarationSniff.php
@@ -0,0 +1,91 @@
<?php declare(strict_types = 1);

namespace SlevomatCodingStandard\Sniffs\Classes;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\ClassHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_ANON_CLASS;
use const T_CLASS;
use const T_COMMA;
use const T_OPEN_CURLY_BRACKET;
use const T_SEMICOLON;
use const T_TRAIT;
use const T_WHITESPACE;

class TraitUseDeclarationSniff implements Sniff
{

public const CODE_MULTIPLE_TRAITS_PER_DECLARATION = 'MultipleTraitsPerDeclaration';

/**
* @return mixed[]
*/
public function register(): array
{
return [
T_CLASS,
T_ANON_CLASS,
T_TRAIT,
];
}

/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $classPointer
*/
public function process(File $phpcsFile, $classPointer): void
{
$usePointers = ClassHelper::getTraitUsePointers($phpcsFile, $classPointer);

foreach ($usePointers as $usePointer) {
$this->checkDeclaration($phpcsFile, $usePointer);
}
}


private function checkDeclaration(File $phpcsFile, int $usePointer): void
{
$commaPointer = TokenHelper::findNextLocal($phpcsFile, T_COMMA, $usePointer + 1);
if ($commaPointer === null) {
return;
}

$endPointer = TokenHelper::findNext($phpcsFile, [T_OPEN_CURLY_BRACKET, T_SEMICOLON], $usePointer + 1);

$tokens = $phpcsFile->getTokens();
if ($tokens[$endPointer]['code'] === T_OPEN_CURLY_BRACKET) {
$phpcsFile->addError('Multiple traits per use statement are forbidden.', $usePointer, self::CODE_MULTIPLE_TRAITS_PER_DECLARATION);
return;
}

$fix = $phpcsFile->addFixableError('Multiple traits per use statement are forbidden.', $usePointer, self::CODE_MULTIPLE_TRAITS_PER_DECLARATION);

if (!$fix) {
return;
}

$indentation = '';
$currentPointer = $usePointer - 1;
while ($tokens[$currentPointer]['code'] === T_WHITESPACE && $tokens[$currentPointer]['content'] !== $phpcsFile->eolChar) {
$indentation .= $tokens[$currentPointer]['content'];
$currentPointer--;
}

$phpcsFile->fixer->beginChangeset();

$commaPointers = TokenHelper::findNextAll($phpcsFile, T_COMMA, $usePointer + 1, $endPointer);
foreach ($commaPointers as $commaPointer) {
$pointerAfterComma = TokenHelper::findNextEffective($phpcsFile, $commaPointer + 1);
$phpcsFile->fixer->replaceToken($commaPointer, ';' . $phpcsFile->eolChar . $indentation . 'use ');
for ($i = $commaPointer + 1; $i < $pointerAfterComma; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
}

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

}
31 changes: 2 additions & 29 deletions SlevomatCodingStandard/Sniffs/Classes/TraitUseSpacingSniff.php
Expand Up @@ -4,13 +4,13 @@

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\ClassHelper;
use SlevomatCodingStandard\Helpers\SniffSettingsHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use const T_ANON_CLASS;
use const T_CLASS;
use const T_SEMICOLON;
use const T_TRAIT;
use const T_USE;
use const T_WHITESPACE;
use function count;
use function sprintf;
Expand Down Expand Up @@ -51,7 +51,7 @@ public function register(): array
*/
public function process(File $phpcsFile, $classPointer): void
{
$usePointers = $this->getUsePointers($phpcsFile, $classPointer);
$usePointers = ClassHelper::getTraitUsePointers($phpcsFile, $classPointer);

if (count($usePointers) === 0) {
return;
Expand All @@ -62,33 +62,6 @@ public function process(File $phpcsFile, $classPointer): void
$this->checkLinesBetweenUses($phpcsFile, $usePointers);
}

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $classPointer
* @return int[]
*/
private function getUsePointers(File $phpcsFile, int $classPointer): array
{
$useStatements = [];

$tokens = $phpcsFile->getTokens();

$scopeLevel = $tokens[$classPointer]['level'] + 1;
for ($i = $tokens[$classPointer]['scope_opener'] + 1; $i < $tokens[$classPointer]['scope_closer']; $i++) {
if ($tokens[$i]['code'] !== T_USE) {
continue;
}

if ($tokens[$i]['level'] !== $scopeLevel) {
continue;
}

$useStatements[] = $i;
}

return $useStatements;
}

private function checkLinesBeforeFirstUse(File $phpcsFile, int $firstUsePointer): void
{
/** @var int $pointerBeforeFirstUse */
Expand Down
29 changes: 29 additions & 0 deletions tests/Sniffs/Classes/TraitUseDeclarationSniffTest.php
@@ -0,0 +1,29 @@
<?php declare(strict_types = 1);

namespace SlevomatCodingStandard\Sniffs\Classes;

use SlevomatCodingStandard\Sniffs\TestCase;

class TraitUseDeclarationSniffTest extends TestCase
{

public function testNoErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/traitUseDeclarationNoErrors.php');
self::assertNoSniffErrorInFile($report);
}

public function testErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/traitUseDeclarationErrors.php');

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

self::assertSniffError($report, 6, TraitUseDeclarationSniff::CODE_MULTIPLE_TRAITS_PER_DECLARATION);
self::assertSniffError($report, 8, TraitUseDeclarationSniff::CODE_MULTIPLE_TRAITS_PER_DECLARATION);
self::assertSniffError($report, 10, TraitUseDeclarationSniff::CODE_MULTIPLE_TRAITS_PER_DECLARATION);

self::assertAllFixedInFile($report);
}

}
17 changes: 17 additions & 0 deletions tests/Sniffs/Classes/data/traitUseDeclarationErrors.fixed.php
@@ -0,0 +1,17 @@
<?php

class Whatever
{

use A;
use B;

use C;
use D;
use E;

use F, G {

This comment has been minimized.

Copy link
@carusogabriel

carusogabriel Jul 25, 2018

Contributor

Isn't this one supposed to be fixed as

class Whatever
{
    use F;
    use G {
        G::doSomething as doAnything;
    }
}

?

This comment has been minimized.

Copy link
@kukulich

kukulich Jul 25, 2018

Author Contributor

I’ve never used coflicts resolutions so I was lazy to make them fixable too.

G::doSomething as doAnything;
}

}
14 changes: 14 additions & 0 deletions tests/Sniffs/Classes/data/traitUseDeclarationErrors.php
@@ -0,0 +1,14 @@
<?php

class Whatever
{

use A, B;

use C, D, E;

use F, G {
G::doSomething as doAnything;
}

}
15 changes: 15 additions & 0 deletions tests/Sniffs/Classes/data/traitUseDeclarationNoErrors.php
@@ -0,0 +1,15 @@
<?php

class Whatever
{

use A;

use B;


use C;

use D;

}

0 comments on commit d7347f9

Please sign in to comment.