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

ClassSortingSniff::visibilitySort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
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
    CONST CODE_WRONG_SORTING_FOUND = 'WrongSortingFound';
29
30
    /**
31
     * Error message.
32
     */
33
    CONST ERROR_WRONG_CLASS_SORTING = 'Wrong sorting';
34
35
    private $orderTypes = ['T_CONST', 'T_VARIABLE', 'T_FUNCTION-CONSTRUCT', 'T_FUNCTION-DESTRUCT', 'T_FUNCTION'];
36
37
    private $orderVisibility = ['T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC'];
38
39
    /**
40
     * Throw an error when a wrong sorting is detected.
41
     *
42
     * @param $file
43
     * @param $position
44
     *
45
     * @return mixed
46
     */
47
    private static function throwError($file, $position)
48
    {
49
        return $file->addError(
50
            self::ERROR_WRONG_CLASS_SORTING,
51
            $position,
52
            self::CODE_WRONG_SORTING_FOUND
53
        );
54
    }
55
56
    /**
57
     * Register tokens.
58
     *
59
     * @return array
60
     */
61
    public function register(): array
62
    {
63
        return [
64
            T_CLASS
65
        ];
66
    }
67
68
    /**
69
     * Sort the detected Tokens by the schema defined in $orderTypes and $orderVisibility.
70
     *
71
     * @param $a
72
     * @param $b
73
     *
74
     * @return int
75
     */
76
    private function sort($a, $b): int
77
    {
78
        $result = $this->typeSort($a, $b);
79
        if ($result === 0) {
80
            $result = $this->visibilitySort($a, $b);
81
            if ($result === 0) {
82
                $result = strcasecmp($a['name'], $b['name']);
83
            }
84
        }
85
        return $result;
86
    }
87
88
    /**
89
     * Sort all type tokens.
90
     * At the return value the usort() function can see if the entry has to be moved up or down.
91
     *
92
     * @param $a
93
     * @param $b
94
     *
95
     * @return int
96
     */
97
    private function typeSort($a, $b): int
98
    {
99
        return array_search($a['type'], $this->orderTypes, true) - array_search($b['type'], $this->orderTypes, true);
100
    }
101
102
    /**
103
     * Sort all visibility 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 visibilitySort($a, $b): int
112
    {
113
        return array_search($a['visibility'], $this->orderVisibility, true) - array_search($b['visibility'], $this->orderVisibility, true);
114
    }
115
116
    /**
117
     * This method checks the whole class and write all CONST-, visibility- and function-tokens in an array.
118
     * This array will be sorted with the sort() method. After that the original array and the sorted array will be compared.
119
     * If there are any differences an error will be thrown.
120
     *
121
     * @param File $file
122
     * @param int $position
123
     *
124
     * @return int|void
125
     */
126
    public function process(File $file, $position)
127
    {
128
        $tokens = $file->getTokens();
129
130
        $endPosition = $file->findEndOfStatement($position);
131
132
        $classElements = [];
133
        $functionStart = -1;
134
        $functionEnd = -1;
135
136
        $lastVisibility = null;
137
        $lastVisibilityEnd = -1;
138
139
        for ($i = $position; $i < $endPosition; $i++){
140
            if ($tokens[$i]['type'] === 'T_CONST') {
141
                $classElements[] = [
142
                    'type' => 'T_CONST',
143
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
144
                    'name' => $tokens[$file->findNext(T_STRING, $i)]['content']
145
                ];
146
            } elseif($tokens[$i]['type'] === 'T_FUNCTION'){
147
                $type = 'T_FUNCTION';
148
                $name = $tokens[$file->findNext(T_STRING, $i)]['content'];
149
                if (strcasecmp($name, '__destruct') === 0){
150
                    $type .= '-DESTRUCT';
151
                } elseif (strcasecmp($name, '__construct') === 0){
152
                    $type .= '-CONSTRUCT';
153
                }
154
155
                $classElements[] = [
156
                    'type' => $type,
157
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
158
                    'name' => $name
159
                ];
160
                $functionStart = $i;
161
                $functionEnd = $file->findEndOfStatement($i);
162
            } elseif ($tokens[$i]['type'] === 'T_VARIABLE' && ($i < $functionStart || $i > $functionEnd)){
163
                $classElements[] = [
164
                    'type' => 'T_VARIABLE',
165
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
166
                    'name' => $tokens[$i]['content']
167
                ];
168
            } elseif (in_array($tokens[$i]['type'], ['T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC'])){
169
                $lastVisibility = $tokens[$i]['type'];
170
                $lastVisibilityEnd = $file->findEndOfStatement($i);
171
            }
172
        }
173
174
        $originalClassElements = $classElements;
175
        usort($classElements, [$this, 'sort']);
176
177
        if ($classElements !== $originalClassElements){
178
            self::throwError($file, $position);
179
        }
180
    }
181
}
182