From ab1167eafe062d9169c15c66307b32871f026a54 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 14 Aug 2025 15:10:26 -0300 Subject: [PATCH 1/7] ContextHelper::is_in_function_call(): add basic tests Since there were no previous tests for utility methods, it was also necessary to modify the test structure to enable running utility tests. They are different from the sniff tests as they extend `UtilityMethodTestCase` and don't need to run via `./vendor/squizlabs/php_codesniffer/tests/AllTests.php`. --- Tests/bootstrap.php | 15 ++ .../IsInFunctionCallUnitTest.inc | 57 ++++++ .../IsInFunctionCallUnitTest.php | 165 ++++++++++++++++++ composer.json | 6 +- phpunit.xml.dist | 5 +- 5 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc create mode 100644 WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index fd4c952893..7ec3f5582c 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -45,6 +45,21 @@ ) { require_once $phpcsDir . $ds . 'autoload.php'; require_once $phpcsDir . $ds . 'tests' . $ds . 'bootstrap.php'; // PHPUnit 6.x+ support. + + spl_autoload_register( + function ( $className ) { + // Only try & load our own classes. + if ( stripos( $className, 'WordPressCS' ) !== 0 ) { + return; + } + + $file = realpath( dirname( __DIR__ ) ) . DIRECTORY_SEPARATOR . strtr( str_replace( 'WordPressCS\\', '', $className ), '\\', DIRECTORY_SEPARATOR ) . '.php'; + + if ( file_exists( $file ) ) { + include_once $file; + } + } + ); } else { echo 'Uh oh... can\'t find PHPCS. diff --git a/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc b/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc new file mode 100644 index 0000000000..36f11ca226 --- /dev/null +++ b/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc @@ -0,0 +1,57 @@ +my_function( /* test inside function pointer 11 */ $a ); +/* test function call 12 */ $obj?->my_function( /* test inside function pointer 12 */ $a ); + +/* + * Make sure that tokens inside a function call are correctly identified when `$allow_nested` is + * set to true. + * + * The below should be recognized as inside a function call to one of the valid functions. + */ +/* test function call 13 */ my_function( another_function( /* test inside function pointer 13 */ $a ) ); +another_function( /* test function call 14 */ my_function( /* test inside function pointer 14 */ $a ) ); +/* test function call 15 */ my_function( middle_function( inner_function( /* test inside function pointer 15 */ $a ) ) ); diff --git a/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php b/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php new file mode 100644 index 0000000000..0389cfd67b --- /dev/null +++ b/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php @@ -0,0 +1,165 @@ +getTargetToken( $commentString, $tokenType ); + $result = ContextHelper::is_in_function_call( self::$phpcsFile, $stackPtr, array( 'my_function' => true ) ); + $this->assertFalse( $result ); + } + + /** + * Data provider. + * + * @return array + * @see testIsInFunctionCallShouldReturnFalse() + */ + public static function dataIsInFunctionCallShouldReturnFalse() { + return array( + array( '/* test return false 1 */', \T_CONSTANT_ENCAPSED_STRING ), + array( '/* test return false 2 */', \T_VARIABLE ), + array( '/* test return false 3 */', \T_VARIABLE ), + array( '/* test return false 4 */', \T_VARIABLE ), + array( '/* test return false 5 */', \T_VARIABLE ), + array( '/* test return false 6 */', \T_VARIABLE ), + array( '/* test return false 7 */', \T_VARIABLE ), + array( '/* test return false 8 */', \T_VARIABLE ), + ); + } + + /** + * Test is_in_function_call() returns pointer to function name if given token is inside a function call. + * + * @dataProvider dataIsInFunctionCallShouldReturnFunctionPointer + * + * @param string $insideFunctionCommentString The comment which prefaces the target token inside a function in the test file. + * @param int|string $insideFunctionTokenType The token type to search for. + * @param string $functionCallCommentString The comment which prefaces the function call token in the test file. + * + * @return void + */ + public function testIsInFunctionCallShouldReturnFunctionPointer( $insideFunctionCommentString, $insideFunctionTokenType, $functionCallCommentString ) { + $insideFunctionPtr = $this->getTargetToken( $insideFunctionCommentString, $insideFunctionTokenType ); + $functionNamePtr = $this->getTargetToken( $functionCallCommentString, Collections::nameTokens() ); + $result = ContextHelper::is_in_function_call( self::$phpcsFile, $insideFunctionPtr, array( 'my_function' => true ) ); + $this->assertSame( $result, $functionNamePtr ); + } + + /** + * Data provider. + * + * @return array + * @see testIsInFunctionCallShouldReturnFunctionPointer() + */ + public static function dataIsInFunctionCallShouldReturnFunctionPointer() { + return array( + array( '/* test inside function pointer 1 */', \T_VARIABLE, '/* test function call 1 */' ), + array( '/* test inside function pointer 2 */', \T_VARIABLE, '/* test function call 2 */' ), + array( '/* test inside function pointer 3 */', \T_VARIABLE, '/* test function call 3 */' ), + ); + } + + /** + * Test is_in_function_call() returns pointer to function name if given token is inside a + * function call when `$global_functions` is set to false. + * + * @dataProvider dataIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse + * + * @param string $insideFunctionCommentString The comment which prefaces the target token inside a function in the test file. + * @param int|string $insideFunctionTokenType The token type to search for. + * @param string $functionCallCommentString The comment which prefaces the function call token in the test file. + * + * @return void + */ + public function testIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse( $insideFunctionCommentString, $insideFunctionTokenType, $functionCallCommentString ) { + $insideFunctionPtr = $this->getTargetToken( $insideFunctionCommentString, $insideFunctionTokenType ); + $functionNamePtr = $this->getTargetToken( $functionCallCommentString, Collections::nameTokens() ); + $result = ContextHelper::is_in_function_call( self::$phpcsFile, $insideFunctionPtr, array( 'my_function' => true ), false ); + $this->assertSame( $result, $functionNamePtr ); + } + + /** + * Data provider. + * + * @return array + * @see testIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse() + */ + public static function dataIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse() { + return array( + array( '/* test inside function pointer 4 */', \T_VARIABLE, '/* test function call 4 */' ), + array( '/* test inside function pointer 5 */', \T_VARIABLE, '/* test function call 5 */' ), + array( '/* test inside function pointer 6 */', \T_VARIABLE, '/* test function call 6 */' ), + array( '/* test inside function pointer 7 */', \T_VARIABLE, '/* test function call 7 */' ), + array( '/* test inside function pointer 8 */', \T_VARIABLE, '/* test function call 8 */' ), + array( '/* test inside function pointer 9 */', \T_VARIABLE, '/* test function call 9 */' ), + array( '/* test inside function pointer 10 */', \T_VARIABLE, '/* test function call 10 */' ), + array( '/* test inside function pointer 11 */', \T_VARIABLE, '/* test function call 11 */' ), + array( '/* test inside function pointer 12 */', \T_VARIABLE, '/* test function call 12 */' ), + ); + } + + /** + * Test is_in_function_call() returns pointer to function name if given token is inside a + * function call when `$allow_nested` is set to true. + * + * @dataProvider dataIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue + * + * @param string $insideFunctionCommentString The comment which prefaces the target token inside a function in the test file. + * @param int|string $insideFunctionTokenType The token type to search for. + * @param string $functionCallCommentString The comment which prefaces the function call token in the test file. + * + * @return void + */ + public function testIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue( $insideFunctionCommentString, $insideFunctionTokenType, $functionCallCommentString ) { + $insideFunctionPtr = $this->getTargetToken( $insideFunctionCommentString, $insideFunctionTokenType ); + $functionNamePtr = $this->getTargetToken( $functionCallCommentString, Collections::nameTokens() ); + $result = ContextHelper::is_in_function_call( self::$phpcsFile, $insideFunctionPtr, array( 'my_function' => true ), true, true ); + $this->assertSame( $result, $functionNamePtr ); + } + + /** + * Data provider. + * + * @return array + * @see testIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue() + */ + public static function dataIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue() { + return array( + array( '/* test inside function pointer 13 */', \T_VARIABLE, '/* test function call 13 */' ), + array( '/* test inside function pointer 14 */', \T_VARIABLE, '/* test function call 14 */' ), + array( '/* test inside function pointer 15 */', \T_VARIABLE, '/* test function call 15 */' ), + ); + } +} diff --git a/composer.json b/composer.json index a5afb55f6b..a678fb6c20 100644 --- a/composer.json +++ b/composer.json @@ -53,10 +53,12 @@ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" ], "run-tests": [ - "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" + "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage", + "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/ --no-coverage" ], "coverage": [ - "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" + "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php", + "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/" ], "check-complete": [ "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness -q ./WordPress" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a7a2b1401f..757c96fec1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,7 +13,10 @@ forceCoversAnnotation="true"> - + + ./WordPress/Util/Tests/ + + ./WordPress/Tests/ From 414031e50bf035a77637ffa93fe309fa7b0d4ba1 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 14 Oct 2025 08:05:28 -0300 Subject: [PATCH 2/7] Fix indentation (use tabs instead of spaces) --- composer.json | 4 ++-- phpunit.xml.dist | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index a678fb6c20..3c1067c0f0 100644 --- a/composer.json +++ b/composer.json @@ -54,11 +54,11 @@ ], "run-tests": [ "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage", - "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/ --no-coverage" + "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/ --no-coverage" ], "coverage": [ "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php", - "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/" + "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/" ], "check-complete": [ "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness -q ./WordPress" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 757c96fec1..0f3566b327 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,9 +13,9 @@ forceCoversAnnotation="true"> - - ./WordPress/Util/Tests/ - + + ./WordPress/Util/Tests/ + ./WordPress/Tests/ From a5236761e6abd4846c577ed72fe7cd63fef4bc24 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 14 Oct 2025 10:56:23 -0300 Subject: [PATCH 3/7] Remove is_in_function_call() tests from NonceVerificationUnitTest.1.inc --- .../Security/NonceVerificationUnitTest.1.inc | 22 +++++++++---------- .../Security/NonceVerificationUnitTest.php | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc index aee51ae39a..f1f7ff385e 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc @@ -291,18 +291,18 @@ function function_containing_nested_closure() { }; } -// Tests specifically for the ContextHelper::is_in_function_call(). -function disallow_custom_unslash_before_noncecheck_via_method() { - $var = MyClass::stripslashes_from_strings_only( $_POST['foo'] ); // Bad. - wp_verify_nonce( $var ); - echo $var; -} -function disallow_custom_unslash_before_noncecheck_via_namespaced_function() { - $var = MyNamespace\stripslashes_from_strings_only( $_POST['foo'] ); // Bad. - wp_verify_nonce( $var ); - echo $var; -} + + + + + + + + + + + // Tests specifically for the ContextHelper::is_in_isset_or_empty(). function allow_in_array_key_exists_before_noncecheck() { diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.php b/WordPress/Tests/Security/NonceVerificationUnitTest.php index aa5e832e83..97bf652750 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.php +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.php @@ -59,8 +59,6 @@ public function getErrorList( $testFile = '' ) { 192 => 1, 242 => 1, 259 => 1, - 296 => 1, - 302 => 1, 325 => 1, 329 => 1, 337 => 1, From 1ea69cf214f8c794798db8ff7a5d8b35bed1b80a Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 22 Oct 2025 10:08:57 -0300 Subject: [PATCH 4/7] Move utility tests to WordPress/Tests/Helpers/ContextHelper/ --- Tests/bootstrap.php | 15 --------------- .../ContextHelper/IsInFunctionCallUnitTest.inc | 0 .../ContextHelper/IsInFunctionCallUnitTest.php | 2 +- composer.json | 6 ++---- phpunit.xml.dist | 5 +---- 5 files changed, 4 insertions(+), 24 deletions(-) rename WordPress/{Util => }/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc (100%) rename WordPress/{Util => }/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php (99%) diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 7ec3f5582c..fd4c952893 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -45,21 +45,6 @@ ) { require_once $phpcsDir . $ds . 'autoload.php'; require_once $phpcsDir . $ds . 'tests' . $ds . 'bootstrap.php'; // PHPUnit 6.x+ support. - - spl_autoload_register( - function ( $className ) { - // Only try & load our own classes. - if ( stripos( $className, 'WordPressCS' ) !== 0 ) { - return; - } - - $file = realpath( dirname( __DIR__ ) ) . DIRECTORY_SEPARATOR . strtr( str_replace( 'WordPressCS\\', '', $className ), '\\', DIRECTORY_SEPARATOR ) . '.php'; - - if ( file_exists( $file ) ) { - include_once $file; - } - } - ); } else { echo 'Uh oh... can\'t find PHPCS. diff --git a/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc similarity index 100% rename from WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc rename to WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc diff --git a/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php similarity index 99% rename from WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php rename to WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php index 0389cfd67b..f7bac33628 100644 --- a/WordPress/Util/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php @@ -7,7 +7,7 @@ * @license https://opensource.org/licenses/MIT MIT */ -namespace WordPressCS\WordPress\Util\Tests\Helpers\ContextHelper; +namespace WordPressCS\WordPress\Tests\Helpers\ContextHelper; use PHPCSUtils\Tokens\Collections; use WordPressCS\WordPress\Helpers\ContextHelper; diff --git a/composer.json b/composer.json index 3c1067c0f0..a5afb55f6b 100644 --- a/composer.json +++ b/composer.json @@ -53,12 +53,10 @@ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" ], "run-tests": [ - "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage", - "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/ --no-coverage" + "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" ], "coverage": [ - "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php", - "@php ./vendor/phpunit/phpunit/phpunit WordPress/Util/Tests/" + "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" ], "check-complete": [ "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness -q ./WordPress" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0f3566b327..a7a2b1401f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,10 +13,7 @@ forceCoversAnnotation="true"> - - ./WordPress/Util/Tests/ - - + ./WordPress/Tests/ From 59e4e53ecbcee43c953d7179c5813d0630da35dd Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 22 Oct 2025 15:02:05 -0300 Subject: [PATCH 5/7] Refactor is_in_function_call() tests to cover all parameter combinations and to remove duplicate test cases --- .../IsInFunctionCallUnitTest.inc | 106 +++-- .../IsInFunctionCallUnitTest.php | 429 ++++++++++++++---- 2 files changed, 406 insertions(+), 129 deletions(-) diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc index 36f11ca226..404f64d24f 100644 --- a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc @@ -1,57 +1,79 @@ my_function( /* test inside function pointer 11 */ $a ); -/* test function call 12 */ $obj?->my_function( /* test inside function pointer 12 */ $a ); - -/* - * Make sure that tokens inside a function call are correctly identified when `$allow_nested` is - * set to true. - * - * The below should be recognized as inside a function call to one of the valid functions. - */ -/* test function call 13 */ my_function( another_function( /* test inside function pointer 13 */ $a ) ); -another_function( /* test function call 14 */ my_function( /* test inside function pointer 14 */ $a ) ); -/* test function call 15 */ my_function( middle_function( inner_function( /* test inside function pointer 15 */ $a ) ) ); +MyNamespace\/* testNamespacedFunction */valid_function1( /* testNamespacedFunctionInsideCall */ some_function() ); +\MyNamespace\/* testFullyQualifiedNamespacedFunction */valid_function1( + /* testFullyQualifiedNamespacedFunctionInsideCall */ null +); +namespace\MyNamespace\/* testNamespaceRelativeFunction */valid_function2( + /* testNamespaceRelativeFunctionInsideCall */ 3.14 +); +MyClass::/* testStaticMethod */valid_function2( + /* testStaticMethodInsideCall */ 'text' +); +/* testObjectMethod */ +$obj->valid_function1( /* testObjectMethodInsideCall */ array() ); +/* testNullsafeObjectMethod */ +$obj?->valid_function1( /* testNullsafeObjectMethodInsideCall */ [ 1, 2, 3 ] ); +/* testNestedOuter */ +valid_function1( another_function( /* testNestedOuterInsideCall */ 'param' ) ); +another_function( /* testNestedInner */ valid_function1( /* testNestedInnerInsideCall */ true ) ); +/* testNestedMultipleLevels */ +valid_function1( middle_function( inner_function( /* testNestedMultipleLevelsInsideCall */ 999 ) ) ); +MyNamespace\/* testNestedBothNamespacedOuter */ valid_function1( + MyNamespace\other_function( + /* testNestedBothNamespacedOuterInsideCall */ 'value' . $var + ) +); +/* testComplexParametersAlwaysMatch */ +valid_function1( + array( 'key1' => 'value1' ), + other_function( 'nested' ), + /* testComplexParametersAlwaysMatchInsideCall */ $var +); +/* testComplexParametersNestedOnly */ +valid_function1( + function() { return 'closure value'; }, + $obj->method(), + another_function( /* testComplexParametersNestedOnlyInsideCall */ $var ) +); +/* testComplexParametersNonGlobal */ +$obj->valid_function1( + array( 'key1' => get_value() ), + MyClass::helper(), + /* testComplexParametersNonGlobalInsideCall */ true +); +MyClass::/* testComplexParametersNonGlobalNested */ valid_function1( + $obj->method(), + 'text', + array( 'key' => MyNamespace\other_function( /* testComplexParametersNonGlobalNestedInsideCall */ 123 ) ) +); diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php index f7bac33628..e817cd46fc 100644 --- a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php @@ -9,7 +9,6 @@ namespace WordPressCS\WordPress\Tests\Helpers\ContextHelper; -use PHPCSUtils\Tokens\Collections; use WordPressCS\WordPress\Helpers\ContextHelper; use PHPCSUtils\TestUtils\UtilityMethodTestCase; @@ -22,144 +21,400 @@ */ final class IsInFunctionCallUnitTest extends UtilityMethodTestCase { + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return `false` regardless + * of the value of the parameters `$global_function` and `$allow_nested`. + * + * @var array + */ + const EXPECT_NO_MATCH = array( + 'global_only' => false, + 'global_nested' => false, + 'non_global_only' => false, + 'non_global_nested' => false, + ); /** - * Test is_in_function_call() returns false if given token is not inside a function call - * or not inside one of the expected function calls. + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer regardless of the value of the parameters `$global_function` and `$allow_nested`. * - * @dataProvider dataIsInFunctionCallShouldReturnFalse + * @var array + */ + const EXPECT_ALWAYS_MATCH = array( + 'global_only' => true, + 'global_nested' => true, + 'non_global_only' => true, + 'non_global_nested' => true, + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer when `$global_function` is `false`, and `false` when `$global_function` is `true`. * - * @param string $commentString The comment which prefaces the target token in the test file. - * @param int|string $tokenType The token type to search for. + * @var array + */ + const EXPECT_NON_GLOBAL_ONLY = array( + 'global_only' => false, + 'global_nested' => false, + 'non_global_only' => true, + 'non_global_nested' => true, + ); + + /** + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer when `$allow_nested` is `true`, and `false` when `$allow_nested` is `false`. * - * @return void + * @var array */ - public function testIsInFunctionCallShouldReturnFalse( $commentString, $tokenType ) { - $stackPtr = $this->getTargetToken( $commentString, $tokenType ); - $result = ContextHelper::is_in_function_call( self::$phpcsFile, $stackPtr, array( 'my_function' => true ) ); - $this->assertFalse( $result ); - } + const EXPECT_NESTED_ONLY = array( + 'global_only' => false, + 'global_nested' => true, + 'non_global_only' => false, + 'non_global_nested' => true, + ); /** - * Data provider. + * Expected results: when a test case uses this constant, `is_in_function_call()` should return the function name + * pointer when both `$global_function` is `false` and `$allow_nested` is `true`, and `false` otherwise. * - * @return array - * @see testIsInFunctionCallShouldReturnFalse() + * @var array */ - public static function dataIsInFunctionCallShouldReturnFalse() { - return array( - array( '/* test return false 1 */', \T_CONSTANT_ENCAPSED_STRING ), - array( '/* test return false 2 */', \T_VARIABLE ), - array( '/* test return false 3 */', \T_VARIABLE ), - array( '/* test return false 4 */', \T_VARIABLE ), - array( '/* test return false 5 */', \T_VARIABLE ), - array( '/* test return false 6 */', \T_VARIABLE ), - array( '/* test return false 7 */', \T_VARIABLE ), - array( '/* test return false 8 */', \T_VARIABLE ), + const EXPECT_NON_GLOBAL_NESTED_ONLY = array( + 'global_only' => false, + 'global_nested' => false, + 'non_global_only' => false, + 'non_global_nested' => true, + ); + + /** + * Maps expected result keys to their corresponding is_in_function_call() parameter values. + * + * @var array> + */ + const PARAMETER_MAP = array( + 'global_only' => array( + 'global_function' => true, + 'allow_nested' => false, + ), + 'global_nested' => array( + 'global_function' => true, + 'allow_nested' => true, + ), + 'non_global_only' => array( + 'global_function' => false, + 'allow_nested' => false, + ), + 'non_global_nested' => array( + 'global_function' => false, + 'allow_nested' => true, + ), + ); + + /** + * Test is_in_function_call() with $global_function=true (default) and $allow_nested=false (default). + * + * @dataProvider dataIsInFunctionCall + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $tokenType The token type to search for. + * @param array $expectedResults Which parameter combinations should match. + * @param string|null $expectedFunctionMarker Optional. The comment for the expected function (if match expected). + * + * @return void + */ + public function testIsInFunctionCallWithDefaultParams( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker = null + ) { + $this->runIsInFunctionCallTest( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker, + 'global_only' ); } /** - * Test is_in_function_call() returns pointer to function name if given token is inside a function call. + * Test is_in_function_call() with $global_function=false and $allow_nested=false (default). * - * @dataProvider dataIsInFunctionCallShouldReturnFunctionPointer + * @dataProvider dataIsInFunctionCall * - * @param string $insideFunctionCommentString The comment which prefaces the target token inside a function in the test file. - * @param int|string $insideFunctionTokenType The token type to search for. - * @param string $functionCallCommentString The comment which prefaces the function call token in the test file. + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $tokenType The token type to search for. + * @param array $expectedResults Which parameter combinations should match. + * @param string|null $expectedFunctionMarker Optional. The comment for the expected function (if match expected). * * @return void */ - public function testIsInFunctionCallShouldReturnFunctionPointer( $insideFunctionCommentString, $insideFunctionTokenType, $functionCallCommentString ) { - $insideFunctionPtr = $this->getTargetToken( $insideFunctionCommentString, $insideFunctionTokenType ); - $functionNamePtr = $this->getTargetToken( $functionCallCommentString, Collections::nameTokens() ); - $result = ContextHelper::is_in_function_call( self::$phpcsFile, $insideFunctionPtr, array( 'my_function' => true ) ); - $this->assertSame( $result, $functionNamePtr ); + public function testIsInFunctionCallWithGlobalFalse( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker = null + ) { + $this->runIsInFunctionCallTest( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker, + 'non_global_only' + ); } /** - * Data provider. + * Test is_in_function_call() with $global_function=true (default) and $allow_nested=true. + * + * @dataProvider dataIsInFunctionCall + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $tokenType The token type to search for. + * @param array $expectedResults Which parameter combinations should match. + * @param string|null $expectedFunctionMarker Optional. The comment for the expected function (if match expected). * - * @return array - * @see testIsInFunctionCallShouldReturnFunctionPointer() + * @return void */ - public static function dataIsInFunctionCallShouldReturnFunctionPointer() { - return array( - array( '/* test inside function pointer 1 */', \T_VARIABLE, '/* test function call 1 */' ), - array( '/* test inside function pointer 2 */', \T_VARIABLE, '/* test function call 2 */' ), - array( '/* test inside function pointer 3 */', \T_VARIABLE, '/* test function call 3 */' ), + public function testIsInFunctionCallWithNestedTrue( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker = null + ) { + $this->runIsInFunctionCallTest( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker, + 'global_nested' ); } /** - * Test is_in_function_call() returns pointer to function name if given token is inside a - * function call when `$global_functions` is set to false. + * Test is_in_function_call() with $global_function=false and $allow_nested=true. * - * @dataProvider dataIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse + * @dataProvider dataIsInFunctionCall * - * @param string $insideFunctionCommentString The comment which prefaces the target token inside a function in the test file. - * @param int|string $insideFunctionTokenType The token type to search for. - * @param string $functionCallCommentString The comment which prefaces the function call token in the test file. + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $tokenType The token type to search for. + * @param array $expectedResults Which parameter combinations should match. + * @param string|null $expectedFunctionMarker Optional. The comment for the expected function (if match expected). * * @return void */ - public function testIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse( $insideFunctionCommentString, $insideFunctionTokenType, $functionCallCommentString ) { - $insideFunctionPtr = $this->getTargetToken( $insideFunctionCommentString, $insideFunctionTokenType ); - $functionNamePtr = $this->getTargetToken( $functionCallCommentString, Collections::nameTokens() ); - $result = ContextHelper::is_in_function_call( self::$phpcsFile, $insideFunctionPtr, array( 'my_function' => true ), false ); - $this->assertSame( $result, $functionNamePtr ); + public function testIsInFunctionCallWithGlobalFalseNestedTrue( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker = null + ) { + $this->runIsInFunctionCallTest( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker, + 'non_global_nested' + ); } /** - * Data provider. + * Test is_in_function_call() when $valid_functions is an empty array. * - * @return array - * @see testIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse() + * @return void */ - public static function dataIsInFunctionCallShouldReturnFunctionPointerWhenGlobalIsFalse() { - return array( - array( '/* test inside function pointer 4 */', \T_VARIABLE, '/* test function call 4 */' ), - array( '/* test inside function pointer 5 */', \T_VARIABLE, '/* test function call 5 */' ), - array( '/* test inside function pointer 6 */', \T_VARIABLE, '/* test function call 6 */' ), - array( '/* test inside function pointer 7 */', \T_VARIABLE, '/* test function call 7 */' ), - array( '/* test inside function pointer 8 */', \T_VARIABLE, '/* test function call 8 */' ), - array( '/* test inside function pointer 9 */', \T_VARIABLE, '/* test function call 9 */' ), - array( '/* test inside function pointer 10 */', \T_VARIABLE, '/* test function call 10 */' ), - array( '/* test inside function pointer 11 */', \T_VARIABLE, '/* test function call 11 */' ), - array( '/* test inside function pointer 12 */', \T_VARIABLE, '/* test function call 12 */' ), + public function testIsInFunctionCallShouldReturnFalseWhenEmptyValidFunctions() { + $insideFunctionPtr = $this->getTargetToken( '/* testLowercaseNameInsideCall */', \T_WHITESPACE ); + $result = ContextHelper::is_in_function_call( + self::$phpcsFile, + $insideFunctionPtr, + array() ); + + $this->assertFalse( $result ); } /** - * Test is_in_function_call() returns pointer to function name if given token is inside a - * function call when `$allow_nested` is set to true. - * - * @dataProvider dataIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue + * Helper method to test is_in_function_call() with specific parameters. * - * @param string $insideFunctionCommentString The comment which prefaces the target token inside a function in the test file. - * @param int|string $insideFunctionTokenType The token type to search for. - * @param string $functionCallCommentString The comment which prefaces the function call token in the test file. + * @param string $testMarker The comment which prefaces the target token. + * @param int|string $tokenType The token type to search for. + * @param array $expectedResults Which parameter combinations should match. + * @param string|null $expectedFunctionMarker The comment for the expected function (if match expected). + * @param string $expectedKey Which key in expectedResults to check. * * @return void */ - public function testIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue( $insideFunctionCommentString, $insideFunctionTokenType, $functionCallCommentString ) { - $insideFunctionPtr = $this->getTargetToken( $insideFunctionCommentString, $insideFunctionTokenType ); - $functionNamePtr = $this->getTargetToken( $functionCallCommentString, Collections::nameTokens() ); - $result = ContextHelper::is_in_function_call( self::$phpcsFile, $insideFunctionPtr, array( 'my_function' => true ), true, true ); - $this->assertSame( $result, $functionNamePtr ); + private function runIsInFunctionCallTest( + $testMarker, + $tokenType, + $expectedResults, + $expectedFunctionMarker, + $expectedKey + ) { + $globalFunction = self::PARAMETER_MAP[ $expectedKey ]['global_function']; + $allowNested = self::PARAMETER_MAP[ $expectedKey ]['allow_nested']; + + $insideFunctionPtr = $this->getTargetToken( $testMarker, $tokenType ); + $result = ContextHelper::is_in_function_call( + self::$phpcsFile, + $insideFunctionPtr, + array( + 'valid_function1' => true, + 'valid_function2' => true, + ), + $globalFunction, + $allowNested + ); + + $expected = $expectedResults[ $expectedKey ] + ? $this->getTargetToken( $expectedFunctionMarker, \T_STRING ) + : false; + + $this->assertSame( $expected, $result, "Failed for: $testMarker with $expectedKey" ); } /** - * Data provider. + * Data provider for all is_in_function_call() tests. * - * @return array - * @see testIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue() + * @return array>> */ - public static function dataIsInFunctionCallShouldReturnFunctionPointerWhenAllowNestedIsTrue() { + public static function dataIsInFunctionCall() { return array( - array( '/* test inside function pointer 13 */', \T_VARIABLE, '/* test function call 13 */' ), - array( '/* test inside function pointer 14 */', \T_VARIABLE, '/* test function call 14 */' ), - array( '/* test inside function pointer 15 */', \T_VARIABLE, '/* test function call 15 */' ), + // Cases that should never match (regardless of parameters). + 'plain_assignment' => array( + 'testMarker' => '/* testPlainAssignment */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'expectedResults' => self::EXPECT_NO_MATCH, + ), + 'different_function' => array( + 'testMarker' => '/* testDifferentFunction */', + 'tokenType' => \T_LNUMBER, + 'expectedResults' => self::EXPECT_NO_MATCH, + ), + 'inside_closure' => array( + 'testMarker' => '/* testInsideClosure */', + 'tokenType' => \T_VARIABLE, + 'expectedResults' => self::EXPECT_NO_MATCH, + ), + 'variable_function' => array( + 'testMarker' => '/* testVariableFunction */', + 'tokenType' => \T_VARIABLE, + 'expectedResults' => self::EXPECT_NO_MATCH, + ), + 'if_condition' => array( + 'testMarker' => '/* testIfCondition */', + 'tokenType' => \T_TRUE, + 'expectedResults' => self::EXPECT_NO_MATCH, + ), + + // Cases that should always match (regardless of parameters). + 'lowercase_name' => array( + 'testMarker' => '/* testLowercaseNameInsideCall */', + 'tokenType' => \T_WHITESPACE, + 'expectedResults' => self::EXPECT_ALWAYS_MATCH, + 'expectedFunctionMarker' => '/* testLowercaseName */', + ), + 'uppercase_name' => array( + 'testMarker' => '/* testUppercaseNameInsideCall */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'expectedResults' => self::EXPECT_ALWAYS_MATCH, + 'expectedFunctionMarker' => '/* testUppercaseName */', + ), + 'fully_qualified' => array( + 'testMarker' => '/* testFullyQualifiedInsideCall */', + 'tokenType' => \T_LNUMBER, + 'expectedResults' => self::EXPECT_ALWAYS_MATCH, + 'expectedFunctionMarker' => '/* testFullyQualified */', + ), + + // Cases that match only when `$global_function` is `false`. + 'namespaced_function' => array( + 'testMarker' => '/* testNamespacedFunctionInsideCall */', + 'tokenType' => \T_STRING, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testNamespacedFunction */', + ), + 'fully_qualified_namespaced_function' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedFunctionInsideCall */', + 'tokenType' => \T_NULL, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testFullyQualifiedNamespacedFunction */', + ), + 'namespace_relative_function' => array( + 'testMarker' => '/* testNamespaceRelativeFunctionInsideCall */', + 'tokenType' => \T_DNUMBER, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testNamespaceRelativeFunction */', + ), + 'static_method' => array( + 'testMarker' => '/* testStaticMethodInsideCall */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testStaticMethod */', + ), + 'object_method' => array( + 'testMarker' => '/* testObjectMethodInsideCall */', + 'tokenType' => \T_ARRAY, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testObjectMethod */', + ), + 'nullsafe_object_method' => array( + 'testMarker' => '/* testNullsafeObjectMethodInsideCall */', + 'tokenType' => \T_OPEN_SHORT_ARRAY, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testNullsafeObjectMethod */', + ), + + // Cases that match only when `$allow_nested` is `true`. + 'nested_outer' => array( + 'testMarker' => '/* testNestedOuterInsideCall */', + 'tokenType' => \T_CONSTANT_ENCAPSED_STRING, + 'expectedResults' => self::EXPECT_NESTED_ONLY, + 'expectedFunctionMarker' => '/* testNestedOuter */', + ), + 'nested_inner' => array( + 'testMarker' => '/* testNestedInnerInsideCall */', + 'tokenType' => \T_TRUE, + 'expectedResults' => self::EXPECT_ALWAYS_MATCH, + 'expectedFunctionMarker' => '/* testNestedInner */', + ), + 'nested_multiple_levels' => array( + 'testMarker' => '/* testNestedMultipleLevelsInsideCall */', + 'tokenType' => \T_LNUMBER, + 'expectedResults' => self::EXPECT_NESTED_ONLY, + 'expectedFunctionMarker' => '/* testNestedMultipleLevels */', + ), + 'nested_both_namespaced_outer' => array( + 'testMarker' => '/* testNestedBothNamespacedOuterInsideCall */', + 'tokenType' => \T_STRING_CONCAT, + 'expectedResults' => self::EXPECT_NON_GLOBAL_NESTED_ONLY, + 'expectedFunctionMarker' => '/* testNestedBothNamespacedOuter */', + ), + 'complex_parameters_always_match' => array( + 'testMarker' => '/* testComplexParametersAlwaysMatchInsideCall */', + 'tokenType' => \T_VARIABLE, + 'expectedResults' => self::EXPECT_ALWAYS_MATCH, + 'expectedFunctionMarker' => '/* testComplexParametersAlwaysMatch */', + ), + 'complex_parameters_nested_only' => array( + 'testMarker' => '/* testComplexParametersNestedOnlyInsideCall */', + 'tokenType' => \T_VARIABLE, + 'expectedResults' => self::EXPECT_NESTED_ONLY, + 'expectedFunctionMarker' => '/* testComplexParametersNestedOnly */', + ), + 'complex_parameters_non_global' => array( + 'testMarker' => '/* testComplexParametersNonGlobalInsideCall */', + 'tokenType' => \T_TRUE, + 'expectedResults' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedFunctionMarker' => '/* testComplexParametersNonGlobal */', + ), + 'complex_parameters_non_global_nested' => array( + 'testMarker' => '/* testComplexParametersNonGlobalNestedInsideCall */', + 'tokenType' => \T_LNUMBER, + 'expectedResults' => self::EXPECT_NON_GLOBAL_NESTED_ONLY, + 'expectedFunctionMarker' => '/* testComplexParametersNonGlobalNested */', + ), ); } } From 4441f6e1fbed3c046848f8334589faf0f36fe46f Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 11 Nov 2025 09:26:57 -0300 Subject: [PATCH 6/7] Revert "Remove is_in_function_call() tests from NonceVerificationUnitTest.1.inc" This reverts commit a5236761e6abd4846c577ed72fe7cd63fef4bc24. --- .../Security/NonceVerificationUnitTest.1.inc | 22 +++++++++---------- .../Security/NonceVerificationUnitTest.php | 2 ++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc index f1f7ff385e..aee51ae39a 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc @@ -291,18 +291,18 @@ function function_containing_nested_closure() { }; } +// Tests specifically for the ContextHelper::is_in_function_call(). +function disallow_custom_unslash_before_noncecheck_via_method() { + $var = MyClass::stripslashes_from_strings_only( $_POST['foo'] ); // Bad. + wp_verify_nonce( $var ); + echo $var; +} - - - - - - - - - - - +function disallow_custom_unslash_before_noncecheck_via_namespaced_function() { + $var = MyNamespace\stripslashes_from_strings_only( $_POST['foo'] ); // Bad. + wp_verify_nonce( $var ); + echo $var; +} // Tests specifically for the ContextHelper::is_in_isset_or_empty(). function allow_in_array_key_exists_before_noncecheck() { diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.php b/WordPress/Tests/Security/NonceVerificationUnitTest.php index 97bf652750..aa5e832e83 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.php +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.php @@ -59,6 +59,8 @@ public function getErrorList( $testFile = '' ) { 192 => 1, 242 => 1, 259 => 1, + 296 => 1, + 302 => 1, 325 => 1, 329 => 1, 337 => 1, From 3fc573626a07070d561b8c0327d0c0857c14918d Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 11 Nov 2025 09:28:29 -0300 Subject: [PATCH 7/7] Update NonceVerificationUnitTest.1.inc now that there are dedicated tests for the is_in_function_call() method - Remove the @covers is_in_function_call() tag - Remove code comment. The tests are still relevant for the sniff, but are not anymore specifically to test is_in_function_call(). --- WordPress/Tests/Security/NonceVerificationUnitTest.1.inc | 2 +- WordPress/Tests/Security/NonceVerificationUnitTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc index aee51ae39a..eaab2af8ac 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.1.inc @@ -291,7 +291,7 @@ function function_containing_nested_closure() { }; } -// Tests specifically for the ContextHelper::is_in_function_call(). + function disallow_custom_unslash_before_noncecheck_via_method() { $var = MyClass::stripslashes_from_strings_only( $_POST['foo'] ); // Bad. wp_verify_nonce( $var ); diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.php b/WordPress/Tests/Security/NonceVerificationUnitTest.php index aa5e832e83..11866be485 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.php +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.php @@ -18,7 +18,6 @@ * @since 0.13.0 Class name changed: this class is now namespaced. * @since 1.0.0 This sniff has been moved from the `CSRF` category to the `Security` category. * - * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_function_call * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_type_test * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_isset_or_empty * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_in_array_comparison