2626 *
2727 * PHP version ^7.0.0
2828 *
29- * @since TSF 1.0.0
29+ * @since 1.0.0
3030 */
3131class OpcodesSniff extends Sniff {
3232
3333 /**
3434 * @link <https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_compile.c#L3750-L3829>
35- * @var string[] $funcs
35+ * @var string[] $opFuncs
3636 */
37- protected $ funcs = [
37+ protected $ opFuncs = [
3838 'strlen ' ,
3939 'is_null ' ,
4040 'is_bool ' ,
@@ -71,9 +71,9 @@ class OpcodesSniff extends Sniff {
7171
7272 /**
7373 * @link <https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c#L411-L416>
74- * @var string[] $constfuncs
74+ * @var string[] $opConstFuncs
7575 */
76- protected $ constfuncs = [
76+ protected $ opConstFuncs = [
7777 'define ' ,
7878 'function_exists ' ,
7979 'is_callable ' ,
@@ -83,9 +83,35 @@ class OpcodesSniff extends Sniff {
8383 ];
8484
8585 /**
86- * @var string[] $checks
86+ * @var string[] $internalFuncs
87+ * @see register(), there it's populated.
8788 */
88- public $ checks = [];
89+ protected $ internalFuncs = [];
90+
91+ /**
92+ * @var string[] $noopInternal
93+ * @see register(), there it's populated.
94+ */
95+ protected $ noopInternal = [];
96+
97+ /**
98+ * @var string[] $allNoopChecks
99+ * @see register(), there it's populated.
100+ */
101+ protected $ allNoopChecks = [];
102+
103+ /**
104+ * @var string[] $opChecks Internal functions that should be checked for opcode improvements (e.g., 'array_keys').
105+ * This should not yield a benefit, though.
106+ * @since 1.1.0
107+ */
108+ public $ opChecks = [];
109+
110+ /**
111+ * @var string[] $noopChecks User functions that should not be checked for opcode improvements (i.e., your namespaced functions).
112+ * @since 1.1.0
113+ */
114+ public $ userNoopChecks = [];
89115
90116 /**
91117 * Returns an array of tokens this test wants to listen for.
@@ -96,12 +122,25 @@ class OpcodesSniff extends Sniff {
96122 */
97123 public function register () {
98124 // Handle case-insensitivity of function names.
99- $ this ->funcs = $ this ->arrayKeysToLowercase ( $ this ->funcs );
100- $ this ->constfuncs = $ this ->arrayKeysToLowercase ( $ this ->constfuncs );
101- $ this ->checks = $ this ->arrayKeysToLowercase ( $ this ->checks );
125+ $ this ->opFuncs = array_map ( 'strtolower ' , $ this ->opFuncs );
126+ $ this ->opConstFuncs = array_map ( 'strtolower ' , $ this ->opConstFuncs );
127+ $ this ->opChecks = array_map ( 'strtolower ' , $ this ->opChecks );
128+
129+ // Combine opChecks.
130+ $ this ->opChecks = array_unique ( array_merge ( $ this ->opChecks , $ this ->opFuncs , $ this ->opConstFuncs ) );
102131
103- // Combine checks.
104- $ this ->checks = array_merge ( $ this ->checks , $ this ->funcs , $ this ->constfuncs );
132+ $ this ->internalFuncs = array_map ( 'strtolower ' , get_defined_functions ()['internal ' ] );
133+
134+ $ this ->noopInternal = array_diff (
135+ $ this ->internalFuncs ,
136+ $ this ->opFuncs ,
137+ $ this ->opConstFuncs
138+ );
139+
140+ $ this ->userNoopChecks = array_map ( 'strtolower ' , $ this ->userNoopChecks );
141+
142+ // Combine noopChecks.
143+ $ this ->allNoopChecks = array_unique ( array_merge ( $ this ->noopInternal , $ this ->userNoopChecks ) );
105144
106145 $ targets = [
107146 \T_STRING ,
@@ -142,50 +181,63 @@ public function process( File $phpcsFile, $stackPtr ) {
142181 *
143182 * @param int $stackPtr The position of the current token in
144183 * the stack passed in $tokens.
145- *
146- * @return bool
147184 */
148185 protected function process_namespaces ( $ stackPtr ) {
149186
150187 $ function = $ this ->tokens [ $ stackPtr ]['content ' ];
151188 $ functionLc = strtolower ( $ function );
152189
153190 if ( '' !== $ this ->determineNamespace ( $ this ->phpcsFile , $ stackPtr ) ) {
154- if ( in_array ( $ functionLc , $ this ->checks , true ) && false === $ this ->is_token_globally_namespaced ( $ stackPtr ) ) {
155- $ this ->phpcsFile ->addWarning (
156- 'Function %s should have a leading namespace separator (`\`). ' ,
157- $ stackPtr ,
158- 'ShouldHaveNamespaceEscape ' ,
159- ["{$ function }() " ]
160- );
191+ if ( in_array ( $ functionLc , $ this ->opChecks , true ) ) {
192+ if ( false === $ this ->is_token_globally_namespaced ( $ stackPtr ) ) {
193+
194+ $ warning = $ this ->is_object_creation ( $ stackPtr )
195+ ? 'Class %s should have a leading namespace separator `\`. '
196+ : 'Function %s should have a leading namespace separator `\`. ' ;
197+
198+ $ this ->phpcsFile ->addWarning (
199+ $ warning ,
200+ $ stackPtr ,
201+ 'ShouldHaveNamespaceEscape ' ,
202+ [ "{$ function }() " ]
203+ );
204+ }
205+ } elseif ( ! in_array ( $ functionLc , $ this ->internalFuncs , true ) && ! in_array ( $ functionLc , $ this ->allNoopChecks , true ) ) {
206+ if ( false === $ this ->is_token_globally_namespaced ( $ stackPtr ) ) {
207+
208+ $ warning = $ this ->is_object_creation ( $ stackPtr )
209+ ? 'Class %s should have a leading namespace separator `\`. '
210+ : 'Function %s should have a leading namespace separator `\`. ' ;
211+
212+ $ this ->phpcsFile ->addWarning (
213+ $ warning ,
214+ $ stackPtr ,
215+ 'ShouldHaveNamespaceEscape ' ,
216+ [ "{$ function }() " ]
217+ );
218+ }
161219 }
162220 } else {
163221 // When there's no namespace, we're already in the correct scope for the opcode.
164222 // Warn dev that there's a useless NS escape.
165- if ( true === $ this ->is_token_globally_namespaced ( $ stackPtr ) ) {
166- $ this ->phpcsFile ->addWarning (
167- 'Function %s does not need a leading namespace separator. ' ,
168- $ stackPtr ,
169- 'UselessLeadingNamespaceEscape ' ,
170- ["{$ function }() " ]
171- );
223+ if ( ! in_array ( $ functionLc , $ this ->userNoopChecks , true ) ) {
224+ if ( true === $ this ->is_token_globally_namespaced ( $ stackPtr ) ) {
225+
226+ $ warning = $ this ->is_object_creation ( $ stackPtr )
227+ ? 'Class %s should have a leading namespace separator `\`. '
228+ : 'Function %s should have a leading namespace separator `\`. ' ;
229+
230+ $ this ->phpcsFile ->addWarning (
231+ $ warning ,
232+ $ stackPtr ,
233+ 'UselessLeadingNamespaceEscape ' ,
234+ [ "{$ function }() " ]
235+ );
236+ }
172237 }
173238 }
174239 }
175240
176- /**
177- * Is the class/function/constant name namespaced or global?
178- *
179- * @since 1.0.0
180- *
181- * @param string $FQName Fully Qualified name of a class, function etc.
182- * I.e. should always start with a `\`.
183- * @return bool True if namespaced, false if global.
184- */
185- public function hasLeadingNamespaceDelimiter ( $ name ) {
186- return 0 === strpos ( $ name , '\\' );
187- }
188-
189241 /**
190242 * Verify is the current token is a function call.
191243 *
@@ -208,10 +260,6 @@ protected function is_targetted_token( $stackPtr ) {
208260 return false ;
209261 }
210262
211- // if ( $this->is_token_globally_namespaced( $stackPtr ) === true ) {
212- // return false;
213- // }
214-
215263 $ prev = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ stackPtr - 1 ), null , true );
216264 if ( false !== $ prev ) {
217265 // Skip sniffing on function, class definitions or for function aliases in use statements.
@@ -262,6 +310,7 @@ protected function is_targetted_token( $stackPtr ) {
262310 * @return bool
263311 */
264312 protected function is_class_object_call ( $ stackPtr ) {
313+
265314 $ before = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ stackPtr - 1 ), null , true , null , true );
266315
267316 if ( false === $ before ) {
@@ -277,6 +326,31 @@ protected function is_class_object_call( $stackPtr ) {
277326 return true ;
278327 }
279328
329+ /**
330+ * Check if a particular token is a (static or non-static) call to an object.
331+ *
332+ * @since 1.1.0
333+ *
334+ * @param int $stackPtr The position of the current token in
335+ * the stack passed in $tokens.
336+ *
337+ * @return bool
338+ */
339+ protected function is_object_creation ( $ stackPtr ) {
340+
341+ $ before = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ stackPtr - 2 ), null , true , null , true );
342+
343+ if ( false === $ before ) {
344+ return false ;
345+ }
346+
347+ if ( \T_NEW !== $ this ->tokens [ $ before ]['code ' ] ) {
348+ return false ;
349+ }
350+
351+ return true ;
352+ }
353+
280354 /**
281355 * Check if a particular token is prefixed with a namespace.
282356 *
@@ -292,6 +366,7 @@ protected function is_class_object_call( $stackPtr ) {
292366 * @return bool
293367 */
294368 protected function is_token_globally_namespaced ( $ stackPtr ) {
369+
295370 $ prev = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ stackPtr - 1 ), null , true , null , true );
296371
297372 if ( false === $ prev ) {
@@ -309,9 +384,6 @@ protected function is_token_globally_namespaced( $stackPtr ) {
309384
310385 $ function = $ this ->tokens [ $ stackPtr ]['content ' ];
311386 $ functionLc = strtolower ( $ function );
312- if ( '_init_tsf ' === $ functionLc ) {
313- throw new \Exception ( serialize ( $ this ->tokens [ $ before_prev ] ) );
314- }
315387
316388 // This is an actual non-global namespace lookup.
317389 if ( \T_STRING === $ this ->tokens [ $ before_prev ]['code ' ]
0 commit comments