Skip to content

Commit d3ac40a

Browse files
authored
Merge pull request #1667 from WordPress-Coding-Standards/feature/improve-global-function-call-detection
Improve global function call detection
2 parents 3ba3005 + 08f4a7f commit d3ac40a

File tree

6 files changed

+95
-53
lines changed

6 files changed

+95
-53
lines changed

WordPress/AbstractFunctionRestrictionsSniff.php

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,15 @@ public function process_token( $stackPtr ) {
213213
public function is_targetted_token( $stackPtr ) {
214214

215215
// Exclude function definitions, class methods, and namespaced calls.
216-
if ( \T_STRING === $this->tokens[ $stackPtr ]['code'] && isset( $this->tokens[ ( $stackPtr - 1 ) ] ) ) {
216+
if ( \T_STRING === $this->tokens[ $stackPtr ]['code'] ) {
217+
if ( $this->is_class_object_call( $stackPtr ) === true ) {
218+
return false;
219+
}
220+
221+
if ( $this->is_token_namespaced( $stackPtr ) === true ) {
222+
return false;
223+
}
224+
217225
$prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true );
218226

219227
if ( false !== $prev ) {
@@ -222,21 +230,11 @@ public function is_targetted_token( $stackPtr ) {
222230
\T_FUNCTION => \T_FUNCTION,
223231
\T_CLASS => \T_CLASS,
224232
\T_AS => \T_AS, // Use declaration alias.
225-
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
226-
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
227233
);
228234

229235
if ( isset( $skipped[ $this->tokens[ $prev ]['code'] ] ) ) {
230236
return false;
231237
}
232-
233-
// Skip namespaced functions, ie: \foo\bar() not \bar().
234-
if ( \T_NS_SEPARATOR === $this->tokens[ $prev ]['code'] ) {
235-
$pprev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $prev - 1 ), null, true );
236-
if ( false !== $pprev && \T_STRING === $this->tokens[ $pprev ]['code'] ) {
237-
return false;
238-
}
239-
}
240238
}
241239

242240
return true;

WordPress/Sniff.php

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,23 +1461,12 @@ protected function is_in_isset_or_empty( $stackPtr ) {
14611461
}
14621462

14631463
if ( \T_STRING === $previous_code && 'array_key_exists' === $this->tokens[ $previous_non_empty ]['content'] ) {
1464-
$before_function = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $previous_non_empty - 1 ), null, true, null, true );
1465-
1466-
if ( false !== $before_function ) {
1467-
if ( \T_OBJECT_OPERATOR === $this->tokens[ $before_function ]['code']
1468-
|| \T_DOUBLE_COLON === $this->tokens[ $before_function ]['code']
1469-
) {
1470-
// Method call.
1471-
return false;
1472-
}
1464+
if ( $this->is_class_object_call( $previous_non_empty ) === true ) {
1465+
return false;
1466+
}
14731467

1474-
if ( \T_NS_SEPARATOR === $this->tokens[ $before_function ]['code'] ) {
1475-
$before_before = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $before_function - 1 ), null, true, null, true );
1476-
if ( false !== $before_before && \T_STRING === $this->tokens[ $before_before ]['code'] ) {
1477-
// Namespaced function call.
1478-
return false;
1479-
}
1480-
}
1468+
if ( $this->is_token_namespaced( $previous_non_empty ) === true ) {
1469+
return false;
14811470
}
14821471

14831472
$second_param = $this->get_function_call_parameter( $previous_non_empty, 2 );
@@ -1489,6 +1478,71 @@ protected function is_in_isset_or_empty( $stackPtr ) {
14891478
return false;
14901479
}
14911480

1481+
/**
1482+
* Check if a particular token is a (static or non-static) call to a class method or property.
1483+
*
1484+
* @internal Note: this may still mistake a namespaced function imported via a `use` statement for
1485+
* a global function!
1486+
*
1487+
* @since 2.1.0
1488+
*
1489+
* @param int $stackPtr The index of the token in the stack.
1490+
*
1491+
* @return bool
1492+
*/
1493+
protected function is_class_object_call( $stackPtr ) {
1494+
$before = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true, null, true );
1495+
1496+
if ( false === $before ) {
1497+
return false;
1498+
}
1499+
1500+
if ( \T_OBJECT_OPERATOR !== $this->tokens[ $before ]['code']
1501+
&& \T_DOUBLE_COLON !== $this->tokens[ $before ]['code']
1502+
) {
1503+
return false;
1504+
}
1505+
1506+
return true;
1507+
}
1508+
1509+
/**
1510+
* Check if a particular token is prefixed with a namespace.
1511+
*
1512+
* @internal This will give a false positive if the file is not namespaced and the token is prefixed
1513+
* with `namespace\`.
1514+
*
1515+
* @since 2.1.0
1516+
*
1517+
* @param int $stackPtr The index of the token in the stack.
1518+
*
1519+
* @return bool
1520+
*/
1521+
protected function is_token_namespaced( $stackPtr ) {
1522+
$prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true, null, true );
1523+
1524+
if ( false === $prev ) {
1525+
return false;
1526+
}
1527+
1528+
if ( \T_NS_SEPARATOR !== $this->tokens[ $prev ]['code'] ) {
1529+
return false;
1530+
}
1531+
1532+
$before_prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $prev - 1 ), null, true, null, true );
1533+
if ( false === $before_prev ) {
1534+
return false;
1535+
}
1536+
1537+
if ( \T_STRING !== $this->tokens[ $before_prev ]['code']
1538+
&& \T_NAMESPACE !== $this->tokens[ $before_prev ]['code']
1539+
) {
1540+
return false;
1541+
}
1542+
1543+
return true;
1544+
}
1545+
14921546
/**
14931547
* Check if something is only being sanitized.
14941548
*
@@ -1853,22 +1907,14 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl
18531907
continue 2;
18541908
}
18551909

1856-
$previous_non_empty = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $i - 1 ), null, true, null, true );
1857-
if ( false !== $previous_non_empty ) {
1858-
if ( \T_OBJECT_OPERATOR === $this->tokens[ $previous_non_empty ]['code']
1859-
|| \T_DOUBLE_COLON === $this->tokens[ $previous_non_empty ]['code']
1860-
) {
1861-
// Method call.
1862-
continue 2;
1863-
}
1910+
if ( $this->is_class_object_call( $i ) === true ) {
1911+
// Method call.
1912+
continue 2;
1913+
}
18641914

1865-
if ( \T_NS_SEPARATOR === $this->tokens[ $previous_non_empty ]['code'] ) {
1866-
$pprev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $previous_non_empty - 1 ), null, true, null, true );
1867-
if ( false !== $pprev && \T_STRING === $this->tokens[ $pprev ]['code'] ) {
1868-
// Namespaced function call.
1869-
continue 2;
1870-
}
1871-
}
1915+
if ( $this->is_token_namespaced( $i ) === true ) {
1916+
// Namespaced function call.
1917+
continue 2;
18721918
}
18731919

18741920
$params = $this->get_function_call_parameters( $i );
@@ -2680,10 +2726,7 @@ public function is_use_of_global_constant( $stackPtr ) {
26802726
return false;
26812727
}
26822728

2683-
if ( false !== $prev
2684-
&& \T_NS_SEPARATOR === $this->tokens[ $prev ]['code']
2685-
&& \T_STRING === $this->tokens[ ( $prev - 1 ) ]['code']
2686-
) {
2729+
if ( $this->is_token_namespaced( $stackPtr ) === true ) {
26872730
// Namespaced constant of the same name.
26882731
return false;
26892732
}

WordPress/Sniffs/WP/DiscouragedConstantsSniff.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,7 @@ public function process_arbitrary_tstring( $stackPtr ) {
124124
return;
125125
}
126126

127-
if ( false !== $prev
128-
&& \T_NS_SEPARATOR === $this->tokens[ $prev ]['code']
129-
&& \T_STRING === $this->tokens[ ( $prev - 1 ) ]['code']
130-
) {
127+
if ( $this->is_token_namespaced( $stackPtr ) === true ) {
131128
// Namespaced constant of the same name.
132129
return;
133130
}

WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,7 @@ protected function process_global_statement( $stackPtr, $in_function_scope ) {
310310
}
311311

312312
// Don't throw false positives for static class properties.
313-
$previous = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $ptr - 1 ), null, true, null, true );
314-
if ( false !== $previous && \T_DOUBLE_COLON === $this->tokens[ $previous ]['code'] ) {
313+
if ( $this->is_class_object_call( $ptr ) === true ) {
315314
continue;
316315
}
317316

WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.inc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,8 @@ phpinfo(); // Ok - within excluded group.
3232
// phpcs:set WordPress.PHP.DevelopmentFunctions exclude[]
3333
trigger_error(); // Error.
3434
phpinfo(); // Error.
35+
36+
Wrapper_Class::var_dump(); // OK, not the native PHP function.
37+
$wrapper ->var_dump(); // OK, not the native PHP function.
38+
namespace\var_dump(); // OK as long as the file is namespaced.
39+
MyNamespace\var_dump(); // OK, namespaced function.

WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ define( 'My\STYLESHEETPATH', 'something' );
4242
if ( defined( 'STYLESHEETPATH' ) ) { // Ok.
4343
// Do something unrelated.
4444
}
45-
45+
echo namespace\STYLESHEETPATH; // "Magic" namespace operator.
4646

4747
/*
4848
* These are all bad.

0 commit comments

Comments
 (0)