Completed
Push — master ( 14c836...906b18 )
by Björn
06:09 queued 03:52
created

UCVFSortingSniff   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 185
rs 10
c 0
b 0
f 0
wmc 14
lcom 1
cbo 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BestIt\Sniffs\Formatting;
6
7
use BestIt\CodeSniffer\Helper\PropertyHelper;
8
use BestIt\CodeSniffer\Helper\TokenHelper;
9
use BestIt\CodeSniffer\Helper\UseStatementHelper;
10
use BestIt\Sniffs\AbstractSniff;
11
use BestIt\Sniffs\ClassRegistrationTrait;
12
use function array_combine;
13
use function array_filter;
14
use function array_keys;
15
use function array_map;
16
use function array_search;
17
use function uasort;
18
use const T_CONST;
19
use const T_FUNCTION;
20
use const T_USE;
21
use const T_VARIABLE;
22
23
/**
24
 * Checks the sorting of the contents of a class (T_USE > T_CONST > T_VARIABLE > T_FUNCTION).
25
 *
26
 * @author blange <[email protected]>
27
 * @package BestIt\Sniffs\Formatting
28
 */
29
class UCVFSortingSniff extends AbstractSniff
30
{
31
    use ClassRegistrationTrait;
32
33
    /**
34
     * You MUST sort the contents of your classes, traits, interface, etc. in the following order: T_USE, T_CONST, T_VARIABLE, T_FUNCTION.
35
     *
36
     * @var string
37
     */
38
    public const CODE_WRONG_POSITION = 'WrongPosition';
39
40
    /**
41
     * The error message of a structure is at the wrong position.
42
     *
43
     * @var string
44
     */
45
    private const MESSAGE_WRONG_POSITION = 'Your php structure is at a wrong position. ' .
46
        'The sorting order is: T_USE, T_CONST, T_VARIABLE, T_FUNCTION. We expect a %s (%s).';
47
48
    /**
49
     * The tokens which are checked in the correct sorting order.
50
     *
51
     * @var array
52
     */
53
    private array $sortedTokens = [
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_ARRAY, expecting T_FUNCTION or T_CONST
Loading history...
54
        T_USE,
55
        T_CONST,
56
        T_VARIABLE,
57
        T_FUNCTION
58
    ];
59
60
    /**
61
     * Loads the positons for the tokens of $this->>sortedTokens.
62
     *
63
     * @return int[]
64
     */
65
    private function loadSubTokenPositions(): array
66
    {
67
        return TokenHelper::findNextAll(
68
            $this->file,
69
            $this->sortedTokens,
70
            $this->stackPos,
71
            $this->token['scope_closer']
72
        );
73
    }
74
75
    /**
76
     * Loads all sub tokens.
77
     *
78
     * @return array
79
     */
80
    private function loadSubTokens(): array
81
    {
82
        $subTokens = $this->loadTokensForPositions($this->loadSubTokenPositions());
83
84
        return $this->removeUnwantedTokens($subTokens);
85
    }
86
87
    /**
88
     * Loads the tokens for the positions.
89
     *
90
     * @param array $subTokenPoss
91
     *
92
     * @return array
93
     */
94
    private function loadTokensForPositions(array $subTokenPoss): array
95
    {
96
        $subTokens = array_map(function (int $position): array {
97
            return $this->tokens[$position];
98
        }, $subTokenPoss);
99
100
        return array_combine($subTokenPoss, $subTokens);
101
    }
102
103
    /**
104
     * Processes the token.
105
     *
106
     * @return void
107
     */
108
    protected function processToken(): void
109
    {
110
        $subTokens = $this->loadSubTokens();
111
        $sortedTokens = $this->sortTokens($subTokens);
112
113
        $this->validateSorting($subTokens, $sortedTokens);
114
    }
115
116
    /**
117
     * Removes inline vars and uses for anon-functions.
118
     *
119
     * @param array $subTokens
120
     *
121
     * @return array
122
     */
123
    private function removeUnwantedTokens(array $subTokens): array
124
    {
125
        return array_filter($subTokens, function (array $subToken): bool {
126
            switch ($subToken['code']) {
127
                case T_VARIABLE:
128
                    $return = (new PropertyHelper($this->file))->isProperty($subToken['pointer']);
129
                    break;
130
131
                case T_USE:
132
                    $return = UseStatementHelper::isTraitUse($this->file, $subToken['pointer']);
133
                    break;
134
135
                default:
136
                    $return = true;
137
            }
138
139
            return $return;
140
        });
141
    }
142
143
    /**
144
     * This sniff needs the pointer marker so add it to the tokens.
145
     *
146
     * @return void
147
     */
148
    protected function setUp(): void
149
    {
150
        parent::setUp();
151
152
        $this->addPointerToTokens();
153
    }
154
155
    /**
156
     * Sorts the tokens as required by $this->sortingTokens.
157
     *
158
     * @param array $subTokens
159
     *
160
     * @return array
161
     */
162
    private function sortTokens(array $subTokens): array
163
    {
164
        uasort($subTokens, function (array $leftToken, array $rightToken): int {
165
            // Don't change the structure by default.
166
            $return = $leftToken['line'] <=> $rightToken['line'];
167
168
            // Sort by type
169
            if ($leftToken['code'] != $rightToken['code']) {
170
                $leftIndex = array_search($leftToken['code'], $this->sortedTokens);
171
                $rightIndex = array_search($rightToken['code'], $this->sortedTokens);
172
173
                $return = $leftIndex <=> $rightIndex;
174
            }
175
176
            return $return;
177
        });
178
179
        return $subTokens;
180
    }
181
182
    /**
183
     * Validates the sorting of the tokens and registers an error if wrong sorted.
184
     *
185
     * @param array $originalTokens
186
     * @param array $sortedTokens
187
     *
188
     * @return void
189
     */
190
    private function validateSorting(array $originalTokens, array $sortedTokens): void
191
    {
192
        $sortedPositions = array_keys($sortedTokens);
193
        $sortedIndex = 0;
194
195
        foreach ($originalTokens as $originalPosition => $originalToken) {
196
            $sortedPosition = $sortedPositions[$sortedIndex++];
197
            $sortedToken = $sortedTokens[$sortedPosition];
198
199
            // We don't need an error if the "type block" is the same, so check the code additionally.
200
            if (($sortedPosition !== $originalPosition) && ($originalToken['code'] !== $sortedToken['code'])) {
201
                $this->file->addError(
202
                    self::MESSAGE_WRONG_POSITION,
203
                    $originalPosition,
204
                    static::CODE_WRONG_POSITION,
205
                    [
206
                        $sortedToken['type'],
207
                        $sortedToken['content']
208
                    ]
209
                );
210
            }
211
        }
212
    }
213
}
214