From ab1167eafe062d9169c15c66307b32871f026a54 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 14 Aug 2025 15:10:26 -0300 Subject: [PATCH 1/3] 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/3] 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/3] 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,