Passed
Pull Request — master (#37)
by
unknown
02:35
created

ClassSortingSniff::throwFixableError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace BestIt\Sniffs\Formatting;
4
5
use PHP_CodeSniffer\Files\File;
6
use PHP_CodeSniffer\Sniffs\Sniff;
7
8
/**
9
 * Sniff for the sorting of a class.
10
 * The standard sorting is:
11
 *  1. Constants
12
 *  2. Properties
13
 *  3. Constructor
14
 *  4. Destructor
15
 *  5. Methods
16
 * All entries above are also sorted by visibility and alphabetical.
17
 *
18
 * This sniff checks if your class corresponds to this sorting.
19
 *
20
 * @package BestIt\Sniffs\Formatting
21
 * @author Mika Bertels <[email protected]>
22
 */
23
final class ClassSortingSniff implements Sniff
24
{
25
    /**
26
     * Error code.
27
     *
28
     * @var string
29
     */
30
    CONST CODE_WRONG_SORTING_FOUND = 'WrongSortingFound';
31
32
    /**
33
     * Error message.
34
     *
35
     * @var string
36
     */
37
    CONST ERROR_WRONG_CLASS_SORTING = 'Wrong sorting';
38
39
    /**
40
     * Order from types.
41
     *
42
     * @var array
43
     */
44
    public $orderTypes = ['T_CONST', 'T_VARIABLE', 'T_FUNCTION-CONSTRUCT', 'T_FUNCTION-DESTRUCT', 'T_FUNCTION'];
45
46
    /**
47
     * Order from visibilities.
48
     *
49
     * @var array
50
     */
51
    public $orderVisibility = ['T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC'];
52
53
    /**
54
     * Throw an error when a wrong sorting is detected.
55
     *
56
     * @param File $file
57
     * @param int $position
58
     *
59
     * @return boolean
60
     */
61
    private static function throwFixableError($file, $position)
62
    {
63
        return $file->addFixableError(
64
            self::ERROR_WRONG_CLASS_SORTING,
65
            $position,
66
            self::CODE_WRONG_SORTING_FOUND
67
        );
68
    }
69
70
    /**
71
     * Register tokens.
72
     *
73
     * @return array
74
     */
75
    public function register(): array
76
    {
77
        return [
78
            T_CLASS
79
        ];
80
    }
81
82
    /**
83
     * Sort the detected Tokens by the schema defined in $orderTypes and $orderVisibility.
84
     *
85
     * @param $a
86
     * @param $b
87
     *
88
     * @return int
89
     */
90
    private function sort($a, $b): int
91
    {
92
        $result = $this->typeSort($a, $b);
93
        if ($result === 0) {
94
            $result = $this->visibilitySort($a, $b);
95
            if ($result === 0) {
96
                $result = strcasecmp($a['name'], $b['name']);
97
            }
98
        }
99
        return $result;
100
    }
101
102
    /**
103
     * Sort all type tokens.
104
     * At the return value the usort() function can see if the entry has to be moved up or down.
105
     *
106
     * @param $a
107
     * @param $b
108
     *
109
     * @return int
110
     */
111
    private function typeSort($a, $b): int
112
    {
113
        return array_search($a['type'], $this->orderTypes, true) - array_search($b['type'], $this->orderTypes, true);
114
    }
115
116
    /**
117
     * Sort all visibility tokens.
118
     * At the return value the usort() function can see if the entry has to be moved up or down.
119
     *
120
     * @param $a
121
     * @param $b
122
     *
123
     * @return int
124
     */
125
    private function visibilitySort($a, $b): int
126
    {
127
        return array_search($a['visibility'], $this->orderVisibility, true) - array_search($b['visibility'], $this->orderVisibility, true);
128
    }
129
130
    /**
131
     * This method checks the whole class and write all CONST-, visibility- and function-tokens in an array.
132
     * This array will be sorted with the sort() method. After that the original array and the sorted array will be compared.
133
     * If there are any differences an error will be thrown.
134
     *
135
     * @param File $file
136
     * @param int $position
137
     *
138
     * @return void
139
     */
140
    public function process(File $file, $position)
141
    {
142
        $tokens = $file->getTokens();
143
144
        $endPosition = $file->findEndOfStatement($position);
145
146
        $classElements = [];
147
        $functionStart = -1;
148
        $functionEnd = -1;
149
150
        $lastVisibility = null;
151
        $lastVisibilityEnd = -1;
152
153
        for ($i = $position; $i < $endPosition; $i++){
154
            if ($tokens[$i]['type'] === 'T_CONST') {
155
                $classElements[] = [
156
                    'type' => 'T_CONST',
157
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
158
                    'name' => $tokens[$file->findNext(T_STRING, $i)]['content']
159
                ];
160
            } elseif($tokens[$i]['type'] === 'T_FUNCTION'){
161
                $type = 'T_FUNCTION';
162
                $name = $tokens[$file->findNext(T_STRING, $i)]['content'];
163
                if (strcasecmp($name, '__destruct') === 0){
164
                    $type .= '-DESTRUCT';
165
                } elseif (strcasecmp($name, '__construct') === 0){
166
                    $type .= '-CONSTRUCT';
167
                }
168
169
                $classElements[] = [
170
                    'type' => $type,
171
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
172
                    'name' => $name
173
                ];
174
                $functionStart = $i;
175
                $functionEnd = $file->findEndOfStatement($i);
176
            } elseif ($tokens[$i]['type'] === 'T_VARIABLE' && ($i < $functionStart || $i > $functionEnd)){
177
                $classElements[] = [
178
                    'type' => 'T_VARIABLE',
179
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
180
                    'name' => $tokens[$i]['content']
181
                ];
182
            } elseif (in_array($tokens[$i]['type'], ['T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC'])){
183
                $lastVisibility = $tokens[$i]['type'];
184
                $lastVisibilityEnd = $file->findEndOfStatement($i);
185
            }
186
        }
187
188
        $originalClassElements = $classElements;
189
        usort($classElements, [$this, 'sort']);
190
191
        if ($classElements !== $originalClassElements){
192
            self::throwFixableError($file, $position);
193
        }
194
    }
195
}
196