PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 5.8.4
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v5.8.4
7.2.2 7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 All 37 releases
mlsimport / vendor / phpcsstandards / phpcsextra / Universal / Sniffs / CodeAnalysis / StaticInFinalClassSniff.php

StaticInFinalClassSniff.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 5.8.4, at vendor/phpcsstandards/phpcsextra/Universal/Sniffs/CodeAnalysis/StaticInFinalClassSniff.php

217 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4 *
5 * @package PHPCSExtra
6 * @copyright 2020 PHPCSExtra Contributors
7 * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8 * @link https://github.com/PHPCSStandards/PHPCSExtra
9 */
10
11 namespace PHPCSExtra\Universal\Sniffs\CodeAnalysis;
12
13 use PHP_CodeSniffer\Files\File;
14 use PHP_CodeSniffer\Sniffs\Sniff;
15 use PHP_CodeSniffer\Util\Tokens;
16 use PHPCSUtils\Utils\Conditions;
17 use PHPCSUtils\Utils\FunctionDeclarations;
18 use PHPCSUtils\Utils\GetTokensAsString;
19 use PHPCSUtils\Utils\ObjectDeclarations;
20 use PHPCSUtils\Utils\Scopes;
21
22 /**
23 * Forbid the use of the `static` keyword for late static binding in OO constructs which are final.
24 *
25 * @since 1.0.0
26 */
27 final class StaticInFinalClassSniff implements Sniff
28 {
29
30 /**
31 * OO Scopes in which late static binding is useless.
32 *
33 * @var array<int|string>
34 */
35 private $validOOScopes = [
36 \T_CLASS, // Only if final.
37 \T_ANON_CLASS, // Final by nature.
38 \T_ENUM, // Final by design.
39 ];
40
41 /**
42 * Returns an array of tokens this test wants to listen for.
43 *
44 * @since 1.0.0
45 *
46 * @return array<int|string>
47 */
48 public function register()
49 {
50 return [
51 // These tokens are used to retrieve return types reliably.
52 \T_FUNCTION,
53 \T_FN,
54 // While this is our "real" target.
55 \T_STATIC,
56 // But we also need this as after "instanceof", `static` is tokenized as `T_STRING in PHPCS < 4.0.0.
57 // See: https://github.com/squizlabs/PHP_CodeSniffer/pull/3121
58 \T_STRING,
59 ];
60 }
61
62 /**
63 * Processes this test, when one of its tokens is encountered.
64 *
65 * @since 1.0.0
66 *
67 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
68 * @param int $stackPtr The position of the current token
69 * in the stack passed in $tokens.
70 *
71 * @return int|void Integer stack pointer to skip forward or void to continue
72 * normal file processing.
73 */
74 public function process(File $phpcsFile, $stackPtr)
75 {
76 $tokens = $phpcsFile->getTokens();
77
78 if ($tokens[$stackPtr]['code'] === \T_STRING
79 && \strtolower($tokens[$stackPtr]['content']) !== 'static'
80 ) {
81 return;
82 }
83
84 if ($tokens[$stackPtr]['code'] === \T_FUNCTION
85 || $tokens[$stackPtr]['code'] === \T_FN
86 ) {
87 /*
88 * Check return types for methods in final classes, anon classes and enums.
89 *
90 * Will return the scope opener of the function to prevent potential duplicate notifications.
91 */
92 $scopeOpener = $stackPtr;
93 if (isset($tokens[$stackPtr]['scope_opener']) === true) {
94 $scopeOpener = $tokens[$stackPtr]['scope_opener'];
95 }
96
97 if ($tokens[$stackPtr]['code'] === \T_FUNCTION) {
98 $ooPtr = Scopes::validDirectScope($phpcsFile, $stackPtr, $this->validOOScopes);
99 if ($ooPtr === false) {
100 // Method in a trait (not known where it is used), interface (never final) or not in an OO scope.
101 return $scopeOpener;
102 }
103 } else {
104 $ooPtr = Conditions::getLastCondition($phpcsFile, $stackPtr, $this->validOOScopes);
105 if ($ooPtr === false) {
106 // Arrow function outside of OO.
107 return $scopeOpener;
108 }
109 }
110
111 if ($tokens[$ooPtr]['code'] === \T_CLASS) {
112 $classProps = ObjectDeclarations::getClassProperties($phpcsFile, $ooPtr);
113 if ($classProps['is_final'] === false) {
114 // Method in a non-final class.
115 return $scopeOpener;
116 }
117 }
118
119 $functionProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr);
120 if ($functionProps['return_type'] === '') {
121 return $scopeOpener;
122 }
123
124 $staticPtr = $phpcsFile->findNext(
125 \T_STATIC,
126 $functionProps['return_type_token'],
127 ($functionProps['return_type_end_token'] + 1)
128 );
129
130 if ($staticPtr === false) {
131 return $scopeOpener;
132 }
133
134 // Found a return type containing the `static` type.
135 $this->handleError($phpcsFile, $staticPtr, 'ReturnType', '"static" return type');
136
137 return $scopeOpener;
138 }
139
140 /*
141 * Check other uses of static.
142 */
143 $functionPtr = Conditions::getLastCondition($phpcsFile, $stackPtr, [\T_FUNCTION, \T_CLOSURE]);
144 if ($functionPtr === false || $tokens[$functionPtr]['code'] === \T_CLOSURE) {
145 /*
146 * When `false`, this code is absolutely invalid, but not something to be addressed via this sniff.
147 * When a closure, we're not interested in it. The closure class is final, but closures
148 * can be bound to other classes. This needs further research and should maybe get its own sniff.
149 */
150 return;
151 }
152
153 $ooPtr = Scopes::validDirectScope($phpcsFile, $functionPtr, $this->validOOScopes);
154 if ($ooPtr === false) {
155 // Not in an OO context.
156 return;
157 }
158
159 if ($tokens[$ooPtr]['code'] === \T_CLASS) {
160 $classProps = ObjectDeclarations::getClassProperties($phpcsFile, $ooPtr);
161 if ($classProps['is_final'] === false) {
162 // Token in a non-final class.
163 return;
164 }
165 }
166
167 $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
168 if ($prevNonEmpty !== false) {
169 if ($tokens[$prevNonEmpty]['code'] === \T_INSTANCEOF) {
170 $prevPrevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prevNonEmpty - 1), null, true);
171 $extraMsg = GetTokensAsString::compact($phpcsFile, $prevPrevNonEmpty, $stackPtr, true);
172 $this->handleError($phpcsFile, $stackPtr, 'InstanceOf', '"' . $extraMsg . '"');
173 return;
174 }
175
176 if ($tokens[$prevNonEmpty]['code'] === \T_NEW) {
177 $this->handleError($phpcsFile, $stackPtr, 'NewInstance', '"new static"');
178 return;
179 }
180 }
181
182 $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
183 if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_DOUBLE_COLON) {
184 $nextNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true);
185 $extraMsg = GetTokensAsString::compact($phpcsFile, $stackPtr, $nextNextNonEmpty, true);
186 $this->handleError($phpcsFile, $stackPtr, 'ScopeResolution', '"' . $extraMsg . '"');
187 return;
188 }
189 }
190
191 /**
192 * Throw and potentially fix the error.
193 *
194 * @since 1.0.0
195 *
196 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
197 * @param int $stackPtr The position of erroneous `T_STATIC` token.
198 * @param string $errorCode The error code for the message.
199 * @param string $extraMsg Addition to the error message.
200 *
201 * @return void
202 */
203 private function handleError($phpcsFile, $stackPtr, $errorCode, $extraMsg)
204 {
205 $fix = $phpcsFile->addFixableError(
206 'Use "self" instead of "static" when using late static binding in a final OO construct. Found: %s',
207 $stackPtr,
208 $errorCode,
209 [$extraMsg]
210 );
211
212 if ($fix === true) {
213 $phpcsFile->fixer->replaceToken($stackPtr, 'self');
214 }
215 }
216 }
217