Completed
Branch master (269e9e)
by Björn
06:04
created

TraitUseDeclarationSniff::processToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BestIt\Sniffs\Formatting;
6
7
use BestIt\CodeSniffer\Helper\ClassHelper;
8
use BestIt\CodeSniffer\Helper\TokenHelper;
9
use BestIt\Sniffs\AbstractSniff;
10
use BestIt\Sniffs\ClassRegistrationTrait;
11
use const T_COMMA;
12
use const T_OPEN_CURLY_BRACKET;
13
use const T_SEMICOLON;
14
use const T_WHITESPACE;
15
16
/**
17
 * Sniffs if the uses in a class are correct.
18
 *
19
 * @author blange <[email protected]>
20
 * @package BestIt\Sniffs\Formatting
21
 */
22
class TraitUseDeclarationSniff extends AbstractSniff
23
{
24
    use ClassRegistrationTrait;
25
26
    /**
27
     * You MUST provide only one "use" per Line for importing traits etc. in classes.
28
     */
29
    public const CODE_MULTIPLE_TRAITS_PER_DECLARATION = 'MultipleTraitsPerDeclaration';
30
31
    /**
32
     * Readable error message.
33
     */
34
    private const MESSAGE_MULTIPLE_TRAITS_PER_DECLARATION = 'Multiple traits per use statement are forbidden.';
35
36
    /**
37
     * The use declarations positions of this "class".
38
     *
39
     * @var array
40
     */
41
    private $uses;
42
43
    /**
44
     * Returns false if there are no uses.
45
     *
46
     * @return bool
47
     */
48
    protected function areRequirementsMet(): bool
49
    {
50
        return (bool) $this->uses = ClassHelper::getTraitUsePointers($this->getFile(), $this->getStackPos());
51
    }
52
53
    /**
54
     * Checks the declaration of the given use position and registers and error if needed.
55
     *
56
     * @param int $usePos
57
     *
58
     * @return void
59
     */
60
    private function checkDeclaration(int $usePos): void
61
    {
62
        $file = $this->getFile()->getBaseFile();
63
64
        if (TokenHelper::findNextLocal($file, T_COMMA, $usePos + 1)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \BestIt\CodeSniffer\Help... \T_COMMA, $usePos + 1) of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
65
            $endPos = TokenHelper::findNext($file, [T_OPEN_CURLY_BRACKET, T_SEMICOLON], $usePos + 1);
66
            $tokens = $file->getTokens();
67
68
            if ($tokens[$endPos]['code'] === T_OPEN_CURLY_BRACKET) {
69
                $file->addError(
70
                    self::MESSAGE_MULTIPLE_TRAITS_PER_DECLARATION,
71
                    $usePos,
72
                    static::CODE_MULTIPLE_TRAITS_PER_DECLARATION
73
                );
74
            } else {
75
                $fix = $file->addFixableError(
76
                    self::MESSAGE_MULTIPLE_TRAITS_PER_DECLARATION,
77
                    $usePos,
78
                    static::CODE_MULTIPLE_TRAITS_PER_DECLARATION
79
                );
80
81
                if ($fix) {
82
                    $this->fixeUse($endPos, $usePos);
83
                }
84
            }
85
        }
86
    }
87
88
    /**
89
     * Fixes the given use position.
90
     *
91
     * @param int $endPos The end of the checked position.
92
     * @param int $usePos
93
     *
94
     * @return void
95
     */
96
    protected function fixeUse(int $endPos, int $usePos): void
97
    {
98
        $indentation = $this->getIndentationForFix($usePos);
99
        $file = $this->getFile()->getBaseFile();
100
        $fixer = $file->fixer;
101
102
        $fixer->beginChangeset();
103
104
        $commaPointers = TokenHelper::findNextAll($file, T_COMMA, $usePos + 1, $endPos);
105
        foreach ($commaPointers as $commaPos) {
106
            $pointerAfterComma = TokenHelper::findNextEffective($file, $commaPos + 1);
107
            $fixer->replaceToken($commaPos, ';' . $file->eolChar . $indentation . 'use ');
108
            for ($i = $commaPos + 1; $i < $pointerAfterComma; $i++) {
109
                $fixer->replaceToken($i, '');
110
            }
111
        }
112
113
        $fixer->endChangeset();
114
    }
115
116
    /**
117
     * Returns the needed indentation whitespace for fhe fixing of the uses.
118
     *
119
     * @param int $usePos
120
     *
121
     * @return string
122
     */
123
    private function getIndentationForFix(int $usePos): string
124
    {
125
        $file = $this->getFile()->getBaseFile();
126
        $indentation = '';
127
        $currentPointer = $usePos - 1;
128
        $tokens = $file->getTokens();
129
130
        while ($tokens[$currentPointer]['code'] === T_WHITESPACE &&
131
            $tokens[$currentPointer]['content'] !== $file->eolChar) {
132
            $indentation .= $tokens[$currentPointer]['content'];
133
            $currentPointer--;
134
        }
135
136
        return $indentation;
137
    }
138
139
    /**
140
     * Processes the token.
141
     *
142
     * @return void
143
     */
144
    protected function processToken(): void
145
    {
146
        foreach ($this->uses as $use) {
147
            $this->checkDeclaration($use);
148
        }
149
    }
150
}
151