|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Codor\Sniffs\Files; |
|
4
|
|
|
|
|
5
|
|
|
use PHP_CodeSniffer_Sniff; |
|
6
|
|
|
use PHP_CodeSniffer_File; |
|
7
|
|
|
|
|
8
|
|
|
class IndentationLevelSniff implements PHP_CodeSniffer_Sniff |
|
9
|
|
|
{ |
|
10
|
|
|
/** |
|
11
|
|
|
* Indentations cannot be >= to this number. |
|
12
|
|
|
* @var integer |
|
13
|
|
|
*/ |
|
14
|
|
|
public $indentationLimit = 2; |
|
15
|
|
|
|
|
16
|
|
|
/** |
|
17
|
|
|
* The highest indentation level found. |
|
18
|
|
|
* @var integer |
|
19
|
|
|
*/ |
|
20
|
|
|
protected $maxIndentFound = 0; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* This array contains the relative scope level per token. |
|
24
|
|
|
* It is increased inside try-catch blocks. |
|
25
|
|
|
* @var array |
|
26
|
|
|
*/ |
|
27
|
|
|
protected $relativeScopeLevels = []; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* Returns the token types that this sniff is interested in. |
|
31
|
|
|
* @return array |
|
32
|
|
|
*/ |
|
33
|
|
|
public function register() |
|
34
|
|
|
{ |
|
35
|
|
|
return [T_FUNCTION, T_CLOSURE, T_SWITCH]; |
|
36
|
|
|
} |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Processes the tokens that this sniff is interested in. |
|
40
|
|
|
* |
|
41
|
|
|
* @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. |
|
42
|
|
|
* @param integer $stackPtr The position in the stack where |
|
43
|
|
|
* the token was found. |
|
44
|
|
|
* @return void |
|
45
|
|
|
*/ |
|
46
|
|
|
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|
47
|
|
|
{ |
|
48
|
|
|
$tokens = $phpcsFile->getTokens(); |
|
49
|
|
|
$token = $tokens[$stackPtr]; |
|
50
|
|
|
$this->maxIndentFound = 0; |
|
51
|
|
|
|
|
52
|
|
|
// Ignore functions with no body |
|
53
|
|
|
if (isset($token['scope_opener']) === false) { |
|
54
|
|
|
return; |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
$this->inspectScope($token, $tokens); |
|
58
|
|
|
|
|
59
|
|
|
if ($this->maxIndentFound <= $this->indentationLimit) { |
|
60
|
|
|
return; |
|
61
|
|
|
} |
|
62
|
|
|
|
|
63
|
|
|
$phpcsFile->addError($this->getErrorMessage(), $stackPtr); |
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Inspect the tokens in the scope of the provided $token. |
|
68
|
|
|
* @param array $token Token Data. |
|
69
|
|
|
* @param array $tokens Tokens. |
|
70
|
|
|
* @return void |
|
71
|
|
|
*/ |
|
72
|
|
|
protected function inspectScope(array $token, array $tokens) |
|
73
|
|
|
{ |
|
74
|
|
|
$start = $token['scope_opener']; |
|
75
|
|
|
$length = $token['scope_closer'] - $start + 1; |
|
76
|
|
|
|
|
77
|
|
|
$scope = array_slice($tokens, $start, $length, true); |
|
78
|
|
|
$scope = $this->removeTokenScopes($scope, 'T_SWITCH'); |
|
79
|
|
|
|
|
80
|
|
|
$this->setRelativeScopeLevels($scope, $scope[$start]['level']); |
|
81
|
|
|
|
|
82
|
|
|
foreach ($scope as $i => $token) { |
|
83
|
|
|
$this->maxIndentFound = max($this->maxIndentFound, $token['level'] - $this->relativeScopeLevels[$i]); |
|
|
|
|
|
|
84
|
|
|
} |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
/** |
|
88
|
|
|
* Set the relative scope level per token. |
|
89
|
|
|
* In a try-catch block the relative scope is one higher. |
|
90
|
|
|
* @param array $scope The tokens in a scope. |
|
91
|
|
|
* @param integer $level The base level of the relative scopes. |
|
92
|
|
|
* @return void |
|
93
|
|
|
*/ |
|
94
|
|
|
protected function setRelativeScopeLevels(array $scope, $level) |
|
95
|
|
|
{ |
|
96
|
|
|
// first set the base level for all tokens |
|
97
|
|
|
foreach (array_keys($scope) as $i) { |
|
98
|
|
|
$this->relativeScopeLevels[$i] = $level; |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
// then increase the base level by one for all the tokens in a try-catch block |
|
102
|
|
|
foreach (array_keys($this->findNestedTokens($scope, 'T_TRY')) as $i) { |
|
103
|
|
|
$this->relativeScopeLevels[$i] += 1; |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
foreach (array_keys($this->findNestedTokens($scope, 'T_CATCH')) as $i) { |
|
107
|
|
|
$this->relativeScopeLevels[$i] += 1; |
|
108
|
|
|
} |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* Remove the bodies of given token type from the scope. |
|
113
|
|
|
* @param array $scope The tokens in a scope. |
|
114
|
|
|
* @param string $type The type of token to remove from the scope. |
|
115
|
|
|
* @return array $scope The tokens scope without the removed tokens. |
|
116
|
|
|
*/ |
|
117
|
|
|
protected function removeTokenScopes(array $scope, $type) |
|
118
|
|
|
{ |
|
119
|
|
|
return array_diff_key($scope, $this->findNestedTokens($scope, $type)); |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Find the tokens nested in the scope of given token type. |
|
124
|
|
|
* @param array $scope The tokens in a scope. |
|
125
|
|
|
* @param string $type The type of token to find in the scope. |
|
126
|
|
|
* @return array $scope The nested tokens. |
|
127
|
|
|
*/ |
|
128
|
|
|
protected function findNestedTokens(array $scope, $type) |
|
129
|
|
|
{ |
|
130
|
|
|
$typeTokens = array_filter($scope, function ($token) use ($type) { |
|
131
|
|
|
return $token['type'] == $type; |
|
132
|
|
|
}); |
|
133
|
|
|
|
|
134
|
|
|
$nestedTokens = []; |
|
135
|
|
|
foreach ($typeTokens as $token) { |
|
136
|
|
|
$range = array_flip(range($token['scope_opener'], $token['scope_closer'])); |
|
137
|
|
|
$nestedTokens += array_intersect_key($scope, $range); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
return $nestedTokens; |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
|
|
/** |
|
144
|
|
|
* Produce the error message. |
|
145
|
|
|
* @return string |
|
146
|
|
|
*/ |
|
147
|
|
|
protected function getErrorMessage() |
|
148
|
|
|
{ |
|
149
|
|
|
// Hack to fix the output numbers for the |
|
150
|
|
|
// indentation levels found and the |
|
151
|
|
|
// indentation limit. |
|
152
|
|
|
$indentationFound = $this->maxIndentFound - 1; |
|
153
|
|
|
$indentationLimit = $this->indentationLimit - 1; |
|
154
|
|
|
return "{$indentationFound} indentation levels found. " . |
|
155
|
|
|
"Maximum of {$indentationLimit} indentation levels allowed."; |
|
156
|
|
|
} |
|
157
|
|
|
} |
|
158
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.