1010namespace WordPressVIPMinimum \Sniffs \Performance ;
1111
1212use PHP_CodeSniffer \Util \Tokens ;
13- use WordPressVIPMinimum \Sniffs \Sniff ;
13+ use PHPCSUtils \Tokens \Collections ;
14+ use PHPCSUtils \Utils \Conditions ;
15+ use WordPressCS \WordPress \AbstractFunctionRestrictionsSniff ;
1416
1517/**
1618 * This sniff check whether a cached value is being overridden.
1719 */
18- class CacheValueOverrideSniff extends Sniff {
20+ class CacheValueOverrideSniff extends AbstractFunctionRestrictionsSniff {
1921
2022 /**
21- * Returns the token types that this sniff is interested in .
23+ * Groups of functions to restrict .
2224 *
23- * @return array<int| string>
25+ * @return array<string, array<string, array<string>> >
2426 */
25- public function register () {
26- return [ T_STRING ];
27+ public function getGroups () {
28+ return [
29+ 'wp_cache_get ' => [
30+ 'functions ' => [ 'wp_cache_get ' ],
31+ ],
32+ ];
2733 }
2834
29-
3035 /**
31- * Processes the tokens that this sniff is interested in .
36+ * Process a matched token .
3237 *
33- * @param int $stackPtr The position in the stack where the token was found.
38+ * @param int $stackPtr The position of the current token in the stack.
39+ * @param string $group_name The name of the group which was matched.
40+ * @param string $matched_content The token content (function name) which was matched
41+ * in lowercase.
3442 *
3543 * @return void
3644 */
37- public function process_token ( $ stackPtr ) {
38-
39- $ functionName = $ this ->tokens [ $ stackPtr ]['content ' ];
45+ public function process_matched_token ( $ stackPtr , $ group_name , $ matched_content ) {
46+ $ openBracket = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , ( $ stackPtr + 1 ), null , true );
47+ if ( $ openBracket === false || isset ( $ this ->tokens [ $ openBracket ]['parenthesis_closer ' ] ) === false ) {
48+ // Import use statement for function or parse error/live coding. Ignore.
49+ return ;
50+ }
4051
41- if ( $ functionName !== 'wp_cache_get ' ) {
42- // Not a function we are looking for.
52+ $ closeBracket = $ this ->tokens [ $ openBracket ]['parenthesis_closer ' ];
53+ $ firstNonEmpty = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , ( $ openBracket + 1 ), null , true );
54+ $ nextNonEmpty = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , ( $ firstNonEmpty + 1 ), null , true );
55+ if ( $ nextNonEmpty === false ) {
56+ // Parse error/live coding. Ignore.
4357 return ;
4458 }
4559
46- if ( $ this ->isFunctionCall ( $ stackPtr ) === false ) {
47- // Not a function call.
60+ if ( $ this ->tokens [ $ firstNonEmpty ]['code ' ] === T_ELLIPSIS
61+ && $ nextNonEmpty === $ closeBracket
62+ ) {
63+ // First class callable. Ignore.
4864 return ;
4965 }
5066
5167 $ variablePos = $ this ->isVariableAssignment ( $ stackPtr );
52-
5368 if ( $ variablePos === false ) {
5469 // Not a variable assignment.
5570 return ;
@@ -58,56 +73,50 @@ public function process_token( $stackPtr ) {
5873 $ variableToken = $ this ->tokens [ $ variablePos ];
5974 $ variableName = $ variableToken ['content ' ];
6075
61- // Find the next non-empty token.
62- $ openBracket = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ stackPtr + 1 , null , true );
76+ // Figure out the scope we need to search in.
77+ $ searchEnd = $ this ->phpcsFile ->numTokens ;
78+ $ functionPtr = Conditions::getLastCondition ( $ this ->phpcsFile , $ stackPtr , [ T_FUNCTION , T_CLOSURE ] );
79+ if ( $ functionPtr !== false && isset ( $ this ->tokens [ $ functionPtr ]['scope_closer ' ] ) ) {
80+ $ searchEnd = $ this ->tokens [ $ functionPtr ]['scope_closer ' ];
81+ }
6382
64- // Find the closing bracket.
65- $ closeBracket = $ this ->tokens [ $ openBracket ]['parenthesis_closer ' ];
83+ $ nextVariableOccurrence = false ;
84+ for ( $ i = $ closeBracket + 1 ; $ i < $ searchEnd ; $ i ++ ) {
85+ if ( $ this ->tokens [ $ i ]['code ' ] === T_VARIABLE && $ this ->tokens [ $ i ]['content ' ] === $ variableName ) {
86+ $ nextVariableOccurrence = $ i ;
87+ break ;
88+ }
89+
90+ // Skip over any and all closed scopes.
91+ if ( isset ( Collections::closedScopes ()[ $ this ->tokens [ $ i ]['code ' ] ] ) ) {
92+ if ( isset ( $ this ->tokens [ $ i ]['scope_closer ' ] ) ) {
93+ $ i = $ this ->tokens [ $ i ]['scope_closer ' ];
94+ }
95+ }
96+ }
6697
67- $ nextVariableOccurrence = $ this ->phpcsFile ->findNext ( T_VARIABLE , $ closeBracket + 1 , null , false , $ variableName );
98+ if ( $ nextVariableOccurrence === false ) {
99+ return ;
100+ }
68101
69- $ rightAfterNextVariableOccurence = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ nextVariableOccurrence + 1 , null , true , null , true );
102+ $ rightAfterNextVariableOccurence = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ nextVariableOccurrence + 1 , $ searchEnd , true , null , true );
70103
71- if ( $ this ->tokens [ $ rightAfterNextVariableOccurence ]['code ' ] !== T_EQUAL ) {
104+ if ( $ rightAfterNextVariableOccurence === false
105+ || $ this ->tokens [ $ rightAfterNextVariableOccurence ]['code ' ] !== T_EQUAL
106+ ) {
72107 // Not a value override.
73108 return ;
74109 }
75110
76- $ valueAfterEqualSign = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ rightAfterNextVariableOccurence + 1 , null , true , null , true );
111+ $ valueAfterEqualSign = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ rightAfterNextVariableOccurence + 1 , $ searchEnd , true , null , true );
77112
78- if ( $ this ->tokens [ $ valueAfterEqualSign ]['code ' ] === T_FALSE ) {
113+ if ( $ valueAfterEqualSign !== false && $ this ->tokens [ $ valueAfterEqualSign ]['code ' ] === T_FALSE ) {
79114 $ message = 'Obtained cached value in `%s` is being overridden. Disabling caching? ' ;
80115 $ data = [ $ variableName ];
81116 $ this ->phpcsFile ->addError ( $ message , $ nextVariableOccurrence , 'CacheValueOverride ' , $ data );
82117 }
83118 }
84119
85- /**
86- * Check whether the examined code is a function call.
87- *
88- * @param int $stackPtr The position of the current token in the stack.
89- *
90- * @return bool
91- */
92- private function isFunctionCall ( $ stackPtr ) {
93-
94- // Find the next non-empty token.
95- $ openBracket = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , $ stackPtr + 1 , null , true );
96-
97- if ( $ this ->tokens [ $ openBracket ]['code ' ] !== T_OPEN_PARENTHESIS ) {
98- // Not a function call.
99- return false ;
100- }
101-
102- // Find the previous non-empty token.
103- $ search = Tokens::$ emptyTokens ;
104- $ search [] = T_BITWISE_AND ;
105- $ previous = $ this ->phpcsFile ->findPrevious ( $ search , $ stackPtr - 1 , null , true );
106-
107- // It's a function definition, not a function call, so return false.
108- return ! ( $ this ->tokens [ $ previous ]['code ' ] === T_FUNCTION );
109- }
110-
111120 /**
112121 * Check whether the examined code is a variable assignment.
113122 *
@@ -117,9 +126,10 @@ private function isFunctionCall( $stackPtr ) {
117126 */
118127 private function isVariableAssignment ( $ stackPtr ) {
119128
120- // Find the previous non-empty token.
129+ // Find the previous non-empty token, but allow for FQN function calls .
121130 $ search = Tokens::$ emptyTokens ;
122131 $ search [] = T_BITWISE_AND ;
132+ $ search [] = T_NS_SEPARATOR ;
123133 $ previous = $ this ->phpcsFile ->findPrevious ( $ search , $ stackPtr - 1 , null , true );
124134
125135 if ( $ this ->tokens [ $ previous ]['code ' ] !== T_EQUAL ) {
0 commit comments