Skip to content

Commit ce82266

Browse files
committed
Functions/RestrictedFunctions::is_targetted_token(): improve the method
The `RestrictedFunctions::is_targetted_token()` method was basically a duplicate of the upstream/parent method with some additional code to handle one specific situation (method calls on a specific variable). However, the parent method has been updated significantly since the code was copied and this sniff wasn't benefitting from that. This commit rewrites the `RestrictedFunctions::is_targetted_token()` method to defer to the parent method in all cases, except for the one specific situation we want to account for. Additionally, it stabilizes and improves the token walking done for that specific situation, as well as takes the PHP 8.0+ nullsafe object operator into account. Includes a number of tests, some to cover the improvements inherited from the parent method, some to cover the improved code in the overloaded method. Includes documenting how PHP 8.1+ first class callables are handled by the sniff.
1 parent 1753c9f commit ce82266

File tree

3 files changed

+37
-38
lines changed

3 files changed

+37
-38
lines changed

WordPressVIPMinimum/Sniffs/Functions/RestrictedFunctionsSniff.php

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -303,47 +303,27 @@ public function getGroups() {
303303
* @return bool
304304
*/
305305
public function is_targetted_token( $stackPtr ) {
306-
// Exclude function definitions, class methods, and namespaced calls.
307-
if ( $this->tokens[ $stackPtr ]['code'] === \T_STRING && isset( $this->tokens[ $stackPtr - 1 ] ) ) {
308-
// Check if this is really a function.
309-
$next = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true );
310-
if ( $next !== false && $this->tokens[ $next ]['code'] !== T_OPEN_PARENTHESIS ) {
311-
return false;
312-
}
306+
if ( empty( $this->groups[ $this->tokens[ $stackPtr ]['content'] ]['object_var'] ) ) {
307+
return parent::is_targetted_token( $stackPtr );
308+
}
313309

314-
$prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true );
315-
if ( $prev !== false ) {
310+
// Start difference to parent class method.
311+
// Check to see if the token is a method call on a specific object variable.
312+
$next = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true );
313+
if ( $next === false || $this->tokens[ $next ]['code'] !== T_OPEN_PARENTHESIS ) {
314+
return false;
315+
}
316316

317-
// Start difference to parent class method.
318-
// Check to see if function is a method on a specific object variable.
319-
if ( ! empty( $this->groups[ $this->tokens[ $stackPtr ]['content'] ]['object_var'] ) ) {
320-
$prevPrev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 2, null, true );
317+
$prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $stackPtr - 1, null, true );
318+
if ( $this->tokens[ $prev ]['code'] !== T_OBJECT_OPERATOR
319+
&& $this->tokens[ $prev ]['code'] !== T_NULLSAFE_OBJECT_OPERATOR
320+
) {
321+
return false;
322+
}
321323

322-
return $this->tokens[ $prev ]['code'] === \T_OBJECT_OPERATOR && isset( $this->groups[ $this->tokens[ $stackPtr ]['content'] ]['object_var'][ $this->tokens[ $prevPrev ]['content'] ] );
323-
} // End difference to parent class method.
324+
$prevPrev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $prev - 1, null, true );
324325

325-
// Skip sniffing if calling a same-named method, or on function definitions.
326-
$skipped = [
327-
\T_FUNCTION => \T_FUNCTION,
328-
\T_CLASS => \T_CLASS,
329-
\T_AS => \T_AS, // Use declaration alias.
330-
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
331-
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
332-
\T_NEW => \T_NEW,
333-
];
334-
if ( isset( $skipped[ $this->tokens[ $prev ]['code'] ] ) ) {
335-
return false;
336-
}
337-
// Skip namespaced functions, ie: `\foo\bar()` not `\bar()`.
338-
if ( $this->tokens[ $prev ]['code'] === \T_NS_SEPARATOR ) {
339-
$pprev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $prev - 1, null, true );
340-
if ( $pprev !== false && $this->tokens[ $pprev ]['code'] === \T_STRING ) {
341-
return false;
342-
}
343-
}
344-
}
345-
return true;
346-
}
347-
return false;
326+
return $this->tokens[ $prevPrev ]['code'] === T_VARIABLE
327+
&& isset( $this->groups[ $this->tokens[ $stackPtr ]['content'] ]['object_var'][ $this->tokens[ $prevPrev ]['content'] ] );
348328
}
349329
}

WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.inc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,19 @@ $popular = custom_stats_get_csv( 'postviews', [ 'days' => 2, 'limit' => 20 ]
230230

231231
$foo = new Link; // OK, class, not function.
232232
$foo = new Mail(); // OK, class, not function.
233+
234+
class ReturnByRef {
235+
function &chmod($a) {} // OK, method declaration, not function call.
236+
}
237+
238+
// Class instantiations in PHP 8.0+ attributes are not function calls.
239+
class AttributesShouldBeIgnored {
240+
#[WP_Is_Mobile()] // OK.
241+
public function foo() {}
242+
}
243+
244+
$wp_rewrite->flush_rules; // OK, property access, not function call.
245+
$wp_rewrite /*comment*/ -> /*comment*/ flush_rules(); // Error.
246+
$wp_rewrite?->flush_rules(); // Error.
247+
248+
array_walk($roles, add_role(...)); // Error. PHP 8.1 first class callable.

WordPressVIPMinimum/Tests/Functions/RestrictedFunctionsUnitTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ public function getErrorList() {
9797
199 => 1,
9898
200 => 1,
9999
228 => 1,
100+
245 => 1,
101+
246 => 1,
102+
248 => 1,
100103
];
101104
}
102105

0 commit comments

Comments
 (0)