@@ -1703,7 +1703,7 @@ protected function get_array_access_key( $stackPtr ) {
17031703 }
17041704
17051705 /**
1706- * Check if the existence of a variable is validated with isset() or empty ().
1706+ * Check if the existence of a variable is validated with isset(), empty() or array_key_exists ().
17071707 *
17081708 * When $in_condition_only is false, (which is the default), this is considered
17091709 * valid:
@@ -1726,6 +1726,7 @@ protected function get_array_access_key( $stackPtr ) {
17261726 * ```
17271727 *
17281728 * @since 0.5.0
1729+ * @since 2.0.1 Now recognizes array_key_exists() as a validation function.
17291730 *
17301731 * @param int $stackPtr The index of this token in the stack.
17311732 * @param string $array_key An array key to check for ("bar" in $foo['bar']).
@@ -1791,36 +1792,94 @@ protected function is_validated( $stackPtr, $array_key = null, $in_condition_onl
17911792 }
17921793
17931794 $ bare_array_key = $ this ->strip_quotes ( $ array_key );
1795+ $ targets = array (
1796+ \T_ISSET => 'construct ' ,
1797+ \T_EMPTY => 'construct ' ,
1798+ \T_UNSET => 'construct ' ,
1799+ \T_STRING => 'function_call ' ,
1800+ );
17941801
17951802 // phpcs:ignore Generic.CodeAnalysis.JumbledIncrementer.Found -- On purpose, see below.
17961803 for ( $ i = ( $ scope_start + 1 ); $ i < $ scope_end ; $ i ++ ) {
17971804
1798- if ( ! \in_array ( $ this ->tokens [ $ i ]['code ' ], array ( \ T_ISSET , \ T_EMPTY , \ T_UNSET ), true ) ) {
1805+ if ( isset ( $ targets [ $ this ->tokens [ $ i ]['code ' ] ] ) === false ) {
17991806 continue ;
18001807 }
18011808
1802- $ issetOpener = $ this ->phpcsFile ->findNext ( \T_OPEN_PARENTHESIS , $ i );
1803- $ issetCloser = $ this ->tokens [ $ issetOpener ]['parenthesis_closer ' ];
1809+ switch ( $ targets [ $ this ->tokens [ $ i ]['code ' ] ] ) {
1810+ case 'construct ' :
1811+ $ issetOpener = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , ( $ i + 1 ), null , true , null , true );
1812+ if ( false === $ issetOpener || \T_OPEN_PARENTHESIS !== $ this ->tokens [ $ issetOpener ]['code ' ] ) {
1813+ // Parse error or live coding.
1814+ continue 2 ;
1815+ }
18041816
1805- // Look for this variable. We purposely stomp $i from the parent loop.
1806- for ( $ i = ( $ issetOpener + 1 ); $ i < $ issetCloser ; $ i ++ ) {
1817+ $ issetCloser = $ this ->tokens [ $ issetOpener ]['parenthesis_closer ' ];
18071818
1808- if ( \T_VARIABLE !== $ this ->tokens [ $ i ]['code ' ] ) {
1809- continue ;
1810- }
1819+ // Look for this variable. We purposely stomp $i from the parent loop.
1820+ for ( $ i = ( $ issetOpener + 1 ); $ i < $ issetCloser ; $ i ++ ) {
18111821
1812- if ( $ this -> tokens [ $ stackPtr ][ ' content ' ] !== $ this ->tokens [ $ i ]['content ' ] ) {
1813- continue ;
1814- }
1822+ if ( \ T_VARIABLE !== $ this ->tokens [ $ i ]['code ' ] ) {
1823+ continue ;
1824+ }
18151825
1816- // If we're checking for a specific array key (ex: 'hello' in
1817- // $_POST['hello']), that must match too. Quote-style, however, doesn't matter.
1818- if ( isset ( $ array_key )
1819- && $ this ->strip_quotes ( $ this ->get_array_access_key ( $ i ) ) !== $ bare_array_key ) {
1820- continue ;
1821- }
1826+ if ( $ this ->tokens [ $ stackPtr ]['content ' ] !== $ this ->tokens [ $ i ]['content ' ] ) {
1827+ continue ;
1828+ }
18221829
1823- return true ;
1830+ // If we're checking for a specific array key (ex: 'hello' in
1831+ // $_POST['hello']), that must match too. Quote-style, however, doesn't matter.
1832+ if ( isset ( $ array_key )
1833+ && $ this ->strip_quotes ( $ this ->get_array_access_key ( $ i ) ) !== $ bare_array_key ) {
1834+ continue ;
1835+ }
1836+
1837+ return true ;
1838+ }
1839+
1840+ break ;
1841+
1842+ case 'function_call ' :
1843+ // Only check calls to array_key_exists().
1844+ if ( 'array_key_exists ' !== $ this ->tokens [ $ i ]['content ' ] ) {
1845+ continue 2 ;
1846+ }
1847+
1848+ $ next_non_empty = $ this ->phpcsFile ->findNext ( Tokens::$ emptyTokens , ( $ i + 1 ), null , true , null , true );
1849+ if ( false === $ next_non_empty || \T_OPEN_PARENTHESIS !== $ this ->tokens [ $ next_non_empty ]['code ' ] ) {
1850+ // Not a function call.
1851+ continue 2 ;
1852+ }
1853+
1854+ $ previous_non_empty = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ i - 1 ), null , true , null , true );
1855+ if ( false !== $ previous_non_empty ) {
1856+ if ( \T_OBJECT_OPERATOR === $ this ->tokens [ $ previous_non_empty ]['code ' ]
1857+ || \T_DOUBLE_COLON === $ this ->tokens [ $ previous_non_empty ]['code ' ]
1858+ ) {
1859+ // Method call.
1860+ continue 2 ;
1861+ }
1862+
1863+ if ( \T_NS_SEPARATOR === $ this ->tokens [ $ previous_non_empty ]['code ' ] ) {
1864+ $ pprev = $ this ->phpcsFile ->findPrevious ( Tokens::$ emptyTokens , ( $ previous_non_empty - 1 ), null , true , null , true );
1865+ if ( false !== $ pprev && \T_STRING === $ this ->tokens [ $ pprev ]['code ' ] ) {
1866+ // Namespaced function call.
1867+ continue 2 ;
1868+ }
1869+ }
1870+ }
1871+
1872+ $ params = $ this ->get_function_call_parameters ( $ i );
1873+ if ( $ params [2 ]['raw ' ] !== $ this ->tokens [ $ stackPtr ]['content ' ] ) {
1874+ continue 2 ;
1875+ }
1876+
1877+ if ( isset ( $ array_key )
1878+ && $ this ->strip_quotes ( $ params [1 ]['raw ' ] ) !== $ bare_array_key ) {
1879+ continue 2 ;
1880+ }
1881+
1882+ return true ;
18241883 }
18251884 }
18261885
0 commit comments