Completed
Pull Request — master (#37)
by
unknown
07:28
created

ClassSortingSniff::throwError()   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
 * Class ClassSorting.
10
 * @package BestIt\Sniffs\Formatting
11
 * @author Mika Bertels <[email protected]>
12
 */
13
final class ClassSortingSniff implements Sniff
14
{
15
    /**
16
     * Error code.
17
     */
18
    CONST CODE_WRONG_SORTING_FOUND = 'WrongSortingFound';
19
20
    /**
21
     * Error message.
22
     */
23
    CONST ERROR_WRONG_CLASS_SORTING = 'Wrong sorting';
24
25
    private $orderTypes = ['T_CONST', 'T_VARIABLE', 'T_FUNCTION-CONSTRUCT', 'T_FUNCTION-DESTRUCT', 'T_FUNCTION'];
26
27
    private $orderVisibility = ['T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC'];
28
29
    /**
30
     * @param $file
31
     * @param $position
32
     *
33
     * @return mixed
34
     */
35
    private static function throwError($file, $position)
36
    {
37
        return $file->addError(
38
            self::ERROR_WRONG_CLASS_SORTING,
39
            $position,
40
            self::CODE_WRONG_SORTING_FOUND
41
        );
42
    }
43
44
    /**
45
     * @return array
46
     */
47
    public function register(): array
48
    {
49
        return [
50
            T_CLASS
51
        ];
52
    }
53
54
    /**
55
     * Sort.
56
     *
57
     * @param $a
58
     * @param $b
59
     *
60
     * @return int
61
     */
62
    private function sort($a, $b): int
63
    {
64
        $result = $this->typeSort($a, $b);
65
        if ($result === 0) {
66
            $result = $this->visibilitySort($a, $b);
67
            if ($result === 0) {
68
                $result = strcasecmp($a['name'], $b['name']);
69
            }
70
        }
71
        return $result;
72
    }
73
74
    /**
75
     * Type sort.
76
     *
77
     * @param $a
78
     * @param $b
79
     *
80
     * @return int
81
     */
82
    private function typeSort($a, $b): int
83
    {
84
        return array_search($a['type'], $this->orderTypes, true) - array_search($b['type'], $this->orderTypes, true);
85
    }
86
87
    /**
88
     * Visibility sort.
89
     *
90
     * @param $a
91
     * @param $b
92
     *
93
     * @return int
94
     */
95
    private function visibilitySort($a, $b): int
96
    {
97
        return array_search($a['visibility'], $this->orderVisibility, true) - array_search($b['visibility'], $this->orderVisibility, true);
98
    }
99
100
    /**
101
     * @param File $file
102
     * @param int $position
103
     *
104
     * @return int|void
105
     */
106
    public function process(File $file, $position)
107
    {
108
        $tokens = $file->getTokens();
109
110
        $endPosition = $file->findEndOfStatement($position);
111
112
        $classElements = [];
113
        $functionStart = -1;
114
        $functionEnd = -1;
115
116
        $lastVisibility = null;
117
        $lastVisibilityEnd = -1;
118
119
        for ($i = $position; $i < $endPosition; $i++){
120
            if ($tokens[$i]['type'] === 'T_CONST') {
121
                $classElements[] = [
122
                    'type' => 'T_CONST',
123
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
124
                    'name' => $tokens[$file->findNext(T_STRING, $i)]['content']
125
                ];
126
            } elseif($tokens[$i]['type'] === 'T_FUNCTION'){
127
                $type = 'T_FUNCTION';
128
                $name = $tokens[$file->findNext(T_STRING, $i)]['content'];
129
                if (strcasecmp($name, '__destruct') === 0){
130
                    $type .= '-DESTRUCT';
131
                } elseif (strcasecmp($name, '__construct') === 0){
132
                    $type .= '-CONSTRUCT';
133
                }
134
135
                $classElements[] = [
136
                    'type' => $type,
137
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
138
                    'name' => $name
139
                ];
140
                $functionStart = $i;
141
                $functionEnd = $file->findEndOfStatement($i);
142
            } elseif ($tokens[$i]['type'] === 'T_VARIABLE' && ($i < $functionStart || $i > $functionEnd)){
143
                $classElements[] = [
144
                    'type' => 'T_VARIABLE',
145
                    'visibility' => $i <= $lastVisibilityEnd ? $lastVisibility : 'T_PUBLIC',
146
                    'name' => $tokens[$i]['content']
147
                ];
148
            } elseif (in_array($tokens[$i]['type'], ['T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC'])){
149
                $lastVisibility = $tokens[$i]['type'];
150
                $lastVisibilityEnd = $file->findEndOfStatement($i);
151
            }
152
        }
153
154
        $originalClassElements = $classElements;
155
        usort($classElements, [$this, 'sort']);
156
157
        if ($classElements !== $originalClassElements){
158
            self::throwError($file, $position);
159
        }
160
    }
161
}
162