Skip to content

Commit 1438761

Browse files
authored
Merge pull request #862 from Automattic/feature/functions-dynamic-calls-minor-improvements
2 parents 31f6da9 + 14e1be4 commit 1438761

File tree

4 files changed

+71
-39
lines changed

4 files changed

+71
-39
lines changed

WordPressVIPMinimum/Sniffs/Functions/DynamicCallsSniff.php

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,29 @@ class DynamicCallsSniff extends Sniff {
4646
];
4747

4848
/**
49-
* Array of variable assignments encountered, along with their values.
49+
* Potential end tokens for which the end pointer has to be set back by one.
5050
*
51-
* Populated at run-time.
51+
* {@internal The PHPCS `findEndOfStatement()` method is not completely consistent
52+
* in how it returns the statement end. This is just a simple way to bypass
53+
* the inconsistency for our purposes.}
5254
*
53-
* @var array<string, string> The key is the name of the variable, the value, its assigned value.
55+
* @var array<int|string, true>
5456
*/
55-
private $variables_arr = [];
57+
private $inclusiveStopPoints = [
58+
T_COLON => true,
59+
T_COMMA => true,
60+
T_DOUBLE_ARROW => true,
61+
T_SEMICOLON => true,
62+
];
5663

5764
/**
58-
* The position in the stack where the token was found.
65+
* Array of variable assignments encountered, along with their values.
66+
*
67+
* Populated at run-time.
5968
*
60-
* @var int
69+
* @var array<string, string> The key is the name of the variable, the value, its assigned value.
6170
*/
62-
private $stackPtr;
71+
private $variables_arr = [];
6372

6473
/**
6574
* Returns the token types that this sniff is interested in.
@@ -78,49 +87,44 @@ public function register() {
7887
* @return void
7988
*/
8089
public function process_token( $stackPtr ) {
81-
$this->stackPtr = $stackPtr;
82-
8390
// First collect all variables encountered and their values.
84-
$this->collect_variables();
91+
$this->collect_variables( $stackPtr );
8592

8693
// Then find all dynamic calls, and report them.
87-
$this->find_dynamic_calls();
94+
$this->find_dynamic_calls( $stackPtr );
8895
}
8996

9097
/**
9198
* Finds any variable-definitions in the file being processed and stores them
9299
* internally in a private array.
93100
*
101+
* @param int $stackPtr The position in the stack where the token was found.
102+
*
94103
* @return void
95104
*/
96-
private function collect_variables() {
105+
private function collect_variables( $stackPtr ) {
97106

98-
$current_var_name = $this->tokens[ $this->stackPtr ]['content'];
107+
$current_var_name = $this->tokens[ $stackPtr ]['content'];
99108

100109
/*
101110
* Find assignments ( $foo = "bar"; ) by finding all non-whitespaces,
102111
* and checking if the first one is T_EQUAL.
103112
*/
104-
$t_item_key = $this->phpcsFile->findNext(
105-
Tokens::$emptyTokens,
106-
$this->stackPtr + 1,
107-
null,
108-
true,
109-
null,
110-
true
111-
);
112-
113+
$t_item_key = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true );
113114
if ( $t_item_key === false || $this->tokens[ $t_item_key ]['code'] !== T_EQUAL ) {
114115
return;
115116
}
116117

117118
/*
118119
* Find assignments which only assign a plain text string.
119120
*/
120-
$end_of_statement = $this->phpcsFile->findNext( [ T_SEMICOLON, T_CLOSE_TAG ], ( $t_item_key + 1 ) );
121-
$value_ptr = null;
121+
$end_of_statement = $this->phpcsFile->findEndOfStatement( ( $t_item_key + 1 ) );
122+
if ( isset( $this->inclusiveStopPoints[ $this->tokens[ $end_of_statement ]['code'] ] ) === true ) {
123+
--$end_of_statement;
124+
}
122125

123-
for ( $i = $t_item_key + 1; $i < $end_of_statement; $i++ ) {
126+
$value_ptr = null;
127+
for ( $i = $t_item_key + 1; $i <= $end_of_statement; $i++ ) {
124128
if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) {
125129
continue;
126130
}
@@ -160,9 +164,11 @@ private function collect_variables() {
160164
*
161165
* Report on this when found, using the name of the function in the message.
162166
*
167+
* @param int $stackPtr The position in the stack where the token was found.
168+
*
163169
* @return void
164170
*/
165-
private function find_dynamic_calls() {
171+
private function find_dynamic_calls( $stackPtr ) {
166172
// No variables detected; no basis for doing anything.
167173
if ( empty( $this->variables_arr ) ) {
168174
return;
@@ -172,20 +178,20 @@ private function find_dynamic_calls() {
172178
* If variable is not found in our registry of variables, do nothing, as we cannot be
173179
* sure that the function being called is one of the disallowed ones.
174180
*/
175-
if ( ! isset( $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ) ) {
181+
if ( ! isset( $this->variables_arr[ $this->tokens[ $stackPtr ]['content'] ] ) ) {
176182
return;
177183
}
178184

179185
/*
180186
* Check if we have an '(' next.
181187
*/
182-
$next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $this->stackPtr + 1 ), null, true );
188+
$next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );
183189
if ( $next === false || $this->tokens[ $next ]['code'] !== T_OPEN_PARENTHESIS ) {
184190
return;
185191
}
186192

187193
$message = 'Dynamic calling is not recommended in the case of %s().';
188-
$data = [ $this->variables_arr[ $this->tokens[ $this->stackPtr ]['content'] ] ];
189-
$this->phpcsFile->addError( $message, $this->stackPtr, 'DynamicCalls', $data );
194+
$data = [ $this->variables_arr[ $this->tokens[ $stackPtr ]['content'] ] ];
195+
$this->phpcsFile->addError( $message, $stackPtr, 'DynamicCalls', $data );
190196
}
191197
}

WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.inc renamed to WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.1.inc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
function my_test() {
44
echo esc_html( "foo" );
55
}
6-
6+
$irrelevant = 10;
77

88
$my_notokay_func = 'extract';
99
$my_notokay_func(); // Bad.
@@ -34,5 +34,15 @@ $ensure_no_notices_are_thrown_on_parse_error = /*comment*/ ;
3434
$test_double_quoted_string = "assert";
3535
$test_double_quoted_string(); // Bad.
3636

37-
// Intentional parse error. This has to be the last test in the file.
38-
$my_notokay_func
37+
function hasStaticVars() {
38+
static $staticvar_foo = 'irrelevant', $staticvar_bar = 'func_num_args', $staticvar_baz = 'nothing';
39+
$staticvar_foo(); // OK.
40+
$staticvar_bar(); // Bad.
41+
$staticvar_baz(); // OK.
42+
}
43+
44+
function functionParams( $param_foo = 'func_get_arg', $param_bar = 'nothing', $param_baz = 'func_num_args') {
45+
$param_foo(); // Bad.
46+
$param_bar(); // OK.
47+
$param_baz(); // Bad.
48+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
// Intentional parse error. This has to be the last (only) test in the file.
4+
$my_notokay_func

WordPressVIPMinimum/Tests/Functions/DynamicCallsUnitTest.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,26 @@ class DynamicCallsUnitTest extends AbstractSniffUnitTest {
1919
/**
2020
* Returns the lines where errors should occur.
2121
*
22+
* @param string $testFile The name of the file being tested.
23+
*
2224
* @return array<int, int> Key is the line number, value is the number of expected errors.
2325
*/
24-
public function getErrorList() {
25-
return [
26-
9 => 1,
27-
15 => 1,
28-
35 => 1,
29-
];
26+
public function getErrorList( $testFile = '' ) {
27+
28+
switch ( $testFile ) {
29+
case 'DynamicCallsUnitTest.1.inc':
30+
return [
31+
9 => 1,
32+
15 => 1,
33+
35 => 1,
34+
40 => 1,
35+
45 => 1,
36+
47 => 1,
37+
];
38+
39+
default:
40+
return [];
41+
}
3042
}
3143

3244
/**

0 commit comments

Comments
 (0)