From c84f4694d58bafc898eb37479added37ec1a3361 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 6 Jul 2026 16:57:30 -0300 Subject: [PATCH 01/48] PR 2708 DB/PreparedSQL: add tests for namespaced names --- WordPress/Tests/DB/PreparedSQLUnitTest.1.inc | 46 +++++++++++++++++++- WordPress/Tests/DB/PreparedSQLUnitTest.php | 12 +++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc index ac97d71ce8..3ef695fbbf 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc @@ -33,7 +33,7 @@ $wpdb->query( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . esc_sql( $f $wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . ABSINT( $foo ) . ";" ); // Ok. // Test multi-line strings. -$all_post_meta = $wpdb->get_results( $wpdb->prepare( sprintf( +$all_post_meta = $wpdb->get_results( $wpdb->prepare( \SPRINTF( 'SELECT `post_id`, `meta_value` FROM `%s` WHERE `meta_key` = "sort_order" @@ -45,7 +45,7 @@ $all_post_meta = $wpdb->get_results( $wpdb->prepare( sprintf( $wpdb->query( " SELECT * FROM $wpdb->posts - WHERE post_title LIKE '" . esc_sql( $foo ) . "';" + WHERE post_title LIKE '" . \esc_SQL( $foo ) . "';" ); // Ok. $wpdb->query( $wpdb->prepare( " @@ -157,3 +157,45 @@ MyNamespace\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" \MyNamespace\WPDB::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. namespace\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. The sniff should start flagging this once it can resolve relative namespaces as this test file is not namespaced. namespace\Sub\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE '" . foo() . "';" ); // Ok. + +/* + * Safeguard correct handling of all types of namespaced calls to PreparedSQLSniff::$SQLEscapingFunctions. + * + * Note: The sniff currently has a limitation in how it identifies and counts errors for namespaced function calls that + * match function names in $SQLEscapingFunctions, $SQLAutoEscapedFunctions, or + * FormattingFunctionsHelper::$formattingFunctions. When it encounters such a call, it treats the function name as if it + * were a global function call and skips checking the contents. For example, `MyNamespace\absint( $foo )` should trigger + * two errors (one for MyNamespace\absint, one for $foo), but currently only triggers one error for "MyNamespace" + * because the sniff incorrectly treats "absint" as a valid global escaping function and skips its contents. + * Additionally, multi-level namespace calls like `namespace\Sub\count( $foo )` generate multiple errors (one for + * "namespace", one for "Sub") instead of recognizing it as a single namespaced function call. This will be easier to + * fix once only PHPCS 4 is supported. Reported in https://github.com/WordPress/WordPress-Coding-Standards/issues/2648. + */ +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \absint( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . MyNamespace\esc_sql( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \MyNamespace\intval( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . namespace\floatval( $foo ) ); // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . namespace\Sub\like_escape( $foo ) ); + +/* + * Safeguard correct handling of all types of namespaced calls to PreparedSQLSniff::$SQLAutoEscapedFunctions. + * + * Note: See the comment above the $SQLEscapingFunctions tests for details about the sniff's current limitations. + */ +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \count( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \Count( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . MyNamespace\count( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \MyNamespace\count( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . namespace\count( $foo ) ); // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . namespace\Sub\count( $foo ) ); + +/* + * Safeguard correct handling of all types of namespaced calls to FormattingFunctionsHelper::$formattingFunctions. + * + * Note: See the comment above the $SQLEscapingFunctions tests for details about the sniff's current limitations. + */ +$wpdb->get_results( \sprintf( "SELECT * FROM $wpdb->posts WHERE ID = %s", intval( $id ) ) ); +$wpdb->get_results( MyNamespace\wp_sprintf( "SELECT * FROM $wpdb->posts WHERE ID = %s", intval( $id ) ) ); +$wpdb->get_results( \MyNamespace\sprintf( "SELECT * FROM $wpdb->posts WHERE ID = %s", intval( $id ) ) ); +$wpdb->get_results( namespace\wp_sprintf( "SELECT * FROM $wpdb->posts WHERE ID = %s", intval( $id ) ) ); // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. +$wpdb->get_results( namespace\Sub\sprintf( "SELECT * FROM $wpdb->posts WHERE ID = %s", intval( $id ) ) ); diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.php b/WordPress/Tests/DB/PreparedSQLUnitTest.php index 8d887dd044..8439bec565 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.php @@ -68,6 +68,18 @@ public function getErrorList( $testFile = '' ) { 132 => 2, 154 => 1, 155 => 1, + 175 => 1, + 176 => 1, + 177 => 1, + 178 => 2, + 187 => 1, + 188 => 1, + 189 => 1, + 190 => 2, + 198 => 1, + 199 => 1, + 200 => 1, + 201 => 2, ); case 'PreparedSQLUnitTest.2.inc': From 22a81d21cd6ddd365763c62b67025d3032ab508c Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 24 Mar 2026 14:59:40 +0000 Subject: [PATCH 02/48] PR 2714 DB/PreparedSQLPlaceholders: add namespaced tests This commit adds tests to the `WordPress.DB.PreparedSQLPlaceholders` sniff covering the different forms of namespaced calls (partially qualified, fully qualified, and namespace-relative using the `namespace` keyword) to the `sprintf()`, `implode()`, and `array_fill()` functions the sniff analyzes, both when nested inside `sprintf()` and in direct string concatenation. It also adds tests for method calls named `sprintf()`, `implode()`, or `array_fill()`, which should not be treated as calls to the global functions. --- .../DB/PreparedSQLPlaceholdersUnitTest.inc | 289 +++++++++++++++++- .../DB/PreparedSQLPlaceholdersUnitTest.php | 32 ++ 2 files changed, 315 insertions(+), 6 deletions(-) diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc index 791df5a509..d9f586e3fa 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc @@ -14,7 +14,7 @@ $sql = $wpdb->prepare( 'SELECT * FROM `table` WHERE id = ' . $id ); // OK - this $sql = $wpdb->prepare( "SELECT * FROM `table` WHERE id = $id" ); // OK - this will be handled by the PreparedSQL sniff. $sql = $wpdb->prepare( "SELECT * FROM `table` WHERE id = {$id['some%sing']}" ); // OK - this will be handled by the PreparedSQL sniff. $sql = $wpdb?->prepare( 'SELECT * FROM ' . $wpdb->users ); // Warning. -$sql = $wpdb->prepare( "SELECT * FROM `{$wpdb->users}`" ); // Warning. +$sql = $wpdb->PREPARE( "SELECT * FROM `{$wpdb->users}`" ); // Warning. $sql = $wpdb->prepare( "SELECT * FROM `{$wpdb->users}` WHERE id = $id" ); // OK - this will be handled by the PreparedSQL sniff. /* @@ -80,7 +80,7 @@ $where = $wpdb->prepare( ); // OK. $where = $wpdb->prepare( - sprintf( + \sprintf( "{$wpdb->posts}.post_type IN (%s) AND {$wpdb->posts}.post_status IN (%s)", implode( ',', array_fill( 0, count($post_types), '%s' ), ), @@ -99,7 +99,7 @@ $where = $wpdb->prepare( ); // OK. $query = $wpdb->prepare( - sprintf( + Sprintf( 'SELECT COUNT(ID) FROM `%s` WHERE ID IN (%s) @@ -124,7 +124,7 @@ $results = $wpdb->get_results( ); // OK. $query = $wpdb->prepare( - sprintf( + \SPRINTF( 'SELECT COUNT(ID) FROM `%s` WHERE ID in (%s) @@ -382,7 +382,7 @@ $where = $wpdb->prepare( $where = $wpdb->prepare( sprintf( "{$wpdb->posts}.post_type IN (%s)", - \implode( ',', array_fill( 0, count($post_types), '%s' ) ) + \ImplodE( ',', array_fill( 0, count($post_types), '%s' ) ) ), $post_types ); // OK. @@ -397,7 +397,7 @@ $where = $wpdb->prepare( $where = $wpdb->prepare( "{$wpdb->posts}.post_type IN (" - . implode( ',', \array_fill( 0, count($post_types), '%s' ) ) + . implode( ',', \Array_Fill( 0, count($post_types), '%s' ) ) . ") AND {$wpdb->posts}.post_status IN (" . implode( ',', \array_fill( 0, count($post_statusses), '%s' ) ) . ')', @@ -532,3 +532,280 @@ $sql = MyNamespace\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND $sql = \MyNamespace\WPDB::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK. $sql = namespace\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // Ok. The sniff should start flagging this once it can resolve relative namespaces as this test file is not namespaced. $sql = namespace\Sub\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK. + +/* + * Safeguard correct handling of namespaced implode() calls when nested as a parameter to sprintf() (except fully + * qualified calls to the global implode() function, which is already handled above). + */ +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + \MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced. + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + namespace\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + namespace\Sub\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); + +/* + * Safeguard correct handling of namespaced array_fill() calls when nested inside sprintf() (except fully qualified + * calls to the global array_fill() function, which is already handled above). + */ +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', MyNamespace\array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', \MyNamespace\array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced. + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', namespace\array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', namespace\Sub\array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); + +/* + * Safeguard correct handling of namespaced implode() calls in direct string concatenation (when NOT nested inside + * sprintf()). Fully qualified calls to the global implode() are already handled above. + */ +$where = $wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . \MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced. + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . namespace\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . namespace\Sub\implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); + +/* + * Safeguard correct handling of namespaced array_fill() calls in direct string concatenation (when NOT nested inside + * sprintf()). Fully qualified calls to the global array_fill() are already handled above. + */ +$where = $wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . implode( ',', MyNamespace\array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . implode( ',', \MyNamespace\array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced. + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . implode( ',', namespace\array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN (" + . implode( ',', namespace\Sub\array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); + +/* + * Safeguard correct handling of all types of namespaced calls to sprintf() except fully qualified calls to the + * global sprintf() function, which is already handled above. + * + * The calls below are currently false negatives. The sniff incorrectly treats namespaced sprintf() calls as + * calls to the global sprintf() function and does not flag them. + * See: https://github.com/WordPress/WordPress-Coding-Standards/issues/2720 + */ +$where = $wpdb->prepare( + MyNamespace\sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + \MyNamespace\sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + namespace\sprintf( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced. + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + namespace\Sub\sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); + +/* + * Safeguard correct handling of method calls to methods named sprintf(), implode(), or array_fill(). + * These should NOT be analyzed as global function calls. + */ + +// sprintf() method calls. The sprintf() calls below are currently false negatives. The sniff incorrectly treats method +// calls with the same name as global functions as calls to the global functions and does not flag them. +// See: https://github.com/WordPress/WordPress-Coding-Standards/issues/2720 +$where = $wpdb->prepare( + $obj->sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + $obj?->sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + MyClass::sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); + +// implode() method calls nested inside sprintf(). +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + $obj->implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + $obj?->implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + MyClass::implode( ',', array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); + +// implode() method calls in direct string concatenation. +$where = $wpdb->prepare( + "post_status = %s AND post_type IN (" + . $obj->implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "post_status = %s AND post_type IN (" + . $obj?->implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "post_status = %s AND post_type IN (" + . MyClass::implode( ',', array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); + +// array_fill() method calls nested inside implode() which is nested inside sprintf(). +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', $obj->array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', $obj?->array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); +$where = $wpdb->prepare( + sprintf( + "{$wpdb->posts}.post_type IN (%s)", + implode( ',', MyClass::array_fill( 0, count($post_types), '%s' ) ) + ), + $post_types +); + +// array_fill() method calls nested inside implode() in direct string concatenation. +$where = $wpdb->prepare( + "post_status = %s AND post_type IN (" + . implode( ',', $obj->array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "post_status = %s AND post_type IN (" + . implode( ',', $obj?->array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); +$where = $wpdb->prepare( + "post_status = %s AND post_type IN (" + . implode( ',', MyClass::array_fill( 0, count($post_types), '%s' ) ) + . ')', + 'publish' +); diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php index 916567cf3e..e4691350dc 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php @@ -165,6 +165,38 @@ public function getWarningList() { 482 => 1, 490 => 1, 498 => 1, + + // Namespaced sprintf/implode/array_fill calls. + 540 => 1, + 547 => 1, + 554 => 1, + 561 => 1, + 573 => 1, + 580 => 1, + 587 => 1, + 594 => 1, + 606 => 1, + 612 => 1, + 618 => 1, + 624 => 1, + 635 => 1, + 641 => 1, + 647 => 1, + 653 => 1, + + // Method sprintf/implode/array_fill calls. + 728 => 1, + 735 => 1, + 742 => 1, + 751 => 1, + 757 => 1, + 763 => 1, + 771 => 1, + 778 => 1, + 785 => 1, + 794 => 1, + 800 => 1, + 806 => 1, ); } } From 54670c6618f2319dd2537bcb19291f7dd4df476b Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 16:13:03 -0300 Subject: [PATCH 03/48] PR 2716 PrintingFunctionsTrait::is_printing_function(): add tests --- .../IsPrintingFunctionUnitTest.php | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php diff --git a/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php b/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php new file mode 100644 index 0000000000..b58bfc9cb2 --- /dev/null +++ b/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php @@ -0,0 +1,84 @@ +assertSame( + $expectedResult, + self::$testClass->is_printing_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsPrintingFunction() + * + * @return array> + */ + public static function dataIsPrintingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'printf', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'vPrInTf', + 'expectedResult' => true, + ), + 'not_a_printing_function' => array( + 'functionName' => 'echo', + 'expectedResult' => false, + ), + ); + } +} From 7eb2d07c8e69992a757a84802f9a336fb7f13b9f Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 18 Aug 2025 15:00:58 -0300 Subject: [PATCH 04/48] PR 2728 ContextHelper::is_token_namespaced(): add basic tests --- .../IsTokenNamespacedUnitTest.inc | 17 ++++ .../IsTokenNamespacedUnitTest.php | 95 +++++++++++++++++++ .../Tests/WP/DiscouragedFunctionsUnitTest.php | 1 - 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.php diff --git a/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.inc new file mode 100644 index 0000000000..40262226de --- /dev/null +++ b/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.inc @@ -0,0 +1,17 @@ +assertFalse( ContextHelper::is_token_namespaced( self::$phpcsFile, -1 ) ); + } + + /** + * Test is_token_namespaced(). + * + * @dataProvider dataIsTokenNamespaced + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * @param string $tokenContent The token content to use for the target token. + * + * @return void + */ + public function testIsTokenNamespaced( $testMarker, $expectedResult, $tokenContent ) { + $stackPtr = $this->getTargetToken( $testMarker, \T_STRING, $tokenContent ); + $result = ContextHelper::is_token_namespaced( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsTokenNamespaced() + * + * @return array> + */ + public static function dataIsTokenNamespaced() { + return array( + // Cases that should return false. + 'unqualified' => array( + 'testMarker' => '/* testUnqualified */', + 'expectedResult' => false, + 'tokenContent' => 'my_function', + ), + 'fully_qualified' => array( + 'testMarker' => '/* testFullyQualified */', + 'expectedResult' => false, + 'tokenContent' => 'MY_CONSTANT', + ), + + // Cases that should return true. + 'partially_qualified' => array( + 'testMarker' => '/* testPartiallyQualified */', + 'expectedResult' => true, + 'tokenContent' => 'my_function', + ), + 'fully_qualified_namespaced' => array( + 'testMarker' => '/* testFullyQualifiedNamespaced */', + 'expectedResult' => true, + 'tokenContent' => 'MyClass', + ), + 'namespace_relative' => array( + 'testMarker' => '/* testNamespaceRelative */', + 'expectedResult' => true, + 'tokenContent' => 'MY_CONSTANT', + ), + 'namespace_relative_sub' => array( + 'testMarker' => '/* testNamespaceRelativeSub */', + 'expectedResult' => true, + 'tokenContent' => 'my_function', + ), + ); + } +} diff --git a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php index 67031257c3..1bfa8533b7 100644 --- a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php +++ b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php @@ -19,7 +19,6 @@ * * @covers \WordPressCS\WordPress\AbstractFunctionRestrictionsSniff * @covers \WordPressCS\WordPress\Helpers\ContextHelper::has_object_operator_before - * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_token_namespaced * @covers \WordPressCS\WordPress\Sniffs\WP\DiscouragedFunctionsSniff */ final class DiscouragedFunctionsUnitTest extends AbstractSniffUnitTest { From 3090f20ce3bafe5be5b52bdf2b4e7cff9c68d92f Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 5 Nov 2025 09:46:21 -0300 Subject: [PATCH 05/48] PR 2731 ValidationHelper::is_validated(): add basic tests --- ...structNotFollowedByParenthesisUnitTest.inc | 4 + ...edConstructUnclosedParenthesisUnitTest.inc | 4 + ...onValidationNotSeenAtFileScopeUnitTest.inc | 8 + ...edInConditionOnlyNoParenthesisUnitTest.inc | 7 + ...terValidationNotSeenInFunctionUnitTest.inc | 7 + .../ValidationHelper/IsValidatedUnitTest.inc | 211 +++++++++ .../ValidationHelper/IsValidatedUnitTest.php | 439 ++++++++++++++++++ ...sValidatedValidatedAtFileScopeUnitTest.inc | 6 + .../ValidatedSanitizedInputUnitTest.php | 1 - 9 files changed, 686 insertions(+), 1 deletion(-) create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedConstructNotFollowedByParenthesisUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedConstructUnclosedParenthesisUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedFunctionValidationNotSeenAtFileScopeUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedInConditionOnlyNoParenthesisUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedOuterValidationNotSeenInFunctionUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedUnitTest.php create mode 100644 WordPress/Tests/Helpers/ValidationHelper/IsValidatedValidatedAtFileScopeUnitTest.inc diff --git a/WordPress/Tests/Helpers/ValidationHelper/IsValidatedConstructNotFollowedByParenthesisUnitTest.inc b/WordPress/Tests/Helpers/ValidationHelper/IsValidatedConstructNotFollowedByParenthesisUnitTest.inc new file mode 100644 index 0000000000..6d2d4f195d --- /dev/null +++ b/WordPress/Tests/Helpers/ValidationHelper/IsValidatedConstructNotFollowedByParenthesisUnitTest.inc @@ -0,0 +1,4 @@ + isset( $_POST['key'] ); + echo /* testArrowFunctionValidationNotCounted */ $_POST['key']; +} + +function test_construct_wrong_variable_or_key() { + if ( isset( $_GET['key'] ) || isset( $_POST['other'] ) ) { + echo /* testConstructWrongVariableOrKey */ $_POST['key']; + } +} + +function test_construct_param_key_mismatch() { + if ( isset( $_POST['key'] ) ) { + echo /* testConstructParamKeyMismatch */ $_POST['key']; + } +} + +function test_function_name_used_as_constant() { + echo ARRAY_KEY_EXISTS; + echo /* testFunctionNameUsedAsConstant */ $_POST['key']; +} + +function test_function_call_in_attribute() { + #[array_key_exists('key', $_POST)] + echo /* testFunctionCallInAttribute */ $_POST['key']; +} + +function test_function_call_non_global() { + if ( + $obj->array_key_exists( 'key', $_POST ) + || $obj?->key_exists( 'key', $_POST ) + || MyClass::array_key_exists( 'key', $_POST ) + ) { + echo /* testFunctionCallNonGlobal */ $_POST['key']; + } +} + +function test_function_call_namespaced() { + if ( + MyNamespace\array_key_exists( 'key', $_POST ) + || \MyNamespace\key_exists( 'key', $_POST ) + || namespace\array_key_exists( 'key', $_POST ) // This should be considered validated in the future once the method is able to resolve relative namespaces. + || namespace\Sub\key_exists( 'key', $_POST ) + ) { + echo /* testFunctionCallNamespaced */ $_POST['key']; + } +} + +function test_function_call_missing_parameters() { + if ( key_exists() ) { + echo /* testFunctionCallMissingParameters */ $_POST['key']; + } +} + +function test_function_call_wrong_array_param() { + if ( + key_exists( 'key', SOME_CONSTANT ) + || array_key_exists( 'key', $_GET ) + ) { + echo /* testFunctionCallWrongArrayParam */ $_POST['key']; + } +} + +function test_function_call_mismatched_key() { + if ( array_key_exists( 'other', $_POST ) ) { + echo /* testFunctionCallMismatchedKey */ $_POST['key']; + } +} + +function test_coalesce_no_match() { + $a = 'something' ?? 'default'; + $_GET['key'] ??= 'default'; + $_POST['other'] ??= 'default'; + echo /* testCoalesceNoMatch */ $_POST['key']; +} + +/* + * The below should be considered validated. + */ + +function test_validated_with_isset() { + if ( isset( $_POST["key"] ) ) { + echo /* testValidatedWithIsset */ $_POST['key']; + } +} + +function test_validated_with_empty() { + if ( empty( $_POST['key'] ) ) { + echo /* testValidatedWithEmpty */ $_POST['key']; + } +} + +$closure = function() { + if ( empty( $_POST['key'] ) ) { + echo /* testValidatedInClosure */ $_POST['key']; + } +}; + +function test_function_call() { + if ( array_key_exists( 'key', $_POST ) ) { + echo /* testFunctionCall */ $_POST['key']; + } +} + +function test_function_call_mixed_case() { + if ( Key_Exists( 'key', $_POST ) ) { + echo /* testFunctionCallMixedCase */ $_POST['key']; + } +} + +function test_function_call_fully_qualified() { + if ( \array_key_exists( 'key', $_POST ) ) { + echo /* testFunctionCallFullyQualified */ $_POST['key']; + } +} + +function test_function_call_fully_qualified_uppercase() { + if ( \KEY_EXISTS( 'key', $_POST ) ) { + echo /* testFunctionCallFullyQualifiedUppercase */ $_POST['key']; + } +} + +function test_validated_with_null_coalesce() { + $_POST['key'] = $_POST['key'] ?? 'default'; + echo /* testValidatedWithNullCoalesce */ $_POST['key']; +} + +function test_validated_with_coalesce_equal() { + $_POST['key'] ??= 'default'; + echo /* testValidatedWithCoalesceEqual */ $_POST['key']; +} + +/* + * Test cases for `$in_condition_only` set to true. + */ + +function test_in_condition_only_use_inside_condition() { + if ( isset( $_POST['key'] ) ) { + echo /* testInConditionOnlyUseInsideCondition */ $_POST['key']; + } +} + +function test_in_condition_only_use_outside_condition() { + if ( empty( $_POST['key'] ) ) { + return; + } + echo /* testInConditionOnlyUseOutsideCondition */ $_POST['key']; +} + +/* + * Test cases for multi-level array key matching across validation paths. + */ + +function test_array_keys_construct() { + if ( isset( $_POST['key']['sub'] ) ) { + echo /* testArrayKeysConstruct */ $_POST['key']['sub']; + } +} + +function test_array_keys_function_call() { + if ( array_key_exists( 'sub', $_POST['key'] ) ) { + echo /* testArrayKeysFunctionCall */ $_POST['key']['sub']; + } +} + +function test_array_keys_function_call_key_param_mismatch() { + if ( array_key_exists( 'wrong_key', $_POST['key'] ) ) { + echo /* testArrayKeysFunctionCallKeyParamMismatch */ $_POST['key']['sub']; + } +} + +function test_array_keys_coalesce() { + $_POST['key']['sub'] = $_POST['key']['sub'] ?? 'default'; + echo /* testArrayKeysCoalesce */ $_POST['key']['sub']; +} diff --git a/WordPress/Tests/Helpers/ValidationHelper/IsValidatedUnitTest.php b/WordPress/Tests/Helpers/ValidationHelper/IsValidatedUnitTest.php new file mode 100644 index 0000000000..1169847f31 --- /dev/null +++ b/WordPress/Tests/Helpers/ValidationHelper/IsValidatedUnitTest.php @@ -0,0 +1,439 @@ +assertFalse( ValidationHelper::is_validated( self::$phpcsFile, -1 ) ); + } + + /** + * Test is_validated() handles live coding / parse error situations. + * + * @dataProvider dataIsValidatedLiveCoding + * + * @param string $testCaseFile The test case file to parse. + * + * @return void + */ + public function testIsValidatedLiveCoding( $testCaseFile ) { + $this->assertIsValidatedInFile( $testCaseFile, false ); + } + + /** + * Data provider. + * + * @see testIsValidatedLiveCoding() + * + * @return array> + */ + public static function dataIsValidatedLiveCoding() { + return array( + 'construct_not_followed_by_parenthesis' => array( + 'testCaseFile' => 'IsValidatedConstructNotFollowedByParenthesisUnitTest.inc', + ), + 'construct_unclosed_parenthesis' => array( + 'testCaseFile' => 'IsValidatedConstructUnclosedParenthesisUnitTest.inc', + ), + ); + } + + /** + * Test is_validated() correctly respects scope boundaries between file scope and function scope. + * + * @dataProvider dataIsValidatedScopeBoundaries + * + * @param string $testCaseFile The test case file to parse. + * @param bool $expectedResult The expected return value. + * + * @return void + */ + public function testIsValidatedScopeBoundaries( $testCaseFile, $expectedResult ) { + $this->assertIsValidatedInFile( $testCaseFile, $expectedResult ); + } + + /** + * Data provider. + * + * @see testIsValidatedScopeBoundaries() + * + * @return array> + */ + public static function dataIsValidatedScopeBoundaries() { + return array( + 'function_validation_not_seen_at_file_scope' => array( + 'testCaseFile' => 'IsValidatedFunctionValidationNotSeenAtFileScopeUnitTest.inc', + 'expectedResult' => false, + ), + 'outer_validation_not_seen_in_function' => array( + 'testCaseFile' => 'IsValidatedOuterValidationNotSeenInFunctionUnitTest.inc', + 'expectedResult' => false, + ), + 'validated_at_file_scope' => array( + 'testCaseFile' => 'IsValidatedValidatedAtFileScopeUnitTest.inc', + 'expectedResult' => true, + ), + ); + } + + /** + * Test is_validated() with default parameter for $in_condition_only and a single $array_keys entry. + * + * @dataProvider dataIsValidated + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * + * @return void + */ + public function testIsValidated( $testMarker, $expectedResult ) { + $stackPtr = $this->getTargetToken( $testMarker, \T_VARIABLE ); + $result = ValidationHelper::is_validated( self::$phpcsFile, $stackPtr, array( 'key' ) ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsValidated() + * + * @return array> + */ + public static function dataIsValidated() { + return array( + // Cases that should return false. + 'not_validated' => array( + 'testMarker' => '/* testNotValidated */', + 'expectedResult' => false, + ), + 'outer_function_validation_not_counted' => array( + 'testMarker' => '/* testOuterFunctionValidationNotCounted */', + 'expectedResult' => false, + ), + 'closed_scope_validation_not_counted' => array( + 'testMarker' => '/* testClosedScopeValidationNotCounted */', + 'expectedResult' => false, + ), + 'arrow_function_validation_not_counted' => array( + 'testMarker' => '/* testArrowFunctionValidationNotCounted */', + 'expectedResult' => false, + ), + 'construct_wrong_variable_or_key' => array( + 'testMarker' => '/* testConstructWrongVariableOrKey */', + 'expectedResult' => false, + ), + 'function_name_used_as_constant' => array( + 'testMarker' => '/* testFunctionNameUsedAsConstant */', + 'expectedResult' => false, + ), + 'function_call_in_attribute' => array( + 'testMarker' => '/* testFunctionCallInAttribute */', + 'expectedResult' => false, + ), + 'function_call_non_global' => array( + 'testMarker' => '/* testFunctionCallNonGlobal */', + 'expectedResult' => false, + ), + 'function_call_namespaced' => array( + 'testMarker' => '/* testFunctionCallNamespaced */', + 'expectedResult' => false, + ), + 'function_call_missing_parameters' => array( + 'testMarker' => '/* testFunctionCallMissingParameters */', + 'expectedResult' => false, + ), + 'function_call_wrong_array_param' => array( + 'testMarker' => '/* testFunctionCallWrongArrayParam */', + 'expectedResult' => false, + ), + 'function_call_mismatched_key' => array( + 'testMarker' => '/* testFunctionCallMismatchedKey */', + 'expectedResult' => false, + ), + 'coalesce_no_match' => array( + 'testMarker' => '/* testCoalesceNoMatch */', + 'expectedResult' => false, + ), + + // Cases that should return true. + 'validated_with_isset' => array( + 'testMarker' => '/* testValidatedWithIsset */', + 'expectedResult' => true, + ), + 'validated_with_empty' => array( + 'testMarker' => '/* testValidatedWithEmpty */', + 'expectedResult' => true, + ), + 'validated_in_closure' => array( + 'testMarker' => '/* testValidatedInClosure */', + 'expectedResult' => true, + ), + 'function_call' => array( + 'testMarker' => '/* testFunctionCall */', + 'expectedResult' => true, + ), + 'function_call_mixed_case' => array( + 'testMarker' => '/* testFunctionCallMixedCase */', + 'expectedResult' => true, + ), + 'function_call_fully_qualified' => array( + 'testMarker' => '/* testFunctionCallFullyQualified */', + 'expectedResult' => true, + ), + 'function_call_fully_qualified_uppercase' => array( + 'testMarker' => '/* testFunctionCallFullyQualifiedUppercase */', + 'expectedResult' => true, + ), + 'validated_with_null_coalesce' => array( + 'testMarker' => '/* testValidatedWithNullCoalesce */', + 'expectedResult' => true, + ), + 'validated_with_coalesce_equal' => array( + 'testMarker' => '/* testValidatedWithCoalesceEqual */', + 'expectedResult' => true, + ), + ); + } + + /** + * Test is_validated() with $in_condition_only set to true returns false when the variable + * is not inside a condition or the condition has no parentheses. + * + * @dataProvider dataIsValidatedInConditionOnlyReturnsFalse + * + * @param string $testCaseFile The test case file to parse. + * + * @return void + */ + public function testIsValidatedInConditionOnlyReturnsFalse( $testCaseFile ) { + $this->assertIsValidatedInFile( $testCaseFile, false, true ); + } + + /** + * Data provider. + * + * @see testIsValidatedInConditionOnlyReturnsFalse() + * + * @return array> + */ + public static function dataIsValidatedInConditionOnlyReturnsFalse() { + return array( + 'no_condition' => array( + 'testCaseFile' => 'IsValidatedValidatedAtFileScopeUnitTest.inc', + ), + 'condition_without_parentheses' => array( + 'testCaseFile' => 'IsValidatedInConditionOnlyNoParenthesisUnitTest.inc', + ), + ); + } + + /** + * Test is_validated() with $in_condition_only set to true. + * + * @dataProvider dataIsValidatedInConditionOnly + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * + * @return void + */ + public function testIsValidatedInConditionOnly( $testMarker, $expectedResult ) { + $stackPtr = $this->getTargetToken( $testMarker, \T_VARIABLE ); + $result = ValidationHelper::is_validated( self::$phpcsFile, $stackPtr, array( 'key' ), true ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsValidatedInConditionOnly() + * + * @return array> + */ + public static function dataIsValidatedInConditionOnly() { + return array( + 'use_outside_condition' => array( + 'testMarker' => '/* testInConditionOnlyUseOutsideCondition */', + 'expectedResult' => false, + ), + 'use_inside_condition' => array( + 'testMarker' => '/* testInConditionOnlyUseInsideCondition */', + 'expectedResult' => true, + ), + ); + } + + /** + * Test is_validated() multi-level array key matching across validation paths. + * + * @dataProvider dataIsValidatedArrayKeys + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * @param array|string $array_keys The array keys to check for. + * + * @return void + */ + public function testIsValidatedArrayKeys( $testMarker, $expectedResult, $array_keys ) { + $stackPtr = $this->getTargetToken( $testMarker, \T_VARIABLE ); + $result = ValidationHelper::is_validated( self::$phpcsFile, $stackPtr, $array_keys ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsValidatedArrayKeys() + * + * @return array|bool|string>> + */ + public static function dataIsValidatedArrayKeys() { + return array( + 'construct_subset' => array( + 'testMarker' => '/* testArrayKeysConstruct */', + 'expectedResult' => true, + 'array_keys' => array( 'key' ), + ), + 'construct_exact' => array( + 'testMarker' => '/* testArrayKeysConstruct */', + 'expectedResult' => true, + 'array_keys' => array( 'key', 'sub' ), + ), + 'construct_superset' => array( + 'testMarker' => '/* testArrayKeysConstruct */', + 'expectedResult' => false, + 'array_keys' => array( 'key', 'sub', 'deeper' ), + ), + 'construct_wrong_order' => array( + 'testMarker' => '/* testArrayKeysConstruct */', + 'expectedResult' => false, + 'array_keys' => array( 'sub', 'key' ), + ), + 'construct_string_key_valid' => array( + 'testMarker' => '/* testArrayKeysConstruct */', + 'expectedResult' => true, + 'array_keys' => 'key', + ), + 'construct_string_key_invalid' => array( + 'testMarker' => '/* testArrayKeysConstruct */', + 'expectedResult' => false, + 'array_keys' => 'other', + ), + 'construct_key_mismatch' => array( + 'testMarker' => '/* testConstructParamKeyMismatch */', + 'expectedResult' => false, + 'array_keys' => array( 'other' ), + ), + 'construct_empty_array_keys' => array( + 'testMarker' => '/* testValidatedWithIsset */', + 'expectedResult' => true, + 'array_keys' => array(), + ), + 'function_call_all_in_array_param' => array( + 'testMarker' => '/* testArrayKeysFunctionCall */', + 'expectedResult' => true, + 'array_keys' => array( 'key' ), + ), + 'function_call_split_keys' => array( + 'testMarker' => '/* testArrayKeysFunctionCall */', + 'expectedResult' => true, + 'array_keys' => array( 'key', 'sub' ), + ), + 'function_call_superset' => array( + 'testMarker' => '/* testArrayKeysFunctionCall */', + 'expectedResult' => false, + 'array_keys' => array( 'key', 'sub', 'deeper' ), + ), + 'function_call_wrong_order' => array( + 'testMarker' => '/* testArrayKeysFunctionCall */', + 'expectedResult' => false, + 'array_keys' => array( 'sub', 'key' ), + ), + 'function_call_key_param_mismatch' => array( + 'testMarker' => '/* testArrayKeysFunctionCallKeyParamMismatch */', + 'expectedResult' => false, + 'array_keys' => array( 'key', 'sub' ), + ), + 'function_call_empty_array_keys' => array( + 'testMarker' => '/* testFunctionCallMixedCase */', + 'expectedResult' => true, + 'array_keys' => array(), + ), + 'coalesce_subset' => array( + 'testMarker' => '/* testArrayKeysCoalesce */', + 'expectedResult' => true, + 'array_keys' => array( 'key' ), + ), + 'coalesce_exact' => array( + 'testMarker' => '/* testArrayKeysCoalesce */', + 'expectedResult' => true, + 'array_keys' => array( 'key', 'sub' ), + ), + 'coalesce_superset' => array( + 'testMarker' => '/* testArrayKeysCoalesce */', + 'expectedResult' => false, + 'array_keys' => array( 'key', 'sub', 'deeper' ), + ), + 'coalesce_wrong_order' => array( + 'testMarker' => '/* testArrayKeysCoalesce */', + 'expectedResult' => false, + 'array_keys' => array( 'sub', 'key' ), + ), + ); + } + + /** + * Parse a separate .inc file and assert the result of is_validated(). + * + * Temporarily swaps self::$phpcsFile so that getTargetToken() works on the parsed file. + * + * @param string $testCaseFile The test case file to parse. + * @param bool $expectedResult The expected return value. + * @param bool $inConditionOnly What to pass as the $in_condition_only parameter. + * + * @return void + */ + private function assertIsValidatedInFile( $testCaseFile, $expectedResult, $inConditionOnly = false ) { + $originalFile = self::$phpcsFile; + self::$phpcsFile = self::parseFile( + __DIR__ . '/' . $testCaseFile, + $originalFile->ruleset, + $originalFile->config + ); + + $stackPtr = $this->getTargetToken( '/* testValidationTarget */', \T_VARIABLE ); + + $this->assertSame( + $expectedResult, + ValidationHelper::is_validated( self::$phpcsFile, $stackPtr, array( 'key' ), $inConditionOnly ) + ); + + self::$phpcsFile = $originalFile; + } +} diff --git a/WordPress/Tests/Helpers/ValidationHelper/IsValidatedValidatedAtFileScopeUnitTest.inc b/WordPress/Tests/Helpers/ValidationHelper/IsValidatedValidatedAtFileScopeUnitTest.inc new file mode 100644 index 0000000000..79b2ada2f1 --- /dev/null +++ b/WordPress/Tests/Helpers/ValidationHelper/IsValidatedValidatedAtFileScopeUnitTest.inc @@ -0,0 +1,6 @@ + Date: Fri, 15 Aug 2025 15:23:30 -0300 Subject: [PATCH 06/48] PR 2752 ConstantsHelper::is_use_of_global_constant(): add tests --- .../IsUseOfGlobalConstantUnitTest.inc | 134 ++++++++++ .../IsUseOfGlobalConstantUnitTest.php | 244 ++++++++++++++++++ .../Tests/Security/EscapeOutputUnitTest.php | 1 - 3 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.inc create mode 100644 WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php diff --git a/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.inc b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.inc new file mode 100644 index 0000000000..01ffb520c0 --- /dev/null +++ b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.inc @@ -0,0 +1,134 @@ +PHP_OS; + +/* testNullsafeObjectPropertyAccess */ +$obj?->PHP_OS; + +SomeClass::/* testClassConstantAccess */PHP_OS; + +class ScopeModifiersTest { + /* testPrecededByPublic */ + public PHP_OS $prop1; + + /* testPrecededByProtected */ + protected PHP_OS $prop2; + + /* testPrecededByPrivate */ + private PHP_OS $prop3; + + /* testPrecededByPublicSet */ + public public(set) PHP_OS $prop4; + + /* testPrecededByProtectedSet */ + public protected(set) PHP_OS $prop5; + + /* testPrecededByPrivateSet */ + public private(set) PHP_OS $prop6; +} + +/* testPartiallyQualifiedNamespacedConstant */ +MyNamespace\PHP_OS; + +/* testFullyQualifiedNamespacedConstant */ +\MyNamespace\PHP_OS; + +/* testNamespaceRelativeConstant */ +namespace\PHP_OS; // This should be considered use of a global constant in the future once the method is able to resolve relative namespaces. + +/* testNamespaceRelativeSubConstant */ +namespace\Sub\PHP_OS; + +class Foo { + /* testClassConstantDeclaration */ + const PHP_OS = 1; +} + +use const SomeNamespace\{/* testUseConstStatementGrouped */ PHP_OS, PHP_VERSION_ID}; + +use Foo\{Bar, /* testUseStatementGrouped */ PHP_OS}; + +/* + * The below should be recognized as use of a global constant. + */ + +/* testEchoStatement */ +echo PHP_OS; + +/* testEchoStatementFullyQualified */ +echo \PHP_OS; + +// Note: this is counterintuitive behavior, as declaring a constant is not exactly the use of a constant. However, this +// seems intentional considering the docblock of DiscouragedConstantsSniff states that it warns against both usage and +// re-declaration of discouraged WP constants. +/* testConstDeclaration */ +const my_const = 'something'; +const ABC = '123', + /* testConstDeclarationInList */ PHP_OS = 'something'; + +/* testUseConstStatement */ +use const PHP_OS as SSP; diff --git a/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php new file mode 100644 index 0000000000..3cb23ab044 --- /dev/null +++ b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php @@ -0,0 +1,244 @@ +assertFalse( + ConstantsHelper::is_use_of_global_constant( + self::$phpcsFile, + -1 + ) + ); + } + + /** + * Test is_use_of_global_constant(). + * + * @dataProvider dataIsUseOfGlobalConstant + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * @param int|null $tokenType Optional. The token type to use for the target token. + * @param string|null $tokenContent Optional. The token content to use for the target token. + * + * @return void + */ + public function testIsUseOfGlobalConstant( $testMarker, $expectedResult, $tokenType = \T_STRING, $tokenContent = null ) { + $stackPtr = $this->getTargetToken( $testMarker, $tokenType, $tokenContent ); + $result = ConstantsHelper::is_use_of_global_constant( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsUseOfGlobalConstant() + * + * @return array> + */ + public static function dataIsUseOfGlobalConstant() { + return array( + // Cases that should return false. + 'variable_assignment' => array( + 'testMarker' => '/* testVariableAssignment */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + ), + 'function_call' => array( + 'testMarker' => '/* testFunctionCall */', + 'expectedResult' => false, + ), + 'function_declaration' => array( + 'testMarker' => '/* testFunctionDeclaration */', + 'expectedResult' => false, + ), + 'class_instantiation' => array( + 'testMarker' => '/* testClassInstantiation */', + 'expectedResult' => false, + ), + 'static_method_call' => array( + 'testMarker' => '/* testStaticMethodCall */', + 'expectedResult' => false, + ), + 'static_class_constant_access' => array( + 'testMarker' => '/* testStaticClassConstantAccess */', + 'expectedResult' => false, + ), + 'class_name_resolution' => array( + 'testMarker' => '/* testClassNameResolution */', + 'expectedResult' => false, + ), + 'namespace_declaration' => array( + 'testMarker' => '/* testNamespaceDeclaration */', + 'expectedResult' => false, + ), + 'use_statement' => array( + 'testMarker' => '/* testUseStatement */', + 'expectedResult' => false, + ), + 'class_extends' => array( + 'testMarker' => '/* testClassExtends */', + 'expectedResult' => false, + ), + 'class_implements' => array( + 'testMarker' => '/* testClassImplements */', + 'expectedResult' => false, + ), + 'class_instantiation_no_parentheses' => array( + 'testMarker' => '/* testClassInstantiationNoParentheses */', + 'expectedResult' => false, + ), + 'instanceof' => array( + 'testMarker' => '/* testInstanceof */', + 'expectedResult' => false, + ), + 'preceded_by_insteadof' => array( + 'testMarker' => '/* testPrecededByInsteadOf */', + 'expectedResult' => false, + ), + 'goto_label' => array( + 'testMarker' => '/* testGotoLabel */', + 'expectedResult' => false, + ), + 'preceded_by_as' => array( + 'testMarker' => '/* testPrecededByAs */', + 'expectedResult' => false, + ), + 'class_declaration' => array( + 'testMarker' => '/* testClassDeclaration */', + 'expectedResult' => false, + ), + 'interface_declaration' => array( + 'testMarker' => '/* testInterfaceDeclaration */', + 'expectedResult' => false, + ), + 'trait_declaration' => array( + 'testMarker' => '/* testTraitDeclaration */', + 'expectedResult' => false, + ), + 'enum_declaration' => array( + 'testMarker' => '/* testEnumDeclaration */', + 'expectedResult' => false, + ), + 'object_property_access' => array( + 'testMarker' => '/* testObjectPropertyAccess */', + 'expectedResult' => false, + ), + 'nullsafe_object_property_access' => array( + 'testMarker' => '/* testNullsafeObjectPropertyAccess */', + 'expectedResult' => false, + ), + 'class_constant_access' => array( + 'testMarker' => '/* testClassConstantAccess */', + 'expectedResult' => false, + ), + 'preceded_by_public' => array( + 'testMarker' => '/* testPrecededByPublic */', + 'expectedResult' => false, + ), + 'preceded_by_protected' => array( + 'testMarker' => '/* testPrecededByProtected */', + 'expectedResult' => false, + ), + 'preceded_by_private' => array( + 'testMarker' => '/* testPrecededByPrivate */', + 'expectedResult' => false, + ), + 'preceded_by_public_set' => array( + 'testMarker' => '/* testPrecededByPublicSet */', + 'expectedResult' => false, + ), + 'preceded_by_protected_set' => array( + 'testMarker' => '/* testPrecededByProtectedSet */', + 'expectedResult' => false, + ), + 'preceded_by_private_set' => array( + 'testMarker' => '/* testPrecededByPrivateSet */', + 'expectedResult' => false, + ), + 'partially_qualified_namespaced_constant' => array( + 'testMarker' => '/* testPartiallyQualifiedNamespacedConstant */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'PHP_OS', + ), + 'fully_qualified_namespaced_constant' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedConstant */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'PHP_OS', + ), + 'namespace_relative_constant' => array( + 'testMarker' => '/* testNamespaceRelativeConstant */', + 'expectedResult' => false, + ), + 'namespace_relative_sub_constant' => array( + 'testMarker' => '/* testNamespaceRelativeSubConstant */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'PHP_OS', + ), + 'class_constant_declaration' => array( + 'testMarker' => '/* testClassConstantDeclaration */', + 'expectedResult' => false, + ), + 'use_const_statement_grouped' => array( + 'testMarker' => '/* testUseConstStatementGrouped */', + 'expectedResult' => false, + ), + 'use_statement_grouped' => array( + 'testMarker' => '/* testUseStatementGrouped */', + 'expectedResult' => false, + ), + + // Cases that should return true. + 'echo_statement' => array( + 'testMarker' => '/* testEchoStatement */', + 'expectedResult' => true, + ), + 'echo_statement_fully_qualified' => array( + 'testMarker' => '/* testEchoStatementFullyQualified */', + 'expectedResult' => true, + ), + 'const_declaration' => array( + 'testMarker' => '/* testConstDeclaration */', + 'expectedResult' => true, + ), + 'const_declaration_in_list' => array( + 'testMarker' => '/* testConstDeclarationInList */', + 'expectedResult' => true, + ), + 'use_const_statement' => array( + 'testMarker' => '/* testUseConstStatement */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + 'tokenContent' => 'PHP_OS', + ), + ); + } +} diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.php b/WordPress/Tests/Security/EscapeOutputUnitTest.php index 7111a1ff13..3861619c70 100644 --- a/WordPress/Tests/Security/EscapeOutputUnitTest.php +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.php @@ -20,7 +20,6 @@ * * @covers \WordPressCS\WordPress\Helpers\ArrayWalkingFunctionsHelper * @covers \WordPressCS\WordPress\Helpers\ContextHelper::get_safe_cast_tokens - * @covers \WordPressCS\WordPress\Helpers\ConstantsHelper::is_use_of_global_constant * @covers \WordPressCS\WordPress\Helpers\EscapingFunctionsTrait * @covers \WordPressCS\WordPress\Helpers\PrintingFunctionsTrait * @covers \WordPressCS\WordPress\Sniffs\Security\EscapeOutputSniff From c477c29a3bee01d2ab14ff10bec7ec19823633fc Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 16:12:01 -0300 Subject: [PATCH 07/48] PR 2755 EscapingFunctionsTrait: add tests --- .../IsAutoEscapedFunctionUnitTest.php | 111 ++++++++++++++++++ .../IsEscapingFunctionUnitTest.php | 111 ++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php create mode 100644 WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php diff --git a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php new file mode 100644 index 0000000000..12d8fddb99 --- /dev/null +++ b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php @@ -0,0 +1,111 @@ +testClass = new class() { + use EscapingFunctionsTrait; + }; + } + + /** + * Test is_auto_escaped_function() with default auto escaped functions. + * + * @dataProvider dataIsAutoEscapedFunction + * + * @param string $functionName The function name to test. + * @param bool $expectedResult The expected return value. + * + * @return void + */ + public function testIsAutoEscapedFunction( $functionName, $expectedResult ) { + $this->assertSame( + $expectedResult, + $this->testClass->is_auto_escaped_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsAutoEscapedFunction() + * + * @return array> + */ + public static function dataIsAutoEscapedFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'bloginfo', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'bOdY_ClAsS', + 'expectedResult' => true, + ), + 'not_an_auto_escaped_function' => array( + 'functionName' => 'esc_html', + 'expectedResult' => false, + ), + ); + } + + /** + * Test that a custom auto-escaped function is recognized. + * + * @return void + */ + public function testCustomAutoEscapedFunctionIsRecognized() { + $this->assertFalse( $this->testClass->is_auto_escaped_function( 'my_custom_auto_escaped' ) ); + $this->testClass->customAutoEscapedFunctions = array( 'my_custom_auto_escaped' ); + $this->assertTrue( $this->testClass->is_auto_escaped_function( 'my_custom_auto_escaped' ) ); + $this->assertTrue( $this->testClass->is_auto_escaped_function( 'bloginfo' ) ); + } + + /** + * Test that the auto-escaped function list is updated when the custom + * auto-escaped functions are changed. + * + * @return void + */ + public function testIsAutoEscapedFunctionUpdatesWhenCustomFunctionsChange() { + $this->testClass->customAutoEscapedFunctions = array( 'first_custom' ); + $this->assertTrue( $this->testClass->is_auto_escaped_function( 'first_custom' ) ); + + $this->testClass->customAutoEscapedFunctions = array( 'second_custom' ); + $this->assertTrue( $this->testClass->is_auto_escaped_function( 'second_custom' ) ); + $this->assertFalse( $this->testClass->is_auto_escaped_function( 'first_custom' ) ); + } +} diff --git a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php new file mode 100644 index 0000000000..013876030f --- /dev/null +++ b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php @@ -0,0 +1,111 @@ +testClass = new class() { + use EscapingFunctionsTrait; + }; + } + + /** + * Test is_escaping_function() with default escaping functions. + * + * @dataProvider dataIsEscapingFunction + * + * @param string $functionName The function name to test. + * @param bool $expectedResult The expected return value. + * + * @return void + */ + public function testIsEscapingFunction( $functionName, $expectedResult ) { + $this->assertSame( + $expectedResult, + $this->testClass->is_escaping_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsEscapingFunction() + * + * @return array> + */ + public static function dataIsEscapingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'esc_html', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'eSc_AtTr', + 'expectedResult' => true, + ), + 'not_an_escaping_function' => array( + 'functionName' => 'printf', + 'expectedResult' => false, + ), + ); + } + + /** + * Test that a custom escaping function is recognized. + * + * @return void + */ + public function testCustomEscapingFunctionIsRecognized() { + $this->assertFalse( $this->testClass->is_escaping_function( 'my_custom_escape' ) ); + $this->testClass->customEscapingFunctions = array( 'my_custom_escape' ); + $this->assertTrue( $this->testClass->is_escaping_function( 'my_custom_escape' ) ); + $this->assertTrue( $this->testClass->is_escaping_function( 'esc_html' ) ); + } + + /** + * Test that the escaping function list is updated when the custom escaping + * functions are changed. + * + * @return void + */ + public function testIsEscapingFunctionUpdatesWhenCustomFunctionsChange() { + $this->testClass->customEscapingFunctions = array( 'first_custom' ); + $this->assertTrue( $this->testClass->is_escaping_function( 'first_custom' ) ); + + $this->testClass->customEscapingFunctions = array( 'second_custom' ); + $this->assertTrue( $this->testClass->is_escaping_function( 'second_custom' ) ); + $this->assertFalse( $this->testClass->is_escaping_function( 'first_custom' ) ); + } +} From 8ab11c98b8517df2ef5887be0cf5a5b0e344469e Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 6 Jul 2026 19:36:27 +0000 Subject: [PATCH 08/48] PR 2756 SanitizationHelperTrait: add tests for is_sanitizing_function() and is_sanitizing_and_unslashing_function() --- ...anitizingAndUnslashingFunctionUnitTest.php | 82 +++++++++++++++++++ .../IsSanitizingFunctionUnitTest.php | 82 +++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php new file mode 100644 index 0000000000..6d0b3685d8 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php @@ -0,0 +1,82 @@ +assertSame( + $expectedResult, + self::$testClass->is_sanitizing_and_unslashing_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsSanitizingAndUnslashingFunction() + * + * @return array> + */ + public static function dataIsSanitizingAndUnslashingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'absint', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'iNtVaL', + 'expectedResult' => true, + ), + 'not_a_sanitizing_and_unslashing_function' => array( + 'functionName' => 'sanitize_text_field', + 'expectedResult' => false, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php new file mode 100644 index 0000000000..03aabf9097 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php @@ -0,0 +1,82 @@ +assertSame( + $expectedResult, + self::$testClass->is_sanitizing_function( $functionName ) + ); + } + + /** + * Data provider. + * + * @see testIsSanitizingFunction() + * + * @return array> + */ + public static function dataIsSanitizingFunction() { + return array( + 'lowercase_name' => array( + 'functionName' => 'sanitize_text_field', + 'expectedResult' => true, + ), + 'mixedcase_name' => array( + 'functionName' => 'SaNiTiZe_EmAiL', + 'expectedResult' => true, + ), + 'not_a_sanitizing_function' => array( + 'functionName' => 'printf', + 'expectedResult' => false, + ), + ); + } +} From 72f84d02793e8c84e3b57c71e6bc7f4dc0587044 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 6 Jul 2026 19:36:27 +0000 Subject: [PATCH 09/48] PR 2756 SanitizationHelperTrait: add tests for get_sanitizing_functions() and get_sanitizing_and_unslashing_functions() --- ...nitizingAndUnslashingFunctionsUnitTest.php | 90 +++++++++++++++++++ .../GetSanitizingFunctionsUnitTest.php | 90 +++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingAndUnslashingFunctionsUnitTest.php create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingFunctionsUnitTest.php diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingAndUnslashingFunctionsUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingAndUnslashingFunctionsUnitTest.php new file mode 100644 index 0000000000..bf97684826 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingAndUnslashingFunctionsUnitTest.php @@ -0,0 +1,90 @@ +testClass = new class() { + use SanitizationHelperTrait; + }; + } + + /** + * Test that get_sanitizing_and_unslashing_functions() returns an array containing a known + * sanitizing and unslashing function. + * + * @return void + */ + public function testGetSanitizingAndUnslashingFunctionsContainsKnownFunction() { + $result = $this->testClass->get_sanitizing_and_unslashing_functions(); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'absint', $result ); + $this->assertTrue( $result['absint'] ); + } + + /** + * Test that get_sanitizing_and_unslashing_functions() includes custom unslashing sanitizing functions. + * + * @return void + */ + public function testGetSanitizingAndUnslashingFunctionsIncludesCustomFunctions() { + $defaultCount = count( $this->testClass->get_sanitizing_and_unslashing_functions() ); + $this->testClass->customUnslashingSanitizingFunctions = array( 'my_custom_unslasher' ); + + $result = $this->testClass->get_sanitizing_and_unslashing_functions(); + + $this->assertCount( $defaultCount + 1, $result ); + $this->assertArrayHasKey( 'my_custom_unslasher', $result ); + $this->assertFalse( $result['my_custom_unslasher'] ); + } + + /** + * Test that get_sanitizing_and_unslashing_functions() updates the result when custom + * unslashing sanitizing functions are changed. + * + * @return void + */ + public function testGetSanitizingAndUnslashingFunctionsUpdatesWhenCustomFunctionsChange() { + $this->testClass->customUnslashingSanitizingFunctions = array( 'first_custom' ); + $result = $this->testClass->get_sanitizing_and_unslashing_functions(); + $this->assertArrayHasKey( 'first_custom', $result ); + $this->assertFalse( $result['first_custom'] ); + + $this->testClass->customUnslashingSanitizingFunctions = array( 'second_custom' ); + $result = $this->testClass->get_sanitizing_and_unslashing_functions(); + $this->assertArrayHasKey( 'second_custom', $result ); + $this->assertFalse( $result['second_custom'] ); + $this->assertArrayNotHasKey( 'first_custom', $result ); + } +} diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingFunctionsUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingFunctionsUnitTest.php new file mode 100644 index 0000000000..28f2fbd15c --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/GetSanitizingFunctionsUnitTest.php @@ -0,0 +1,90 @@ +testClass = new class() { + use SanitizationHelperTrait; + }; + } + + /** + * Test that get_sanitizing_functions() returns an array containing a known + * sanitizing function. + * + * @return void + */ + public function testGetSanitizingFunctionsContainsKnownFunction() { + $result = $this->testClass->get_sanitizing_functions(); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'sanitize_text_field', $result ); + $this->assertTrue( $result['sanitize_text_field'] ); + } + + /** + * Test that get_sanitizing_functions() includes custom sanitizing functions. + * + * @return void + */ + public function testGetSanitizingFunctionsIncludesCustomFunctions() { + $defaultCount = count( $this->testClass->get_sanitizing_functions() ); + $this->testClass->customSanitizingFunctions = array( 'my_custom_sanitizer' ); + + $result = $this->testClass->get_sanitizing_functions(); + + $this->assertCount( $defaultCount + 1, $result ); + $this->assertArrayHasKey( 'my_custom_sanitizer', $result ); + $this->assertFalse( $result['my_custom_sanitizer'] ); + } + + /** + * Test that get_sanitizing_functions() updates the result when custom + * sanitizing functions are changed. + * + * @return void + */ + public function testGetSanitizingFunctionsUpdatesWhenCustomFunctionsChange() { + $this->testClass->customSanitizingFunctions = array( 'first_custom' ); + $result = $this->testClass->get_sanitizing_functions(); + $this->assertArrayHasKey( 'first_custom', $result ); + $this->assertFalse( $result['first_custom'] ); + + $this->testClass->customSanitizingFunctions = array( 'second_custom' ); + $result = $this->testClass->get_sanitizing_functions(); + $this->assertArrayHasKey( 'second_custom', $result ); + $this->assertFalse( $result['second_custom'] ); + $this->assertArrayNotHasKey( 'first_custom', $result ); + } +} From 33c4820f5ea1d2ab8cf77418f35725a07c90fc78 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 7 Jul 2026 14:14:17 +0000 Subject: [PATCH 10/48] PR 2760 WPDBTrait::is_wpdb_method_call(): add tests --- WordPress/Tests/DB/PreparedSQLUnitTest.php | 1 - .../IsWpdbMethodCallNoMethodNameUnitTest.inc | 7 + ...WpdbMethodCallNoObjectOperatorUnitTest.inc | 7 + ...MethodCallNoOpeningParenthesisUnitTest.inc | 7 + ...bMethodCallUnclosedParenthesisUnitTest.inc | 7 + .../WPDBTrait/IsWpdbMethodCallUnitTest.inc | 50 +++ .../WPDBTrait/IsWpdbMethodCallUnitTest.php | 324 ++++++++++++++++++ .../Helpers/WPDBTrait/WPDBTraitWrapper.php | 57 +++ 8 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoMethodNameUnitTest.inc create mode 100644 WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoObjectOperatorUnitTest.inc create mode 100644 WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoOpeningParenthesisUnitTest.inc create mode 100644 WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnclosedParenthesisUnitTest.inc create mode 100644 WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc create mode 100644 WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php create mode 100644 WordPress/Tests/Helpers/WPDBTrait/WPDBTraitWrapper.php diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.php b/WordPress/Tests/DB/PreparedSQLUnitTest.php index 8439bec565..00e1ce093a 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.php @@ -20,7 +20,6 @@ * * @covers \WordPressCS\WordPress\Helpers\ContextHelper::is_safe_casted * @covers \WordPressCS\WordPress\Helpers\FormattingFunctionsHelper - * @covers \WordPressCS\WordPress\Helpers\WPDBTrait * @covers \WordPressCS\WordPress\Sniffs\DB\PreparedSQLSniff */ final class PreparedSQLUnitTest extends AbstractSniffUnitTest { diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoMethodNameUnitTest.inc b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoMethodNameUnitTest.inc new file mode 100644 index 0000000000..f379c934ca --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoMethodNameUnitTest.inc @@ -0,0 +1,7 @@ + diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoObjectOperatorUnitTest.inc b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoObjectOperatorUnitTest.inc new file mode 100644 index 0000000000..5b446081c2 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallNoObjectOperatorUnitTest.inc @@ -0,0 +1,7 @@ +prepare diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnclosedParenthesisUnitTest.inc b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnclosedParenthesisUnitTest.inc new file mode 100644 index 0000000000..95898646e3 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnclosedParenthesisUnitTest.inc @@ -0,0 +1,7 @@ +prepare( diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc new file mode 100644 index 0000000000..325025d736 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc @@ -0,0 +1,50 @@ +prepare( 'SELECT * FROM table' ); +/* testNotWpdbClass */ +DB::esc_like( $find ); +$array[] = /* testWpdbNotMethodCall */ $wpdb; + +/* testPropertyAccess */ +echo $wpdb->num_queries /* testPropertyAccessOpenParen */ ; + +/* testFunctionCall */ +$result = wpdb( 'SELECT * FROM table' ); +/* testNotTargetMethod */ +$wpdb->insert /* testNotTargetMethodOpenParen */ ( 'table', array( 'name' => 'value' ) ); +/* testUppercaseWpdbVariable */ +$WPDB->prepare( 'SELECT * FROM table' ); + +/* testVariableMethodCall */ +$wpdb->$prepare /* testVariableMethodCallOpenParen */ ( 'SELECT * FROM table' ); + +/* testPartiallyQualified */ +MyNamespace\wpdb::esc_like /* testPartiallyQualifiedOpenParen */ ( $find ); +/* testFullyQualifiedNamespaced */ +\MyNamespace\wpdb::esc_like /* testFullyQualifiedNamespacedOpenParen */ ( $find ); +/* testNamespaceRelative */ +namespace\wpdb::esc_like /* testNamespaceRelativeOpenParen */ ( $find ); // The method should start recognizing this as a WPDB method call once it can resolve relative namespaces since this test file is not namespaced. +/* testNamespaceRelativeSub */ +namespace\Sub\wpdb::esc_like /* testNamespaceRelativeSubOpenParen */ ( $find ); + +/* + * The below should be recognized as a $wpdb method call. + */ + +/* testObjectOperator */ +$wpdb->prepare /* testObjectOperatorOpenParen */ ( 'SELECT * FROM table WHERE id = %d', $id ); +/* testNullsafeObjectOperator */ +$wpdb?->PREPARE /* testNullsafeObjectOperatorOpenParen */ ( 'SELECT * FROM table WHERE id = %d', $id ); +/* testUnqualifiedClassUppercase */ +WPDB::esc_like /* testUnqualifiedClassUppercaseOpenParen */ ( $find ); +/* testUnqualifiedClassLowercase */ +wpdb::esc_like /* testUnqualifiedClassLowercaseOpenParen */ ( $find ); +/* testFullyQualifiedGlobalLowercase */ +\wpdb::esc_like /* testFullyQualifiedGlobalLowercaseOpenParen */ ( $find ); +/* testFullyQualifiedGlobalUppercase */ +\WPDB /* comment */ :: /* comment */ esc_like /* testFullyQualifiedGlobalUppercaseOpenParen */ ( $find ); diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php new file mode 100644 index 0000000000..0f86c7bc05 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php @@ -0,0 +1,324 @@ +testClass = new WPDBTraitWrapper(); + } + + /** + * Test is_wpdb_method_call() returns false if the token does not exist. + * + * @return void + */ + public function testIsWpdbMethodCallReturnsFalseIfTokenDoesNotExist() { + $result = $this->testClass->invoke_is_wpdb_method_call( + self::$phpcsFile, + -1, + array( 'prepare' => true ) + ); + + $this->assertFalse( $result ); + } + + /** + * Test is_wpdb_method_call() returns false when $target_methods is empty. + * + * @return void + */ + public function testIsWpdbMethodCallReturnsFalseWithEmptyTargetMethods() { + $stackPtr = $this->getTargetToken( '/* testObjectOperator */', \T_VARIABLE ); + $result = $this->testClass->invoke_is_wpdb_method_call( + self::$phpcsFile, + $stackPtr, + array() + ); + + $this->assertFalse( $result ); + } + + /** + * Test is_wpdb_method_call() returns false for live coding / parse error situations. + * + * @dataProvider dataIsWpdbMethodCallLiveCoding + * + * @param string $caseFile The test case file to parse. + * + * @return void + */ + public function testIsWpdbMethodCallLiveCoding( $caseFile ) { + $phpcsFile = self::parseFile( + __DIR__ . '/' . $caseFile, + self::$phpcsFile->ruleset, + self::$phpcsFile->config + ); + + $stackPtr = $phpcsFile->findNext( \T_VARIABLE, 0 ); + $this->assertNotFalse( $stackPtr ); + + $result = $this->testClass->invoke_is_wpdb_method_call( + $phpcsFile, + $stackPtr, + array( 'prepare' => true ) + ); + + $this->assertFalse( $result ); + } + + /** + * Data provider. + * + * @see testIsWpdbMethodCallLiveCoding() + * + * @return array> + */ + public static function dataIsWpdbMethodCallLiveCoding() { + return array( + 'no_object_operator' => array( + 'caseFile' => 'IsWpdbMethodCallNoObjectOperatorUnitTest.inc', + ), + 'no_method_name' => array( + 'caseFile' => 'IsWpdbMethodCallNoMethodNameUnitTest.inc', + ), + 'no_opening_parenthesis' => array( + 'caseFile' => 'IsWpdbMethodCallNoOpeningParenthesisUnitTest.inc', + ), + 'unclosed_parenthesis' => array( + 'caseFile' => 'IsWpdbMethodCallUnclosedParenthesisUnitTest.inc', + ), + ); + } + + /** + * Test is_wpdb_method_call(). + * + * @dataProvider dataIsWpdbMethodCall + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * @param int|string $tokenType The token type to search for. + * @param string|null $tokenContent The token content to search for (if applicable). + * @param bool $hasMethodPtr Whether the methodPtr property should be set. + * @param string|bool $openParenMarker The test marker for the expected i token, or false if not expected to be set. + * @param bool $hasEnd Whether the end property should be set. + * + * @return void + */ + public function testIsWpdbMethodCall( + $testMarker, + $expectedResult, + $tokenType, + $tokenContent = null, + $hasMethodPtr = false, + $openParenMarker = false, + $hasEnd = false + ) { + $stackPtr = $this->getTargetToken( $testMarker, $tokenType, $tokenContent ); + $result = $this->testClass->invoke_is_wpdb_method_call( + self::$phpcsFile, + $stackPtr, + array( + 'prepare' => true, + 'esc_like' => true, + ) + ); + + $this->assertSame( $expectedResult, $result ); + + if ( true === $hasMethodPtr ) { + $expectedPtr = self::$phpcsFile->findNext( \T_STRING, ( $stackPtr + 1 ) ); + $this->assertSame( $expectedPtr, $this->testClass->methodPtr ); + } else { + $this->assertNull( $this->testClass->methodPtr ); + } + + if ( false !== $openParenMarker ) { + $expectedIPtr = $this->getTargetToken( $openParenMarker, array( \T_OPEN_PARENTHESIS, \T_SEMICOLON ) ); + $this->assertSame( $expectedIPtr, $this->testClass->i ); + } else { + $this->assertNull( $this->testClass->i ); + } + + // Note: the exact value of end is not verified because it depends on the result of + // BCFile::findEndOfStatement() which would require reimplementing the method's logic in the test. + if ( true === $hasEnd ) { + $this->assertIsInt( $this->testClass->end ); + } else { + $this->assertNull( $this->testClass->end ); + } + } + + /** + * Data provider. + * + * @see testIsWpdbMethodCall() + * + * @return array> + */ + public static function dataIsWpdbMethodCall() { + return array( + // Cases that should return false. + 'not_wpdb_variable' => array( + 'testMarker' => '/* testNotWpdbVariable */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + ), + 'not_wpdb_class' => array( + 'testMarker' => '/* testNotWpdbClass */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + ), + 'wpdb_not_method_call' => array( + 'testMarker' => '/* testWpdbNotMethodCall */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + ), + 'property_access' => array( + 'testMarker' => '/* testPropertyAccess */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testPropertyAccessOpenParen */', + ), + 'function_call' => array( + 'testMarker' => '/* testFunctionCall */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + ), + 'partially_qualified' => array( + 'testMarker' => '/* testPartiallyQualified */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'wpdb', + ), + 'fully_qualified_namespaced' => array( + 'testMarker' => '/* testFullyQualifiedNamespaced */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'wpdb', + ), + 'namespace_relative' => array( + 'testMarker' => '/* testNamespaceRelative */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'wpdb', + ), + 'namespace_relative_sub' => array( + 'testMarker' => '/* testNamespaceRelativeSub */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + 'tokenContent' => 'wpdb', + ), + 'not_target_method' => array( + 'testMarker' => '/* testNotTargetMethod */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testNotTargetMethodOpenParen */', + ), + 'uppercase_wpdb_variable' => array( + 'testMarker' => '/* testUppercaseWpdbVariable */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + ), + 'variable_method_call' => array( + 'testMarker' => '/* testVariableMethodCall */', + 'expectedResult' => false, + 'tokenType' => \T_VARIABLE, + 'tokenContent' => '$wpdb', + 'hasMethodPtr' => false, + 'openParenMarker' => '/* testVariableMethodCallOpenParen */', + ), + + // Cases that should return true. + 'object_operator' => array( + 'testMarker' => '/* testObjectOperator */', + 'expectedResult' => true, + 'tokenType' => \T_VARIABLE, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testObjectOperatorOpenParen */', + 'hasEnd' => true, + ), + 'nullsafe_object_operator' => array( + 'testMarker' => '/* testNullsafeObjectOperator */', + 'expectedResult' => true, + 'tokenType' => \T_VARIABLE, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testNullsafeObjectOperatorOpenParen */', + 'hasEnd' => true, + ), + 'unqualified_class_uppercase' => array( + 'testMarker' => '/* testUnqualifiedClassUppercase */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testUnqualifiedClassUppercaseOpenParen */', + 'hasEnd' => true, + ), + 'unqualified_class_lowercase' => array( + 'testMarker' => '/* testUnqualifiedClassLowercase */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testUnqualifiedClassLowercaseOpenParen */', + 'hasEnd' => true, + ), + 'fully_qualified_global_lowercase' => array( + 'testMarker' => '/* testFullyQualifiedGlobalLowercase */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testFullyQualifiedGlobalLowercaseOpenParen */', + 'hasEnd' => true, + ), + 'fully_qualified_global_uppercase' => array( + 'testMarker' => '/* testFullyQualifiedGlobalUppercase */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testFullyQualifiedGlobalUppercaseOpenParen */', + 'hasEnd' => true, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/WPDBTrait/WPDBTraitWrapper.php b/WordPress/Tests/Helpers/WPDBTrait/WPDBTraitWrapper.php new file mode 100644 index 0000000000..2e8823e110 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/WPDBTraitWrapper.php @@ -0,0 +1,57 @@ +is_wpdb_method_call( $phpcsFile, $stackPtr, $targetMethods ); + } +} From ef2e89b38cfec6d81030c784e81128de0a3573ed Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 6 Jul 2026 19:36:27 +0000 Subject: [PATCH 11/48] PR 2764 SanitizationHelperTrait::is_sanitized(): add tests --- .../IsSanitizedUnitTest.inc | 93 ++++++ .../IsSanitizedUnitTest.php | 270 ++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.inc create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.inc b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.inc new file mode 100644 index 0000000000..b58a73d47b --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.inc @@ -0,0 +1,93 @@ +sanitize_text_field( /* testMethodCall */ $_POST['title'] ); + +/* testStaticMethodCall */ +MyClass::sanitize_text_field( $_POST['title'] ); + +/* testArrayWalkingNonSanitizingCallback */ +array_map( 'strtolower', $_POST['items'] ); + +/* testArrayWalkingNonStringCallback */ +array_map( array( $obj, 'sanitize_text_field' ), $_POST['items'] ); + +/* testArrayWalkingMissingCallback */ +map_deep( value: $_POST['items'] ); // Missing the required callback argument, but that's not the concern of the method. + +/* + * Test cases that should be considered sanitized. + */ + +/* testInUnset */ +unset( $_COOKIE['temp'] ); + +/* testSafeCast */ +(int) $_POST['count']; + +/* testSanitizingFunction */ +sanitize_text_field( $_GET['name'] ); + +/* testSanitizingAndUnslashingFunction */ +\absint( $_POST['id'] ); + +/* testUnslashedThenSanitized */ +sanitize_text_field( wp_unslash( $_POST['name'] ) ); + +/* testArrayWalkingSanitizingCallback */ +array_map( 'sanitize_text_field', $_POST['items'] ); + +sanitize_text_field( /* testStringTokenSanitized */ get_input() ); // T_STRING entry token: not reached via the sniffs that call the method (they only ever pass a T_VARIABLE), but the method supports any token type. + +/* + * Unslashing function call wrapped in a sanitizing function, across the namespace forms of the inner call. + * All of these currently return `true`. + * + * `\wp_unslash()` is the fully qualified global unslashing function, so `true` is correct in every version. + * + * `MyNamespace\wp_unslash()`, `\MyNamespace\wp_unslash()` and `namespace\Sub\wp_unslash()` are namespaced + * functions distinct from the global one, so the method should return `false`. They are false positives in + * PHPCS 3.x due to https://github.com/WordPress/WordPress-Coding-Standards/issues/2665. PHPCS 4.x returns + * `false` correctly. + * + * `namespace\wp_unslash()` is the global function in this non-namespaced file, so it should be `true`, but + * the method does not resolve relative names: like the namespaced forms it returns `true` in PHPCS 3.x (via + * the same bug) and `false` in PHPCS 4.x. + */ +/* testFullyQualifiedGlobalUnslashSanitized */ +sanitize_text_field( \wp_unslash( $_POST['foo'] ) ); +/* testPartiallyQualifiedUnslashSanitized */ +sanitize_text_field( MyNamespace\wp_unslash( $_POST['foo'] ) ); +/* testFullyQualifiedNamespacedUnslashSanitized */ +sanitize_text_field( \MyNamespace\wp_unslash( $_POST['foo'] ) ); +/* testNamespaceRelativeUnslashSanitized */ +sanitize_text_field( namespace\wp_unslash( $_POST['foo'] ) ); +/* testNamespaceRelativeSubUnslashSanitized */ +sanitize_text_field( namespace\Sub\wp_unslash( $_POST['foo'] ) ); diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php new file mode 100644 index 0000000000..aeebab96e8 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php @@ -0,0 +1,270 @@ +assertFalse( self::$testClass->is_sanitized( self::$phpcsFile, -1 ) ); + } + + /** + * Test is_sanitized(). + * + * @dataProvider dataIsSanitized + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * @param int|string $tokenType The token type to search for. Defaults to T_VARIABLE. + * + * @return void + */ + public function testIsSanitized( $testMarker, $expectedResult, $tokenType = \T_VARIABLE ) { + $stackPtr = $this->getTargetToken( $testMarker, $tokenType ); + $result = self::$testClass->is_sanitized( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsSanitized() + * + * @return array> + */ + public static function dataIsSanitized() { + return array( + // Cases where false should be returned. + 'not_within_function_call' => array( + 'testMarker' => '/* testNotWithinFunctionCall */', + 'expectedResult' => false, + ), + 'non_sanitizing_function' => array( + 'testMarker' => '/* testNonSanitizingFunction */', + 'expectedResult' => false, + ), + 'inner_function_not_sanitizing' => array( + 'testMarker' => '/* testInnerFunctionNotSanitizing */', + 'expectedResult' => false, + 'tokenType' => \T_STRING, + ), + 'unslashed_in_non_sanitizing_function' => array( + 'testMarker' => '/* testUnslashedInNonSanitizingFunction */', + 'expectedResult' => false, + ), + 'partially_qualified' => array( + 'testMarker' => '/* testPartiallyQualified */', + 'expectedResult' => false, + ), + 'fully_qualified_namespaced' => array( + 'testMarker' => '/* testFullyQualifiedNamespaced */', + 'expectedResult' => false, + ), + 'namespace_relative' => array( + 'testMarker' => '/* testNamespaceRelative */', + 'expectedResult' => false, + ), + 'namespace_relative_sub' => array( + 'testMarker' => '/* testNamespaceRelativeSub */', + 'expectedResult' => false, + ), + 'method_call' => array( + 'testMarker' => '/* testMethodCall */', + 'expectedResult' => false, + ), + 'static_method_call' => array( + 'testMarker' => '/* testStaticMethodCall */', + 'expectedResult' => false, + ), + 'array_walking_non_sanitizing_callback' => array( + 'testMarker' => '/* testArrayWalkingNonSanitizingCallback */', + 'expectedResult' => false, + ), + 'array_walking_non_string_callback' => array( + 'testMarker' => '/* testArrayWalkingNonStringCallback */', + 'expectedResult' => false, + ), + 'array_walking_missing_callback' => array( + 'testMarker' => '/* testArrayWalkingMissingCallback */', + 'expectedResult' => false, + ), + + // Cases where true should be returned. + 'in_unset' => array( + 'testMarker' => '/* testInUnset */', + 'expectedResult' => true, + ), + 'safe_cast' => array( + 'testMarker' => '/* testSafeCast */', + 'expectedResult' => true, + ), + 'sanitizing_function' => array( + 'testMarker' => '/* testSanitizingFunction */', + 'expectedResult' => true, + ), + 'sanitizing_and_unslashing_function' => array( + 'testMarker' => '/* testSanitizingAndUnslashingFunction */', + 'expectedResult' => true, + ), + 'unslashed_then_sanitized' => array( + 'testMarker' => '/* testUnslashedThenSanitized */', + 'expectedResult' => true, + ), + 'array_walking_sanitizing_callback' => array( + 'testMarker' => '/* testArrayWalkingSanitizingCallback */', + 'expectedResult' => true, + ), + 'string_token_sanitized' => array( + 'testMarker' => '/* testStringTokenSanitized */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + ), + 'fully_qualified_global_unslash_sanitized' => array( + 'testMarker' => '/* testFullyQualifiedGlobalUnslashSanitized */', + 'expectedResult' => true, + ), + 'partially_qualified_unslash_sanitized' => array( + 'testMarker' => '/* testPartiallyQualifiedUnslashSanitized */', + 'expectedResult' => true, + ), + 'fully_qualified_namespaced_unslash_sanitized' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedUnslashSanitized */', + 'expectedResult' => true, + ), + 'namespace_relative_unslash_sanitized' => array( + 'testMarker' => '/* testNamespaceRelativeUnslashSanitized */', + 'expectedResult' => true, + ), + 'namespace_relative_sub_unslash_sanitized' => array( + 'testMarker' => '/* testNamespaceRelativeSubUnslashSanitized */', + 'expectedResult' => true, + ), + ); + } + + /** + * Test that is_sanitized() invokes the unslash callback when the value is used + * without being unslashed, and not when the value has already been unslashed. + * + * @dataProvider dataIsSanitizedUnslashCallback + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value of is_sanitized(). + * @param bool $expectedCalled Whether the unslash callback is expected to be called. + * + * @return void + */ + public function testIsSanitizedUnslashCallback( $testMarker, $expectedResult, $expectedCalled ) { + $stackPtr = $this->getTargetToken( $testMarker, \T_VARIABLE ); + $callCount = 0; + $callArgs = array(); + $callback = static function ( $phpcsFile, $ptr ) use ( &$callCount, &$callArgs ) { + ++$callCount; + $callArgs = array( $phpcsFile, $ptr ); + }; + + $result = self::$testClass->is_sanitized( self::$phpcsFile, $stackPtr, $callback ); + + $this->assertSame( $expectedResult, $result, "Return value mismatch for $testMarker" ); + $this->assertSame( + $expectedCalled ? 1 : 0, + $callCount, + "Unexpected number of unslash callback invocations for $testMarker" + ); + + if ( true === $expectedCalled ) { + $this->assertSame( + array( self::$phpcsFile, $stackPtr ), + $callArgs, + "The unslash callback received unexpected arguments for $testMarker" + ); + } + } + + /** + * Data provider. + * + * @see testIsSanitizedUnslashCallback() + * + * @return array> + */ + public static function dataIsSanitizedUnslashCallback() { + return array( + 'not_within_function_call' => array( + 'testMarker' => '/* testNotWithinFunctionCall */', + 'expectedResult' => false, + 'expectedCalled' => true, + ), + 'non_sanitizing_function' => array( + 'testMarker' => '/* testNonSanitizingFunction */', + 'expectedResult' => false, + 'expectedCalled' => true, + ), + 'sanitized_not_unslashed' => array( + 'testMarker' => '/* testSanitizingFunction */', + 'expectedResult' => true, + 'expectedCalled' => true, + ), + 'unslashed_then_sanitized' => array( + 'testMarker' => '/* testUnslashedThenSanitized */', + 'expectedResult' => true, + 'expectedCalled' => false, + ), + 'unslashed_in_non_sanitizing_function' => array( + 'testMarker' => '/* testUnslashedInNonSanitizingFunction */', + 'expectedResult' => false, + 'expectedCalled' => false, + ), + 'sanitizing_and_unslashing_function' => array( + 'testMarker' => '/* testSanitizingAndUnslashingFunction */', + 'expectedResult' => true, + 'expectedCalled' => false, + ), + ); + } +} From d05b72d98105c7de09539fb4bb3a374ed364801c Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 6 Jul 2026 19:36:27 +0000 Subject: [PATCH 12/48] PR 2764 SanitizationHelperTrait::is_only_sanitized(): add tests --- .../IsOnlySanitizedUnitTest.inc | 41 ++++++ .../IsOnlySanitizedUnitTest.php | 135 ++++++++++++++++++ .../ValidatedSanitizedInputUnitTest.php | 1 - 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/IsOnlySanitizedUnitTest.inc create mode 100644 WordPress/Tests/Helpers/SanitizationHelperTrait/IsOnlySanitizedUnitTest.php diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsOnlySanitizedUnitTest.inc b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsOnlySanitizedUnitTest.inc new file mode 100644 index 0000000000..f207261900 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsOnlySanitizedUnitTest.inc @@ -0,0 +1,41 @@ +assertFalse( self::$testClass->is_only_sanitized( self::$phpcsFile, -1 ) ); + } + + /** + * Test is_only_sanitized(). + * + * @dataProvider dataIsOnlySanitized + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param bool $expectedResult The expected return value. + * @param int|string $tokenType The token type to search for. Defaults to T_VARIABLE. + * + * @return void + */ + public function testIsOnlySanitized( $testMarker, $expectedResult, $tokenType = \T_VARIABLE ) { + $stackPtr = $this->getTargetToken( $testMarker, $tokenType ); + $result = self::$testClass->is_only_sanitized( + self::$phpcsFile, + $stackPtr + ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsOnlySanitized() + * + * @return array> + */ + public static function dataIsOnlySanitized() { + return array( + // Cases where false should be returned. + 'not_sanitized_echo' => array( + 'testMarker' => '/* testNotSanitizedEcho */', + 'expectedResult' => false, + ), + 'inner_function_not_sanitizing' => array( + 'testMarker' => '/* testInnerFunctionNotSanitizing */', + 'expectedResult' => false, + ), + 'only_unslashed' => array( + 'testMarker' => '/* testOnlyUnslashed */', + 'expectedResult' => false, + ), + 'cast_nested_in_function' => array( + 'testMarker' => '/* testCastNestedInFunction */', + 'expectedResult' => false, + ), + 'sanitized_nested_in_function' => array( + 'testMarker' => '/* testSanitizedNestedInFunction */', + 'expectedResult' => false, + ), + + // Cases where true should be returned. + 'single_sanitizing_function' => array( + 'testMarker' => '/* testSingleSanitizingFunction */', + 'expectedResult' => true, + ), + 'unslashing_sanitizing_function' => array( + 'testMarker' => '/* testUnslashingSanitizingFunction */', + 'expectedResult' => true, + ), + 'array_walking_sanitizing_callback' => array( + 'testMarker' => '/* testArrayWalkingSanitizingCallback */', + 'expectedResult' => true, + ), + 'in_unset' => array( + 'testMarker' => '/* testInUnset */', + 'expectedResult' => true, + ), + 'safe_cast' => array( + 'testMarker' => '/* testSafeCast */', + 'expectedResult' => true, + ), + 'string_token_sanitized' => array( + 'testMarker' => '/* testStringTokenSanitized */', + 'expectedResult' => true, + 'tokenType' => \T_STRING, + ), + ); + } +} diff --git a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php index b087e4c5f4..0f00ff0d48 100644 --- a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php +++ b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php @@ -19,7 +19,6 @@ * @since 1.0.0 This sniff has been moved from the `VIP` category to the `Security` category. * * @covers \WordPressCS\WordPress\Helpers\ArrayWalkingFunctionsHelper - * @covers \WordPressCS\WordPress\Helpers\SanitizationHelperTrait * @covers \WordPressCS\WordPress\Helpers\UnslashingFunctionsHelper * @covers \WordPressCS\WordPress\Helpers\VariableHelper * @covers \WordPressCS\WordPress\Sniffs\Security\ValidatedSanitizedInputSniff From e3da696385ad717a54c05fcca7a12cf1bdf4d31c Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 4 Aug 2025 13:35:26 -0300 Subject: [PATCH 13/48] Tests: update to allow for running the tests on PHPCS 4.x As of PHPCS 4.0, the base sniff test class has been renamed from `AbstractSniffUnitTest` to `AbstractSniffTestCase`. Additionally, the PHPCS test setup no longer uses the outdated custom test suite creation. This means that to allow for the tests to run on both PHPCS 3.x and 4.x, some changes are needed. This commit handles this by: * Changing all test files to `extend` the new test case class and adding a class alias to the test bootstrap for compatibility with PHPCS 3.x. * Adding separate scripts to the `composer.json` file for invoking the tests on PHPCS 3.x vs 4.x. * Updating the minimum PHPUnit 9 version to 9.3.4 as required by the PHPCS 4.x native test framework. * Add GH Actions jobs to test against PHPCS 4.x to all `test` matrices. * Updating the `quicktest` and `unit-tests` GH Actions jobs to use the correct Composer script based on the installed PHPCS version. * Run the `basic-qa` code-sniff and ruleset-test jobs against both PHPCS 3.x-dev and 4.x-dev. * Update `basic-qa` to accept the phpcbf NON_FIXABLE exit code (2) on PHPCS 4.x in the fixer-conflict check. PHPCS 4.0 reworked the phpcbf exit codes into a bitmask. _Note: even though PHPCS 4.x supports PHPUnit 10 and 11, we cannot widen the version restrictions for PHPUnit (yet) while PHPCS 3.x is also supported as it would lead to PHPUnit 10/11 being installed for PHPCS 3.x on PHP >= 8.1, which would break the test runs._ --------- Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com> --- .github/workflows/basic-qa.yml | 30 +++++++---- .github/workflows/quicktest.yml | 24 ++++++--- .github/workflows/unit-tests.yml | 52 ++++++++++++++----- Tests/bootstrap.php | 10 ++++ .../ArrayDeclarationSpacingUnitTest.php | 4 +- .../Tests/Arrays/ArrayIndentationUnitTest.php | 4 +- .../ArrayKeySpacingRestrictionsUnitTest.php | 4 +- .../MultipleStatementAlignmentUnitTest.php | 4 +- .../AssignmentInTernaryConditionUnitTest.php | 4 +- .../EscapedNotTranslatedUnitTest.php | 4 +- .../Tests/DB/DirectDatabaseQueryUnitTest.php | 4 +- .../DB/PreparedSQLPlaceholdersUnitTest.php | 4 +- WordPress/Tests/DB/PreparedSQLUnitTest.php | 4 +- .../Tests/DB/RestrictedClassesUnitTest.php | 4 +- .../Tests/DB/RestrictedFunctionsUnitTest.php | 4 +- WordPress/Tests/DB/SlowDBQueryUnitTest.php | 4 +- .../DateTime/CurrentTimeTimestampUnitTest.php | 4 +- .../DateTime/RestrictedFunctionsUnitTest.php | 4 +- WordPress/Tests/Files/FileNameUnitTest.php | 4 +- .../PrefixAllGlobalsUnitTest.php | 4 +- .../ValidFunctionNameUnitTest.php | 4 +- .../ValidHookNameUnitTest.php | 4 +- .../ValidPostTypeSlugUnitTest.php | 4 +- .../ValidVariableNameUnitTest.php | 4 +- .../PHP/DevelopmentFunctionsUnitTest.php | 4 +- .../PHP/DiscouragedPHPFunctionsUnitTest.php | 4 +- WordPress/Tests/PHP/DontExtractUnitTest.php | 4 +- WordPress/Tests/PHP/IniSetUnitTest.php | 4 +- .../Tests/PHP/NoSilencedErrorsUnitTest.php | 4 +- .../Tests/PHP/POSIXFunctionsUnitTest.php | 4 +- .../Tests/PHP/PregQuoteDelimiterUnitTest.php | 4 +- .../PHP/RestrictedPHPFunctionsUnitTest.php | 4 +- WordPress/Tests/PHP/StrictInArrayUnitTest.php | 4 +- WordPress/Tests/PHP/TypeCastsUnitTest.php | 4 +- .../Tests/PHP/YodaConditionsUnitTest.php | 4 +- .../Tests/Security/EscapeOutputUnitTest.php | 4 +- .../Security/NonceVerificationUnitTest.php | 4 +- .../Tests/Security/PluginMenuSlugUnitTest.php | 4 +- .../Tests/Security/SafeRedirectUnitTest.php | 4 +- .../ValidatedSanitizedInputUnitTest.php | 4 +- .../Utils/I18nTextDomainFixerUnitTest.php | 4 +- .../Tests/WP/AlternativeFunctionsUnitTest.php | 4 +- WordPress/Tests/WP/CapabilitiesUnitTest.php | 4 +- WordPress/Tests/WP/CapitalPDangitUnitTest.php | 4 +- WordPress/Tests/WP/ClassNameCaseUnitTest.php | 4 +- WordPress/Tests/WP/CronIntervalUnitTest.php | 4 +- .../Tests/WP/DeprecatedClassesUnitTest.php | 4 +- .../Tests/WP/DeprecatedFunctionsUnitTest.php | 4 +- .../WP/DeprecatedParameterValuesUnitTest.php | 4 +- .../Tests/WP/DeprecatedParametersUnitTest.php | 4 +- .../Tests/WP/DiscouragedConstantsUnitTest.php | 4 +- .../Tests/WP/DiscouragedFunctionsUnitTest.php | 4 +- .../WP/EnqueuedResourceParametersUnitTest.php | 4 +- .../Tests/WP/EnqueuedResourcesUnitTest.php | 4 +- WordPress/Tests/WP/GetMetaSingleUnitTest.php | 4 +- .../WP/GlobalVariablesOverrideUnitTest.php | 4 +- WordPress/Tests/WP/I18nUnitTest.php | 4 +- WordPress/Tests/WP/PostsPerPageUnitTest.php | 4 +- .../CastStructureSpacingUnitTest.php | 4 +- .../ControlStructureSpacingUnitTest.php | 4 +- .../ObjectOperatorSpacingUnitTest.php | 4 +- .../WhiteSpace/OperatorSpacingUnitTest.php | 4 +- composer.json | 20 ++++--- 63 files changed, 217 insertions(+), 151 deletions(-) diff --git a/.github/workflows/basic-qa.yml b/.github/workflows/basic-qa.yml index 9b489f7a7e..11bbb993e2 100644 --- a/.github/workflows/basic-qa.yml +++ b/.github/workflows/basic-qa.yml @@ -13,7 +13,8 @@ concurrency: cancel-in-progress: true env: - PHPCS_DEV: '3.x-dev' + PHPCS_3_DEV: '3.x-dev' + PHPCS_4_DEV: '4.x-dev' UTILS_DEV: 'dev-develop' EXTRA_DEV: 'dev-develop' @@ -21,9 +22,13 @@ jobs: # Check code style of sniffs, rulesets and XML documentation. # Check that all sniffs are feature complete. sniffs: - name: Run code sniffs + name: "Run code sniffs (PHPCS ${{ matrix.dependencies }})" runs-on: ubuntu-latest + strategy: + matrix: + dependencies: [ 'phpcs-3-dev', 'phpcs-4-dev' ] + steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -45,7 +50,7 @@ jobs: - name: "Composer: set PHPCS dependencies (dev)" run: > composer require --no-update --no-scripts --no-interaction - squizlabs/php_codesniffer:"${{ env.PHPCS_DEV }}" + squizlabs/php_codesniffer:"${{ matrix.dependencies == 'phpcs-3-dev' && env.PHPCS_3_DEV || env.PHPCS_4_DEV }}" phpcsstandards/phpcsutils:"${{ env.UTILS_DEV }}" phpcsstandards/phpcsextra:"${{ env.EXTRA_DEV }}" @@ -151,7 +156,7 @@ jobs: strategy: matrix: php: [ 'latest' ] - dependencies: [ 'lowest', 'stable', 'dev' ] + dependencies: [ 'lowest', 'stable', 'phpcs-3-dev', 'phpcs-4-dev' ] name: "Ruleset test: PHP ${{ matrix.php }} on PHPCS ${{ matrix.dependencies }}" @@ -170,10 +175,10 @@ jobs: coverage: none - name: "Composer: set PHPCS dependencies for tests (dev)" - if: ${{ matrix.dependencies == 'dev' }} + if: ${{ matrix.dependencies == 'phpcs-3-dev' || matrix.dependencies == 'phpcs-4-dev' }} run: > composer require --no-update --no-scripts --no-interaction - squizlabs/php_codesniffer:"${{ env.PHPCS_DEV }}" + squizlabs/php_codesniffer:"${{ matrix.dependencies == 'phpcs-3-dev' && env.PHPCS_3_DEV || env.PHPCS_4_DEV }}" phpcsstandards/phpcsutils:"${{ env.UTILS_DEV }}" phpcsstandards/phpcsextra:"${{ env.EXTRA_DEV }}" @@ -216,10 +221,10 @@ jobs: # Test for fixer conflicts by running the auto-fixers of the complete WPCS over the test case files. # This is not an exhaustive test, but should give an early indication for typical fixer conflicts. - # If only fixable errors are found, the exit code will be 1, which can be interpreted as success. - # This check is only run against "dev" as conflicts can not be fixed for already released versions. + # If only fixable errors are found, the exit code will be 1 on PHPCS 3.x and 2 on PHPCS 4.x, nad the test succeeded. + # This check is only run against the dev versions as conflicts can not be fixed for already released versions. - name: Test for fixer conflicts (fixes expected) - if: ${{ matrix.dependencies == 'dev' }} + if: ${{ matrix.dependencies == 'phpcs-3-dev' || matrix.dependencies == 'phpcs-4-dev' }} id: phpcbf continue-on-error: true run: | @@ -230,7 +235,12 @@ jobs: exit "$exitcode" - name: Fail the build on fixer conflicts and other errors - if: ${{ steps.phpcbf.outputs.EXITCODE != 0 && steps.phpcbf.outputs.EXITCODE != 1 }} + if: >- + ${{ + steps.phpcbf.outputs.EXITCODE != 0 + && steps.phpcbf.outputs.EXITCODE != 1 + && ! (matrix.dependencies == 'phpcs-4-dev' && steps.phpcbf.outputs.EXITCODE == 2) + }} run: exit ${{ steps.phpcbf.outputs.EXITCODE }} phpstan: diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 5da0c17a35..93acf351f0 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -65,13 +65,25 @@ jobs: if: ${{ matrix.dependencies == 'stable' }} run: composer lint - - name: Run the unit tests without code coverage - if: ${{ github.event.repository.fork == true || github.ref_name != 'develop' }} - run: composer run-tests + - name: Grab PHPCS version + id: phpcs_version + run: echo "VERSION=$(vendor/bin/phpcs --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT" - - name: Run the unit tests with code coverage - if: ${{ github.event.repository.fork == false && github.ref_name == 'develop' }} - run: composer coverage + - name: Run the unit tests without code coverage (PHPCS 3.x) + if: ${{ (github.event.repository.fork == true || github.ref_name != 'develop') && startsWith( steps.phpcs_version.outputs.VERSION, '3.' ) }} + run: composer run-tests-phpcs3 + + - name: Run the unit tests without code coverage (PHPCS 4.x) + if: ${{ (github.event.repository.fork == true || github.ref_name != 'develop') && startsWith( steps.phpcs_version.outputs.VERSION, '4.' ) }} + run: composer run-tests-phpcs4 + + - name: Run the unit tests with code coverage (PHPCS 3.x) + if: ${{ github.event.repository.fork == false && github.ref_name == 'develop' && startsWith( steps.phpcs_version.outputs.VERSION, '3.' ) }} + run: composer coverage-phpcs3 + + - name: Run the unit tests with code coverage (PHPCS 4.x) + if: ${{ github.event.repository.fork == false && github.ref_name == 'develop' && startsWith( steps.phpcs_version.outputs.VERSION, '4.' ) }} + run: composer coverage-phpcs4 - name: Send coverage report to Codecov if: ${{ success() && github.event.repository.fork == false && github.ref_name == 'develop' }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3a9514793c..5ce3c5555e 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,7 +15,8 @@ concurrency: cancel-in-progress: true env: - PHPCS_DEV: '3.x-dev' + PHPCS_3_DEV: '3.x-dev' + PHPCS_4_DEV: '4.x-dev' UTILS_DEV: 'dev-develop' EXTRA_DEV: 'dev-develop' @@ -52,18 +53,31 @@ jobs: # Test against dev versions of all dependencies with select PHP versions for early detection of issues. - php: '7.2' - dependencies: 'dev' + dependencies: 'phpcs-3-dev' + extensions: '' + coverage: false + - php: '7.2' + dependencies: 'phpcs-4-dev' + extensions: '' + coverage: false + - php: '8.1' + dependencies: 'phpcs-3-dev' extensions: '' coverage: false - php: '8.1' - dependencies: 'dev' + dependencies: 'phpcs-4-dev' + extensions: '' + coverage: false + - php: '8.4' + dependencies: 'phpcs-3-dev' extensions: '' coverage: false - php: '8.5' - dependencies: 'dev' + dependencies: 'phpcs-4-dev' extensions: '' coverage: false + # Add extra build to test against PHPCS 4. #- php: '7.4' # dependencies: '4.0.x-dev as 3.99.99' @@ -83,7 +97,7 @@ jobs: - name: Setup ini config id: set_ini run: | - if [ "${{ matrix.dependencies }}" != "dev" ]; then + if [ "${{ matrix.dependencies }}" != "phpcs-3-dev" ] && [ "${{ matrix.dependencies }}" != "phpcs-4-dev" ]; then echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On, display_startup_errors=On' >> "$GITHUB_OUTPUT" else echo 'PHP_INI=error_reporting=-1, display_errors=On, display_startup_errors=On' >> "$GITHUB_OUTPUT" @@ -98,10 +112,10 @@ jobs: tools: cs2pr - name: "Composer: set PHPCS dependencies for tests (dev)" - if: ${{ matrix.dependencies == 'dev' }} + if: ${{ matrix.dependencies == 'phpcs-3-dev' || matrix.dependencies == 'phpcs-4-dev' }} run: > composer require --no-update --no-scripts --no-interaction - squizlabs/php_codesniffer:"${{ env.PHPCS_DEV }}" + squizlabs/php_codesniffer:"${{ matrix.dependencies == 'phpcs-3-dev' && env.PHPCS_3_DEV || env.PHPCS_4_DEV }}" phpcsstandards/phpcsutils:"${{ env.UTILS_DEV }}" phpcsstandards/phpcsextra:"${{ env.EXTRA_DEV }}" @@ -127,13 +141,25 @@ jobs: if: ${{ matrix.dependencies == 'stable' }} run: composer lint -- --checkstyle | cs2pr - - name: Run the unit tests without code coverage - if: ${{ matrix.coverage == false || github.event.repository.fork == true }} - run: composer run-tests + - name: Grab PHPCS version + id: phpcs_version + run: echo "VERSION=$(vendor/bin/phpcs --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT" + + - name: Run the unit tests without code coverage (PHPCS 3.x) + if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '3.' ) && (matrix.coverage == false || github.event.repository.fork == true) }} + run: composer run-tests-phpcs3 + + - name: Run the unit tests without code coverage (PHPCS 4.x) + if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '4.' ) && (matrix.coverage == false || github.event.repository.fork == true) }} + run: composer run-tests-phpcs4 + + - name: Run the unit tests with code coverage (PHPCS 3.x) + if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '3.' ) && matrix.coverage == true && github.event.repository.fork == false }} + run: composer coverage-phpcs3 - - name: Run the unit tests with code coverage - if: ${{ matrix.coverage == true && github.event.repository.fork == false }} - run: composer coverage + - name: Run the unit tests with code coverage (PHPCS 4.x) + if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '4.' ) && matrix.coverage == true && github.event.repository.fork == false }} + run: composer coverage-phpcs4 - name: Send coverage report to Codecov if: ${{ success() && matrix.coverage == true && github.event.repository.fork == false }} diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index fd4c952893..219c45f5f5 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -57,6 +57,16 @@ die( 1 ); } +// Alias the PHPCS 3.x test case to the PHPCS 4.x name. +if ( class_exists( 'PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest' ) === true + && class_exists( 'PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase' ) === false +) { + class_alias( + 'PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest', + 'PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase' + ); +} + /* * Set the PHPCS_IGNORE_TEST environment variable to ignore tests from other standards. */ diff --git a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php index 6a46292970..5b25eca51c 100644 --- a/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayDeclarationSpacingUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Arrays; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ArrayDeclarationSpacing sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Arrays\ArrayDeclarationSpacingSniff */ -final class ArrayDeclarationSpacingUnitTest extends AbstractSniffUnitTest { +final class ArrayDeclarationSpacingUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php index 2e392f7296..650213a99b 100644 --- a/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayIndentationUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Arrays; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ArrayIndentation sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Arrays\ArrayIndentationSniff */ -final class ArrayIndentationUnitTest extends AbstractSniffUnitTest { +final class ArrayIndentationUnitTest extends AbstractSniffTestCase { /** * The tab width to use during testing. diff --git a/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php b/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php index aa2dc27890..96c8b867a9 100644 --- a/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php +++ b/WordPress/Tests/Arrays/ArrayKeySpacingRestrictionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Arrays; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ArrayKeySpacingRestrictions sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Arrays\ArrayKeySpacingRestrictionsSniff */ -final class ArrayKeySpacingRestrictionsUnitTest extends AbstractSniffUnitTest { +final class ArrayKeySpacingRestrictionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php index 11a839fc13..6daa1b2a1c 100644 --- a/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php +++ b/WordPress/Tests/Arrays/MultipleStatementAlignmentUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Arrays; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the Arrays.MultipleStatementAlignment sniff. @@ -22,7 +22,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Arrays\MultipleStatementAlignmentSniff */ -final class MultipleStatementAlignmentUnitTest extends AbstractSniffUnitTest { +final class MultipleStatementAlignmentUnitTest extends AbstractSniffTestCase { /** * The tab width to use during testing. diff --git a/WordPress/Tests/CodeAnalysis/AssignmentInTernaryConditionUnitTest.php b/WordPress/Tests/CodeAnalysis/AssignmentInTernaryConditionUnitTest.php index 642a8a70fc..4fc10bafe6 100644 --- a/WordPress/Tests/CodeAnalysis/AssignmentInTernaryConditionUnitTest.php +++ b/WordPress/Tests/CodeAnalysis/AssignmentInTernaryConditionUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\CodeAnalysis; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the AssignmentInTernaryCondition sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\CodeAnalysis\AssignmentInTernaryConditionSniff */ -final class AssignmentInTernaryConditionUnitTest extends AbstractSniffUnitTest { +final class AssignmentInTernaryConditionUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php b/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php index eee562f2d6..781a8ede89 100644 --- a/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php +++ b/WordPress/Tests/CodeAnalysis/EscapedNotTranslatedUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\CodeAnalysis; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the EscapedNotTranslated sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\CodeAnalysis\EscapedNotTranslatedSniff */ -final class EscapedNotTranslatedUnitTest extends AbstractSniffUnitTest { +final class EscapedNotTranslatedUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php index 5a4fda7f4a..f3f6520cbd 100644 --- a/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php +++ b/WordPress/Tests/DB/DirectDatabaseQueryUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the DirectDatabaseQuery sniff. @@ -21,7 +21,7 @@ * @covers \WordPressCS\WordPress\Helpers\RulesetPropertyHelper * @covers \WordPressCS\WordPress\Sniffs\DB\DirectDatabaseQuerySniff */ -final class DirectDatabaseQueryUnitTest extends AbstractSniffUnitTest { +final class DirectDatabaseQueryUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php index e4691350dc..e0bd5d17d5 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PreparedSQLPlaceholders sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\DB\PreparedSQLPlaceholdersSniff */ -final class PreparedSQLPlaceholdersUnitTest extends AbstractSniffUnitTest { +final class PreparedSQLPlaceholdersUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.php b/WordPress/Tests/DB/PreparedSQLUnitTest.php index 00e1ce093a..a464deae18 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PreparedSQL sniff. @@ -22,7 +22,7 @@ * @covers \WordPressCS\WordPress\Helpers\FormattingFunctionsHelper * @covers \WordPressCS\WordPress\Sniffs\DB\PreparedSQLSniff */ -final class PreparedSQLUnitTest extends AbstractSniffUnitTest { +final class PreparedSQLUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/DB/RestrictedClassesUnitTest.php b/WordPress/Tests/DB/RestrictedClassesUnitTest.php index a9bb96546e..2844696d8d 100644 --- a/WordPress/Tests/DB/RestrictedClassesUnitTest.php +++ b/WordPress/Tests/DB/RestrictedClassesUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff; /** @@ -23,7 +23,7 @@ * @covers \WordPressCS\WordPress\Helpers\RulesetPropertyHelper * @covers \WordPressCS\WordPress\Sniffs\DB\RestrictedClassesSniff */ -final class RestrictedClassesUnitTest extends AbstractSniffUnitTest { +final class RestrictedClassesUnitTest extends AbstractSniffTestCase { /** * Add a number of extra restricted classes to unit test the abstract diff --git a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php index 7b730c5f59..73ab9a2a5d 100644 --- a/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/DB/RestrictedFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff; /** @@ -21,7 +21,7 @@ * @covers \WordPressCS\WordPress\AbstractFunctionRestrictionsSniff * @covers \WordPressCS\WordPress\Sniffs\DB\RestrictedFunctionsSniff */ -final class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { +final class RestrictedFunctionsUnitTest extends AbstractSniffTestCase { /** * Add a number of extra restricted functions to unit test the abstract diff --git a/WordPress/Tests/DB/SlowDBQueryUnitTest.php b/WordPress/Tests/DB/SlowDBQueryUnitTest.php index 46ba3204d1..4bd5d67a33 100644 --- a/WordPress/Tests/DB/SlowDBQueryUnitTest.php +++ b/WordPress/Tests/DB/SlowDBQueryUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the SlowDBQuery sniff. @@ -21,7 +21,7 @@ * @covers \WordPressCS\WordPress\AbstractArrayAssignmentRestrictionsSniff * @covers \WordPressCS\WordPress\Sniffs\DB\SlowDBQuerySniff */ -final class SlowDBQueryUnitTest extends AbstractSniffUnitTest { +final class SlowDBQueryUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php index 4525e58d79..ad7b25e22c 100644 --- a/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php +++ b/WordPress/Tests/DateTime/CurrentTimeTimestampUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DateTime; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the CurrentTimeTimestamp sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\DateTime\CurrentTimeTimestampSniff */ -final class CurrentTimeTimestampUnitTest extends AbstractSniffUnitTest { +final class CurrentTimeTimestampUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php index 8a37da1c3a..3610a48452 100644 --- a/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php +++ b/WordPress/Tests/DateTime/RestrictedFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\DateTime; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the DateTime.RestrictedFunctions sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\DateTime\RestrictedFunctionsSniff */ -final class RestrictedFunctionsUnitTest extends AbstractSniffUnitTest { +final class RestrictedFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Files/FileNameUnitTest.php b/WordPress/Tests/Files/FileNameUnitTest.php index c56d3661f2..c671695de3 100644 --- a/WordPress/Tests/Files/FileNameUnitTest.php +++ b/WordPress/Tests/Files/FileNameUnitTest.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Files\DummyFile; use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\ConfigDouble; @@ -24,7 +24,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Files\FileNameSniff */ -final class FileNameUnitTest extends AbstractSniffUnitTest { +final class FileNameUnitTest extends AbstractSniffTestCase { /** * Error files with the expected nr of errors. diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php index b93ad6216d..768b7cb383 100644 --- a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\NamingConventions; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PrefixAllGlobals sniff. @@ -20,7 +20,7 @@ * @covers \WordPressCS\WordPress\Helpers\IsUnitTestTrait * @covers \WordPressCS\WordPress\Sniffs\NamingConventions\PrefixAllGlobalsSniff */ -final class PrefixAllGlobalsUnitTest extends AbstractSniffUnitTest { +final class PrefixAllGlobalsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php index 349091a4d8..8168abde4d 100644 --- a/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidFunctionNameUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\NamingConventions; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ValidFunctionName sniff. @@ -20,7 +20,7 @@ * @covers \WordPressCS\WordPress\Helpers\DeprecationHelper * @covers \WordPressCS\WordPress\Sniffs\NamingConventions\ValidFunctionNameSniff */ -final class ValidFunctionNameUnitTest extends AbstractSniffUnitTest { +final class ValidFunctionNameUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php index 763760f1c3..9611b68b43 100644 --- a/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidHookNameUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\NamingConventions; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ValidHookName sniff. @@ -20,7 +20,7 @@ * @covers \WordPressCS\WordPress\Helpers\WPHookHelper::get_functions * @covers \WordPressCS\WordPress\Sniffs\NamingConventions\ValidHookNameSniff */ -final class ValidHookNameUnitTest extends AbstractSniffUnitTest { +final class ValidHookNameUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php index cb2d41c998..fddd4b4bb6 100644 --- a/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidPostTypeSlugUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\NamingConventions; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PostType sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\NamingConventions\ValidPostTypeSlugSniff */ -final class ValidPostTypeSlugUnitTest extends AbstractSniffUnitTest { +final class ValidPostTypeSlugUnitTest extends AbstractSniffTestCase { /** * Set warnings level to 3 to trigger suggestions as warnings. diff --git a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php index 0ac884c991..b114521b6e 100644 --- a/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php +++ b/WordPress/Tests/NamingConventions/ValidVariableNameUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\NamingConventions; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ValidVariableName sniff. @@ -20,7 +20,7 @@ * @covers \WordPressCS\WordPress\Helpers\SnakeCaseHelper * @covers \WordPressCS\WordPress\Sniffs\NamingConventions\ValidVariableNameSniff */ -final class ValidVariableNameUnitTest extends AbstractSniffUnitTest { +final class ValidVariableNameUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php index f3e953545c..ec151f59ec 100644 --- a/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/DevelopmentFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PHP_DevelopmentFunctions sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\DevelopmentFunctionsSniff */ -final class DevelopmentFunctionsUnitTest extends AbstractSniffUnitTest { +final class DevelopmentFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php index b9f0e89a1b..5e716650a3 100644 --- a/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/DiscouragedPHPFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PHP_DiscouragedPHPFunctions sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\DiscouragedPHPFunctionsSniff */ -final class DiscouragedPHPFunctionsUnitTest extends AbstractSniffUnitTest { +final class DiscouragedPHPFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/DontExtractUnitTest.php b/WordPress/Tests/PHP/DontExtractUnitTest.php index 52d6fdeb65..af0dedb48d 100644 --- a/WordPress/Tests/PHP/DontExtractUnitTest.php +++ b/WordPress/Tests/PHP/DontExtractUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the DontExtract sniff. @@ -20,7 +20,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\DontExtractSniff */ -final class DontExtractUnitTest extends AbstractSniffUnitTest { +final class DontExtractUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/IniSetUnitTest.php b/WordPress/Tests/PHP/IniSetUnitTest.php index 1dd5ca4ab7..e3b78a65a0 100644 --- a/WordPress/Tests/PHP/IniSetUnitTest.php +++ b/WordPress/Tests/PHP/IniSetUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the IniSet sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\IniSetSniff */ -final class IniSetUnitTest extends AbstractSniffUnitTest { +final class IniSetUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php index 038c2c8d71..f55db69edb 100644 --- a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php +++ b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PHP.NoSilencedErrors sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\NoSilencedErrorsSniff */ -final class NoSilencedErrorsUnitTest extends AbstractSniffUnitTest { +final class NoSilencedErrorsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php index d4086c3e49..75155bb2bf 100644 --- a/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/POSIXFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the POSIXFunctions sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\POSIXFunctionsSniff */ -final class POSIXFunctionsUnitTest extends AbstractSniffUnitTest { +final class POSIXFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php b/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php index 4c6a254fae..c78b3c1dd0 100644 --- a/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php +++ b/WordPress/Tests/PHP/PregQuoteDelimiterUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PregQuoteDelimiter sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\PregQuoteDelimiterSniff */ -final class PregQuoteDelimiterUnitTest extends AbstractSniffUnitTest { +final class PregQuoteDelimiterUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php index a596706030..601d711b60 100644 --- a/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php +++ b/WordPress/Tests/PHP/RestrictedPHPFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PHP_DiscouragedPHPFunctions sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\RestrictedPHPFunctionsSniff */ -final class RestrictedPHPFunctionsUnitTest extends AbstractSniffUnitTest { +final class RestrictedPHPFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/StrictInArrayUnitTest.php b/WordPress/Tests/PHP/StrictInArrayUnitTest.php index d7104d7d9b..309f61f32e 100644 --- a/WordPress/Tests/PHP/StrictInArrayUnitTest.php +++ b/WordPress/Tests/PHP/StrictInArrayUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the StrictInArray sniff. @@ -20,7 +20,7 @@ * @covers \WordPressCS\WordPress\AbstractFunctionParameterSniff * @covers \WordPressCS\WordPress\Sniffs\PHP\StrictInArraySniff */ -final class StrictInArrayUnitTest extends AbstractSniffUnitTest { +final class StrictInArrayUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/TypeCastsUnitTest.php b/WordPress/Tests/PHP/TypeCastsUnitTest.php index 878b089927..9c4ac39198 100644 --- a/WordPress/Tests/PHP/TypeCastsUnitTest.php +++ b/WordPress/Tests/PHP/TypeCastsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the TypeCasts sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\TypeCastsSniff */ -final class TypeCastsUnitTest extends AbstractSniffUnitTest { +final class TypeCastsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/PHP/YodaConditionsUnitTest.php b/WordPress/Tests/PHP/YodaConditionsUnitTest.php index 3cb8604025..b1cd57c1a4 100644 --- a/WordPress/Tests/PHP/YodaConditionsUnitTest.php +++ b/WordPress/Tests/PHP/YodaConditionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\PHP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the YodaConditions sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\PHP\YodaConditionsSniff */ -final class YodaConditionsUnitTest extends AbstractSniffUnitTest { +final class YodaConditionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Security/EscapeOutputUnitTest.php b/WordPress/Tests/Security/EscapeOutputUnitTest.php index 3861619c70..94dc6623e7 100644 --- a/WordPress/Tests/Security/EscapeOutputUnitTest.php +++ b/WordPress/Tests/Security/EscapeOutputUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Security; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the EscapeOutput sniff. @@ -24,7 +24,7 @@ * @covers \WordPressCS\WordPress\Helpers\PrintingFunctionsTrait * @covers \WordPressCS\WordPress\Sniffs\Security\EscapeOutputSniff */ -final class EscapeOutputUnitTest extends AbstractSniffUnitTest { +final class EscapeOutputUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Security/NonceVerificationUnitTest.php b/WordPress/Tests/Security/NonceVerificationUnitTest.php index d5ee162048..265248a9ba 100644 --- a/WordPress/Tests/Security/NonceVerificationUnitTest.php +++ b/WordPress/Tests/Security/NonceVerificationUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Security; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the NonceVerification sniff. @@ -20,7 +20,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Security\NonceVerificationSniff */ -final class NonceVerificationUnitTest extends AbstractSniffUnitTest { +final class NonceVerificationUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Security/PluginMenuSlugUnitTest.php b/WordPress/Tests/Security/PluginMenuSlugUnitTest.php index cee8c0e1b6..a18a0fca26 100644 --- a/WordPress/Tests/Security/PluginMenuSlugUnitTest.php +++ b/WordPress/Tests/Security/PluginMenuSlugUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Security; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PluginMenuSlug sniff. @@ -20,7 +20,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Security\PluginMenuSlugSniff */ -final class PluginMenuSlugUnitTest extends AbstractSniffUnitTest { +final class PluginMenuSlugUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Security/SafeRedirectUnitTest.php b/WordPress/Tests/Security/SafeRedirectUnitTest.php index 8adb348ce6..fb5e2ba52f 100644 --- a/WordPress/Tests/Security/SafeRedirectUnitTest.php +++ b/WordPress/Tests/Security/SafeRedirectUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Security; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the Security_SafeRedirect sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\Security\SafeRedirectSniff */ -final class SafeRedirectUnitTest extends AbstractSniffUnitTest { +final class SafeRedirectUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php index 0f00ff0d48..dba38a0a3a 100644 --- a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php +++ b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\Security; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ValidatedSanitizedInput sniff. @@ -23,7 +23,7 @@ * @covers \WordPressCS\WordPress\Helpers\VariableHelper * @covers \WordPressCS\WordPress\Sniffs\Security\ValidatedSanitizedInputSniff */ -final class ValidatedSanitizedInputUnitTest extends AbstractSniffUnitTest { +final class ValidatedSanitizedInputUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php index 872050aa97..5674720986 100644 --- a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php +++ b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php @@ -11,7 +11,7 @@ use PHP_CodeSniffer\Files\DummyFile; use PHP_CodeSniffer\Ruleset; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\ConfigDouble; @@ -23,7 +23,7 @@ * @covers \WordPressCS\WordPress\AbstractFunctionParameterSniff::is_targetted_token * @covers \WordPressCS\WordPress\Sniffs\Utils\I18nTextDomainFixerSniff */ -final class I18nTextDomainFixerUnitTest extends AbstractSniffUnitTest { +final class I18nTextDomainFixerUnitTest extends AbstractSniffTestCase { /** * The tab width to use during testing. diff --git a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php index 58f4083729..f7d85becee 100644 --- a/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php +++ b/WordPress/Tests/WP/AlternativeFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the WP_AlternativeFunctions sniff. @@ -20,7 +20,7 @@ * @covers \WordPressCS\WordPress\Helpers\MinimumWPVersionTrait * @covers \WordPressCS\WordPress\Sniffs\WP\AlternativeFunctionsSniff */ -final class AlternativeFunctionsUnitTest extends AbstractSniffUnitTest { +final class AlternativeFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/CapabilitiesUnitTest.php b/WordPress/Tests/WP/CapabilitiesUnitTest.php index 1b4e34dd05..c2d8768b89 100644 --- a/WordPress/Tests/WP/CapabilitiesUnitTest.php +++ b/WordPress/Tests/WP/CapabilitiesUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; use PHPCSUtils\BackCompat\Helper; /** @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\CapabilitiesSniff */ -final class CapabilitiesUnitTest extends AbstractSniffUnitTest { +final class CapabilitiesUnitTest extends AbstractSniffTestCase { /** * Adjust the config to allow for testing with specific CLI arguments. diff --git a/WordPress/Tests/WP/CapitalPDangitUnitTest.php b/WordPress/Tests/WP/CapitalPDangitUnitTest.php index ff545fcc65..2504230a9b 100644 --- a/WordPress/Tests/WP/CapitalPDangitUnitTest.php +++ b/WordPress/Tests/WP/CapitalPDangitUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the CapitalPDangit sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\CapitalPDangitSniff */ -final class CapitalPDangitUnitTest extends AbstractSniffUnitTest { +final class CapitalPDangitUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/ClassNameCaseUnitTest.php b/WordPress/Tests/WP/ClassNameCaseUnitTest.php index f34d832c2d..4f54be28df 100644 --- a/WordPress/Tests/WP/ClassNameCaseUnitTest.php +++ b/WordPress/Tests/WP/ClassNameCaseUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ClassNameCase sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\ClassNameCaseSniff */ -final class ClassNameCaseUnitTest extends AbstractSniffUnitTest { +final class ClassNameCaseUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.php b/WordPress/Tests/WP/CronIntervalUnitTest.php index 4bd6add5b3..e2e2d4291b 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.php +++ b/WordPress/Tests/WP/CronIntervalUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the CronInterval sniff. @@ -20,7 +20,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\CronIntervalSniff */ -final class CronIntervalUnitTest extends AbstractSniffUnitTest { +final class CronIntervalUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/DeprecatedClassesUnitTest.php b/WordPress/Tests/WP/DeprecatedClassesUnitTest.php index 3490968b53..7b8d4cf7ee 100644 --- a/WordPress/Tests/WP/DeprecatedClassesUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedClassesUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the WP_DeprecatedClasses sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\DeprecatedClassesSniff */ -final class DeprecatedClassesUnitTest extends AbstractSniffUnitTest { +final class DeprecatedClassesUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php index 451d635219..8d745c8fc4 100644 --- a/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the WP_DeprecatedFunctions sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\DeprecatedFunctionsSniff */ -final class DeprecatedFunctionsUnitTest extends AbstractSniffUnitTest { +final class DeprecatedFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.php b/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.php index 958738ab87..cd45722ae6 100644 --- a/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedParameterValuesUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the DeprecatedParameterValues sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\DeprecatedParameterValuesSniff */ -final class DeprecatedParameterValuesUnitTest extends AbstractSniffUnitTest { +final class DeprecatedParameterValuesUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php index 51c9e11751..45d6c24003 100644 --- a/WordPress/Tests/WP/DeprecatedParametersUnitTest.php +++ b/WordPress/Tests/WP/DeprecatedParametersUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the DeprecatedParameters sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\DeprecatedParametersSniff */ -final class DeprecatedParametersUnitTest extends AbstractSniffUnitTest { +final class DeprecatedParametersUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php index 9e820865ca..2691ee6eaf 100644 --- a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php +++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the WP_DiscouragedConstants sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\DiscouragedConstantsSniff */ -final class DiscouragedConstantsUnitTest extends AbstractSniffUnitTest { +final class DiscouragedConstantsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php index 1bfa8533b7..f231a3046d 100644 --- a/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php +++ b/WordPress/Tests/WP/DiscouragedFunctionsUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the WP_DiscouragedFunctions sniff. @@ -21,7 +21,7 @@ * @covers \WordPressCS\WordPress\Helpers\ContextHelper::has_object_operator_before * @covers \WordPressCS\WordPress\Sniffs\WP\DiscouragedFunctionsSniff */ -final class DiscouragedFunctionsUnitTest extends AbstractSniffUnitTest { +final class DiscouragedFunctionsUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php index a1dd90b4f5..d819318808 100644 --- a/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php +++ b/WordPress/Tests/WP/EnqueuedResourceParametersUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the EnqueuedCheck sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\EnqueuedResourceParametersSniff */ -final class EnqueuedResourceParametersUnitTest extends AbstractSniffUnitTest { +final class EnqueuedResourceParametersUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php b/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php index d329549306..0eb9f9050e 100644 --- a/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php +++ b/WordPress/Tests/WP/EnqueuedResourcesUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the EnqueuedResources sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\EnqueuedResourcesSniff */ -final class EnqueuedResourcesUnitTest extends AbstractSniffUnitTest { +final class EnqueuedResourcesUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/GetMetaSingleUnitTest.php b/WordPress/Tests/WP/GetMetaSingleUnitTest.php index f1b328c39b..174f01edf7 100644 --- a/WordPress/Tests/WP/GetMetaSingleUnitTest.php +++ b/WordPress/Tests/WP/GetMetaSingleUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the GetMetaSingle sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\GetMetaSingleSniff */ -final class GetMetaSingleUnitTest extends AbstractSniffUnitTest { +final class GetMetaSingleUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.php b/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.php index 954b65ee4e..513c72c0f3 100644 --- a/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.php +++ b/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the GlobalVariables sniff. @@ -23,7 +23,7 @@ * @covers \WordPressCS\WordPress\Helpers\WPGlobalVariablesHelper * @covers \WordPressCS\WordPress\Sniffs\WP\GlobalVariablesOverrideSniff */ -final class GlobalVariablesOverrideUnitTest extends AbstractSniffUnitTest { +final class GlobalVariablesOverrideUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WP/I18nUnitTest.php b/WordPress/Tests/WP/I18nUnitTest.php index 20e7accaab..586bdcf75d 100644 --- a/WordPress/Tests/WP/I18nUnitTest.php +++ b/WordPress/Tests/WP/I18nUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the I18n sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\I18nSniff */ -final class I18nUnitTest extends AbstractSniffUnitTest { +final class I18nUnitTest extends AbstractSniffTestCase { /** * Set CLI values before the file is tested. diff --git a/WordPress/Tests/WP/PostsPerPageUnitTest.php b/WordPress/Tests/WP/PostsPerPageUnitTest.php index bf3296ce25..a25d56f5ba 100644 --- a/WordPress/Tests/WP/PostsPerPageUnitTest.php +++ b/WordPress/Tests/WP/PostsPerPageUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the PostsPerPage sniff. @@ -23,7 +23,7 @@ * @covers \WordPressCS\WordPress\AbstractArrayAssignmentRestrictionsSniff * @covers \WordPressCS\WordPress\Sniffs\WP\PostsPerPageSniff */ -final class PostsPerPageUnitTest extends AbstractSniffUnitTest { +final class PostsPerPageUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php index 5392cf4852..bae649d133 100644 --- a/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/CastStructureSpacingUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WhiteSpace; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the CastStructureSpacing sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WhiteSpace\CastStructureSpacingSniff */ -final class CastStructureSpacingUnitTest extends AbstractSniffUnitTest { +final class CastStructureSpacingUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php index e173ae0ec9..9590baf6b8 100644 --- a/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/ControlStructureSpacingUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WhiteSpace; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ControlStructureSpacing sniff. @@ -19,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WhiteSpace\ControlStructureSpacingSniff */ -final class ControlStructureSpacingUnitTest extends AbstractSniffUnitTest { +final class ControlStructureSpacingUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php index adfbe1ba9c..d86058e99e 100644 --- a/WordPress/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/ObjectOperatorSpacingUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WhiteSpace; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the ObjectOperatorSpacing sniff. @@ -18,7 +18,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WhiteSpace\ObjectOperatorSpacingSniff */ -final class ObjectOperatorSpacingUnitTest extends AbstractSniffUnitTest { +final class ObjectOperatorSpacingUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php index d7343f1ec2..ff97a55474 100644 --- a/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php +++ b/WordPress/Tests/WhiteSpace/OperatorSpacingUnitTest.php @@ -9,7 +9,7 @@ namespace WordPressCS\WordPress\Tests\WhiteSpace; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; /** * Unit test class for the OperatorSpacing sniff. @@ -21,7 +21,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WhiteSpace\OperatorSpacingSniff */ -final class OperatorSpacingUnitTest extends AbstractSniffUnitTest { +final class OperatorSpacingUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. diff --git a/composer.json b/composer.json index 4961107d8d..2107a8bd49 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ }, "require-dev": { "phpcompatibility/php-compatibility": "^10.0.0@dev", - "phpunit/phpunit": "^8.0 || ^9.0", + "phpunit/phpunit": "^8.0 || ^9.3.4", "phpcsstandards/phpcsdevtools": "^1.2.0", "php-parallel-lint/php-parallel-lint": "^1.4.0", "php-parallel-lint/php-console-highlighter": "^1.0.0" @@ -54,12 +54,18 @@ "fix-cs": [ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" ], - "run-tests": [ + "run-tests-phpcs3": [ "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --no-coverage" ], - "coverage": [ + "run-tests-phpcs4": [ + "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" + ], + "coverage-phpcs3": [ "@php ./vendor/phpunit/phpunit/phpunit --filter WordPress ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" ], + "coverage-phpcs4": [ + "@php ./vendor/phpunit/phpunit/phpunit" + ], "check-complete": [ "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness -q ./WordPress" ], @@ -69,7 +75,7 @@ "check-all": [ "@lint", "@check-cs", - "@run-tests", + "if [ -f ./vendor/squizlabs/php_codesniffer/tests/AllTests.php ]; then composer run-tests-phpcs3; else composer run-tests-phpcs4; fi", "@check-complete-strict" ] }, @@ -77,8 +83,10 @@ "lint": "Lint PHP files against parse errors.", "check-cs": "Run the PHPCS script against the entire codebase.", "fix-cs": "Run the PHPCBF script to fix all the autofixable violations on the codebase.", - "run-tests": "Run all the unit tests for the WordPress Coding Standards sniffs without code coverage.", - "coverage": "Run all the unit tests for the WordPress Coding Standards sniffs with code coverage.", + "run-tests-phpcs3": "Run all the unit tests for the WordPress Coding Standards sniffs without code coverage (PHPCS 3.x).", + "run-tests-phpcs4": "Run all the unit tests for the WordPress Coding Standards sniffs without code coverage (PHPCS 4.x).", + "coverage-phpcs3": "Run all the unit tests for the WordPress Coding Standards sniffs with code coverage (PHPCS 3.x).", + "coverage-phpcs4": "Run all the unit tests for the WordPress Coding Standards sniffs with code coverage (PHPCS 4.x).", "check-complete": "Check if all the sniffs have tests.", "check-complete-strict": "Check if all the sniffs have unit tests and XML documentation.", "check-all": "Run all checks (lint, phpcs, feature completeness) and tests." From 1600503ea1a170d033ce5692802933809ea19f64 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 21 Oct 2025 12:08:50 -0300 Subject: [PATCH 14/48] Tests: fix testStdIn() methods to work with PHPCS 4.0 PHPCS 4.x removed special test-specific code from the Ruleset class that handled `Config::$sniffs` restrictions differently when `PHP_CODESNIFFER_IN_TESTS` was defined (see PHPCSStandards/PHP_CodeSniffer/ 996). This caused `testStdIn()` tests that used `$config->standards = array('WordPress')` to attempt loading the entire WordPress standard with all dependencies, resulting in errors about missing standards. The fix uses minimal ruleset XML files that only include the specific sniff being tested making the test pass with both PHPCS 3.x and 4.x. --- WordPress/Tests/Files/FileNameStdInTest.xml | 7 +++++++ WordPress/Tests/Files/FileNameUnitTest.php | 3 +-- WordPress/Tests/Utils/I18nTextDomainFixerStdInTest.xml | 7 +++++++ WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php | 3 +-- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 WordPress/Tests/Files/FileNameStdInTest.xml create mode 100644 WordPress/Tests/Utils/I18nTextDomainFixerStdInTest.xml diff --git a/WordPress/Tests/Files/FileNameStdInTest.xml b/WordPress/Tests/Files/FileNameStdInTest.xml new file mode 100644 index 0000000000..acb124382b --- /dev/null +++ b/WordPress/Tests/Files/FileNameStdInTest.xml @@ -0,0 +1,7 @@ + + + Ruleset for testing FileName with STDIN. + + + + diff --git a/WordPress/Tests/Files/FileNameUnitTest.php b/WordPress/Tests/Files/FileNameUnitTest.php index c671695de3..7444544fb2 100644 --- a/WordPress/Tests/Files/FileNameUnitTest.php +++ b/WordPress/Tests/Files/FileNameUnitTest.php @@ -185,8 +185,7 @@ public function getWarningList() { public function testStdIn() { $config = new ConfigDouble(); Helper::setConfigData( 'installed_paths', dirname( dirname( __DIR__ ) ), true, $config ); - $config->standards = array( 'WordPress' ); - $config->sniffs = array( 'WordPress.Files.FileName' ); + $config->standards = array( __DIR__ . '/FileNameStdInTest.xml' ); $ruleset = new Ruleset( $config ); diff --git a/WordPress/Tests/Utils/I18nTextDomainFixerStdInTest.xml b/WordPress/Tests/Utils/I18nTextDomainFixerStdInTest.xml new file mode 100644 index 0000000000..d158d19c59 --- /dev/null +++ b/WordPress/Tests/Utils/I18nTextDomainFixerStdInTest.xml @@ -0,0 +1,7 @@ + + + Ruleset for testing I18nTextDomainFixer with STDIN. + + + + diff --git a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php index 5674720986..0a4636281f 100644 --- a/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php +++ b/WordPress/Tests/Utils/I18nTextDomainFixerUnitTest.php @@ -214,8 +214,7 @@ public function getWarningList( $testFile = '' ) { public function testStdIn() { $config = new ConfigDouble(); Helper::setConfigData( 'installed_paths', dirname( dirname( __DIR__ ) ), true, $config ); - $config->standards = array( 'WordPress' ); - $config->sniffs = array( 'WordPress.Utils.I18nTextDomainFixer' ); + $config->standards = array( __DIR__ . '/I18nTextDomainFixerStdInTest.xml' ); $ruleset = new Ruleset( $config ); From ba351a2d9176c1151aecf338c6cabaa7a4ec5f68 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 14 Aug 2025 15:13:11 -0300 Subject: [PATCH 15/48] ContextHelper::is_in_function_call(): update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when determining the name of the function being called. --- WordPress/Helpers/ContextHelper.php | 35 ++++++++- .../IsInFunctionCallUnitTest.inc | 12 ++- .../IsInFunctionCallUnitTest.php | 78 ++++++++++++------- 3 files changed, 89 insertions(+), 36 deletions(-) diff --git a/WordPress/Helpers/ContextHelper.php b/WordPress/Helpers/ContextHelper.php index 1da3286715..c408df722b 100644 --- a/WordPress/Helpers/ContextHelper.php +++ b/WordPress/Helpers/ContextHelper.php @@ -113,6 +113,19 @@ final class ContextHelper { ), ); + /** + * List of tokens representing qualified names. + * + * @since 3.3.0 + * + * @var array + */ + private static $qualifiedNameTokens = array( + \T_NAME_FULLY_QUALIFIED => true, + \T_NAME_QUALIFIED => true, + \T_NAME_RELATIVE => true, + ); + /** * Check if a particular token acts - statically or non-statically - on an object. * @@ -186,7 +199,7 @@ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { * * For example: this function could be used to determine if the variable `$foo` is used * in a global function call to the function `is_foo()`. - * In that case, a call to this function would return the stackPtr to the T_STRING `is_foo` + * In that case, a call to this function would return the stackPtr to the name token `is_foo` * for code like: `is_foo( $foo, 'some_other_param' )`, while it would return `false` for * the following code `is_bar( $foo, 'some_other_param' )`. * @@ -215,7 +228,7 @@ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { * or only `strtolower( $var )`. * Defaults to `false`. * - * @return int|bool Stack pointer to the function call T_STRING token or false otherwise. + * @return int|bool Stack pointer to the function call name token or false otherwise. */ public static function is_in_function_call( File $phpcsFile, $stackPtr, array $valid_functions, $global_function = true, $allow_nested = false ) { $valid_functions = array_change_key_case( $valid_functions, \CASE_LOWER ); @@ -232,11 +245,25 @@ public static function is_in_function_call( File $phpcsFile, $stackPtr, array $v foreach ( $nested_parenthesis as $open => $close ) { $prev_non_empty = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $open - 1 ), null, true ); - if ( false === $prev_non_empty || \T_STRING !== $tokens[ $prev_non_empty ]['code'] ) { + if ( false === $prev_non_empty || isset( Collections::nameTokens()[ $tokens[ $prev_non_empty ]['code'] ] ) === false ) { continue; } - if ( isset( $valid_functions[ strtolower( $tokens[ $prev_non_empty ]['content'] ) ] ) === false ) { + $functionNameLC = \strtolower( $tokens[ $prev_non_empty ]['content'] ); + + if ( true === $global_function + && \T_NAME_FULLY_QUALIFIED === $tokens[ $prev_non_empty ]['code'] + ) { + $functionNameLC = \ltrim( $functionNameLC, '\\' ); + } + + if ( false === $global_function + && isset( self::$qualifiedNameTokens[ $tokens[ $prev_non_empty ]['code'] ] ) === true + ) { + $functionNameLC = \substr( $functionNameLC, \strrpos( $functionNameLC, '\\' ) + 1 ); + } + + if ( isset( $valid_functions[ $functionNameLC ] ) === false ) { if ( false === $allow_nested ) { // Function call encountered, but not to one of the allowed functions. return false; diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc index 8a20dade8a..746cff4d9f 100644 --- a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.inc @@ -31,11 +31,14 @@ another_function( /* testNestedInner */ valid_function1( /* testNestedInnerInsid * is `false`. */ -MyNamespace\/* testNamespacedFunction */valid_function1( /* testNamespacedFunctionInsideCall */ some_function() ); -\MyNamespace\/* testFullyQualifiedNamespacedFunction */valid_function1( +/* testNamespacedFunction */ +MyNamespace\valid_function1( /* testNamespacedFunctionInsideCall */ some_function() ); +/* testFullyQualifiedNamespacedFunction */ +\MyNamespace\valid_function1( /* testFullyQualifiedNamespacedFunctionInsideCall */ null ); -namespace\MyNamespace\/* testNamespaceRelativeFunction */valid_function2( +/* testNamespaceRelativeFunction */ +namespace\MyNamespace\valid_function2( /* testNamespaceRelativeFunctionInsideCall */ 3.14 ); MyClass::/* testStaticMethod */valid_function2( @@ -61,7 +64,8 @@ valid_function1( middle_function( inner_function( /* testNestedMultipleLevelsIns * `$global_function` is `false` and `$allow_nested` is `true`. */ -MyNamespace\/* testNestedBothNamespacedOuter */ valid_function1( +/* testNestedBothNamespacedOuter */ +MyNamespace\valid_function1( MyNamespace\other_function( /* testNestedBothNamespacedOuterInsideCall */ 'value' . $var ) diff --git a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php index c80188631b..49032ea50c 100644 --- a/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php +++ b/WordPress/Tests/Helpers/ContextHelper/IsInFunctionCallUnitTest.php @@ -9,6 +9,7 @@ namespace WordPressCS\WordPress\Tests\Helpers\ContextHelper; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\UtilityMethodTestCase; use WordPressCS\WordPress\Helpers\ContextHelper; @@ -206,16 +207,18 @@ public static function dataIsInFunctionCallShouldMatchRegardlessOfValidFunctions * @dataProvider dataIsInFunctionCallWithNestedTrue * @dataProvider dataIsInFunctionCallWithGlobalFalseNestedTrue * - * @param string $marker The comment which prefaces the target token. - * @param int|string $tokenType The token type to search for. - * @param bool $shouldMatch Whether `is_in_function_call()` should find a match. - * @param string|null $expectedMarker The comment which prefaces the expected function name - * in the test file (if a match is expected). - * @param array $params The is_in_function_call() parameter values. + * @param string $marker The comment which prefaces the target token. + * @param int|string $tokenType The token type to search for. + * @param bool $shouldMatch Whether `is_in_function_call()` should find a match. + * @param string|null $expectedMarker The comment which prefaces the expected function name + * in the test file (if a match is expected). + * @param int|string $expectedFunctionTokenType The token type for the expected function token. + * @param string|null $expectedFunctionContent The content for the expected function token. + * @param array $params The is_in_function_call() parameter values. * * @return void */ - public function testIsInFunctionCall( $marker, $tokenType, $shouldMatch, $expectedMarker, $params ) { + public function testIsInFunctionCall( $marker, $tokenType, $shouldMatch, $expectedMarker, $expectedFunctionTokenType, $expectedFunctionContent, $params ) { $insideFunctionPtr = $this->getTargetToken( $marker, $tokenType ); $result = ContextHelper::is_in_function_call( self::$phpcsFile, @@ -230,7 +233,7 @@ public function testIsInFunctionCall( $marker, $tokenType, $shouldMatch, $expect $expected = false; if ( true === $shouldMatch ) { - $expected = $this->getTargetToken( $expectedMarker, \T_STRING ); + $expected = $this->getTargetToken( $expectedMarker, $expectedFunctionTokenType, $expectedFunctionContent ); } $this->assertSame( $expected, $result ); @@ -330,6 +333,9 @@ public static function dataIsInFunctionCallWithGlobalFalseNestedTrue() { * @return array>> */ public static function dataIsInFunctionCall() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + $data = array( // Cases that should never match (regardless of parameters). 'plain_assignment' => array( @@ -372,10 +378,12 @@ public static function dataIsInFunctionCall() { 'expectedMarker' => '/* testUppercaseName */', ), 'fully_qualified' => array( - 'marker' => '/* testFullyQualifiedInsideCall */', - 'tokenType' => \T_LNUMBER, - 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, - 'expectedMarker' => '/* testFullyQualified */', + 'marker' => '/* testFullyQualifiedInsideCall */', + 'tokenType' => \T_LNUMBER, + 'shouldMatch' => self::EXPECT_ALWAYS_MATCH, + 'expectedMarker' => '/* testFullyQualified */', + 'expectedFunctionTokenType' => ( true === $is_phpcs_4 ? \T_NAME_FULLY_QUALIFIED : \T_STRING ), + 'expectedFunctionContent' => ( true === $is_phpcs_4 ? '\valid_function2' : null ), ), 'nested_inner' => array( 'marker' => '/* testNestedInnerInsideCall */', @@ -386,22 +394,28 @@ public static function dataIsInFunctionCall() { // Cases that match only when `$global_function` is `false`. 'namespaced_function' => array( - 'marker' => '/* testNamespacedFunctionInsideCall */', - 'tokenType' => \T_STRING, - 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, - 'expectedMarker' => '/* testNamespacedFunction */', + 'marker' => '/* testNamespacedFunctionInsideCall */', + 'tokenType' => \T_STRING, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testNamespacedFunction */', + 'expectedFunctionTokenType' => ( true === $is_phpcs_4 ? \T_NAME_QUALIFIED : \T_STRING ), + 'expectedFunctionContent' => ( true === $is_phpcs_4 ? 'MyNamespace\valid_function1' : 'valid_function1' ), ), 'fully_qualified_namespaced_function' => array( - 'marker' => '/* testFullyQualifiedNamespacedFunctionInsideCall */', - 'tokenType' => \T_NULL, - 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, - 'expectedMarker' => '/* testFullyQualifiedNamespacedFunction */', + 'marker' => '/* testFullyQualifiedNamespacedFunctionInsideCall */', + 'tokenType' => \T_NULL, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testFullyQualifiedNamespacedFunction */', + 'expectedFunctionTokenType' => ( true === $is_phpcs_4 ? \T_NAME_FULLY_QUALIFIED : \T_STRING ), + 'expectedFunctionContent' => ( true === $is_phpcs_4 ? '\MyNamespace\valid_function1' : 'valid_function1' ), ), 'namespace_relative_function' => array( - 'marker' => '/* testNamespaceRelativeFunctionInsideCall */', - 'tokenType' => \T_DNUMBER, - 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, - 'expectedMarker' => '/* testNamespaceRelativeFunction */', + 'marker' => '/* testNamespaceRelativeFunctionInsideCall */', + 'tokenType' => \T_DNUMBER, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_ONLY, + 'expectedMarker' => '/* testNamespaceRelativeFunction */', + 'expectedFunctionTokenType' => ( true === $is_phpcs_4 ? \T_NAME_RELATIVE : \T_STRING ), + 'expectedFunctionContent' => ( true === $is_phpcs_4 ? 'namespace\MyNamespace\valid_function2' : 'valid_function2' ), ), 'static_method' => array( 'marker' => '/* testStaticMethodInsideCall */', @@ -436,10 +450,12 @@ public static function dataIsInFunctionCall() { 'expectedMarker' => '/* testNestedMultipleLevels */', ), 'nested_both_namespaced_outer' => array( - 'marker' => '/* testNestedBothNamespacedOuterInsideCall */', - 'tokenType' => \T_STRING_CONCAT, - 'shouldMatch' => self::EXPECT_NON_GLOBAL_NESTED_ONLY, - 'expectedMarker' => '/* testNestedBothNamespacedOuter */', + 'marker' => '/* testNestedBothNamespacedOuterInsideCall */', + 'tokenType' => \T_STRING_CONCAT, + 'shouldMatch' => self::EXPECT_NON_GLOBAL_NESTED_ONLY, + 'expectedMarker' => '/* testNestedBothNamespacedOuter */', + 'expectedFunctionTokenType' => ( true === $is_phpcs_4 ? \T_NAME_QUALIFIED : \T_STRING ), + 'expectedFunctionContent' => ( true === $is_phpcs_4 ? 'MyNamespace\valid_function1' : 'valid_function1' ), ), // Safeguard: parentheses in other parameters should not confuse the method. @@ -455,6 +471,12 @@ public static function dataIsInFunctionCall() { if ( isset( $dataset['expectedMarker'] ) === false ) { $data[ $key ]['expectedMarker'] = null; } + if ( isset( $dataset['expectedFunctionTokenType'] ) === false ) { + $data[ $key ]['expectedFunctionTokenType'] = \T_STRING; + } + if ( isset( $dataset['expectedFunctionContent'] ) === false ) { + $data[ $key ]['expectedFunctionContent'] = null; + } } return $data; From 66c28eb713853a545de7302b562f5cedc6fe37f9 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 14 Aug 2025 15:55:10 -0300 Subject: [PATCH 16/48] ValidationHelper::is_validated(): update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking if a PHP native array key exists function was used. --- WordPress/Helpers/ValidationHelper.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/WordPress/Helpers/ValidationHelper.php b/WordPress/Helpers/ValidationHelper.php index d5a22d7d56..eb0ef679b0 100644 --- a/WordPress/Helpers/ValidationHelper.php +++ b/WordPress/Helpers/ValidationHelper.php @@ -35,11 +35,12 @@ final class ValidationHelper { * @var array */ private static $targets = array( - \T_ISSET => 'construct', - \T_EMPTY => 'construct', - \T_STRING => 'function_call', - \T_COALESCE => 'coalesce', - \T_COALESCE_EQUAL => 'coalesce', + \T_ISSET => 'construct', + \T_EMPTY => 'construct', + \T_STRING => 'function_call', + \T_NAME_FULLY_QUALIFIED => 'function_call', + \T_COALESCE => 'coalesce', + \T_COALESCE_EQUAL => 'coalesce', ); /** @@ -218,8 +219,14 @@ public static function is_validated( File $phpcsFile, $stackPtr, $array_keys = a break; case 'function_call': + $contentLC = \strtolower( $tokens[ $i ]['content'] ); + + if ( \T_NAME_FULLY_QUALIFIED === $tokens[ $i ]['code'] ) { + $contentLC = \ltrim( $contentLC, '\\' ); + } + // Only check calls to array_key_exists() and key_exists(). - if ( isset( self::$key_exists_functions[ strtolower( $tokens[ $i ]['content'] ) ] ) === false ) { + if ( isset( self::$key_exists_functions[ $contentLC ] ) === false ) { continue 2; } From 171339a7f988357fd75e57df05ca1c58c9975004 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Fri, 15 Aug 2025 16:28:19 -0300 Subject: [PATCH 17/48] ConstantsHelper::is_use_of_global_constant(): update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when determining if a constant is global or not. --- WordPress/Helpers/ConstantsHelper.php | 17 +++++++++++++--- .../IsUseOfGlobalConstantUnitTest.php | 20 +++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/WordPress/Helpers/ConstantsHelper.php b/WordPress/Helpers/ConstantsHelper.php index 320c6312f8..8ef2b77d23 100644 --- a/WordPress/Helpers/ConstantsHelper.php +++ b/WordPress/Helpers/ConstantsHelper.php @@ -34,7 +34,8 @@ final class ConstantsHelper { /** - * Determine whether an arbitrary T_STRING token is the use of a global constant. + * Determine whether an arbitrary T_STRING or T_NAME_FULLY_QUALIFIED token is the use of a + * global constant. * * @since 1.0.0 * @since 3.0.0 - Moved from the Sniff class to this class. @@ -42,7 +43,7 @@ final class ConstantsHelper { * - The `$phpcsFile` parameter was added. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the T_STRING token. + * @param int $stackPtr The position of the T_STRING or T_NAME_FULLY_QUALIFIED token. * * @return bool */ @@ -55,7 +56,9 @@ public static function is_use_of_global_constant( File $phpcsFile, $stackPtr ) { } // Is this one of the tokens this function handles ? - if ( \T_STRING !== $tokens[ $stackPtr ]['code'] ) { + if ( \T_STRING !== $tokens[ $stackPtr ]['code'] + && \T_NAME_FULLY_QUALIFIED !== $tokens[ $stackPtr ]['code'] + ) { return false; } @@ -91,6 +94,14 @@ public static function is_use_of_global_constant( File $phpcsFile, $stackPtr ) { return false; } + // If the token is a fully qualified name, ensure it does not include a namespace path. + if ( \T_NAME_FULLY_QUALIFIED === $tokens[ $stackPtr ]['code'] ) { + $trimmed = \ltrim( $tokens[ $stackPtr ]['content'], '\\' ); + if ( \strpos( $trimmed, '\\' ) !== false ) { + return false; + } + } + if ( ContextHelper::is_token_namespaced( $phpcsFile, $stackPtr ) === true ) { // Namespaced constant of the same name. return false; diff --git a/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php index 3cb23ab044..e75b4104fb 100644 --- a/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php +++ b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php @@ -9,6 +9,7 @@ namespace WordPressCS\WordPress\Tests\Helpers\ConstantsHelper; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\UtilityMethodTestCase; use WordPressCS\WordPress\Helpers\ConstantsHelper; @@ -62,6 +63,9 @@ public function testIsUseOfGlobalConstant( $testMarker, $expectedResult, $tokenT * @return array> */ public static function dataIsUseOfGlobalConstant() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + return array( // Cases that should return false. 'variable_assignment' => array( @@ -184,24 +188,26 @@ public static function dataIsUseOfGlobalConstant() { 'partially_qualified_namespaced_constant' => array( 'testMarker' => '/* testPartiallyQualifiedNamespacedConstant */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'PHP_OS', + 'tokenType' => ( true === $is_phpcs_4 ? \T_NAME_QUALIFIED : \T_STRING ), + 'tokenContent' => ( true === $is_phpcs_4 ? 'MyNamespace\PHP_OS' : 'PHP_OS' ), ), 'fully_qualified_namespaced_constant' => array( 'testMarker' => '/* testFullyQualifiedNamespacedConstant */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'PHP_OS', + 'tokenType' => ( true === $is_phpcs_4 ? \T_NAME_FULLY_QUALIFIED : \T_STRING ), + 'tokenContent' => ( true === $is_phpcs_4 ? '\MyNamespace\PHP_OS' : 'PHP_OS' ), ), 'namespace_relative_constant' => array( 'testMarker' => '/* testNamespaceRelativeConstant */', 'expectedResult' => false, + 'tokenType' => ( true === $is_phpcs_4 ? \T_NAME_RELATIVE : \T_STRING ), + 'tokenContent' => ( true === $is_phpcs_4 ? 'namespace\PHP_OS' : 'PHP_OS' ), ), 'namespace_relative_sub_constant' => array( 'testMarker' => '/* testNamespaceRelativeSubConstant */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'PHP_OS', + 'tokenType' => ( true === $is_phpcs_4 ? \T_NAME_RELATIVE : \T_STRING ), + 'tokenContent' => ( true === $is_phpcs_4 ? 'namespace\Sub\PHP_OS' : 'PHP_OS' ), ), 'class_constant_declaration' => array( 'testMarker' => '/* testClassConstantDeclaration */', @@ -224,6 +230,8 @@ public static function dataIsUseOfGlobalConstant() { 'echo_statement_fully_qualified' => array( 'testMarker' => '/* testEchoStatementFullyQualified */', 'expectedResult' => true, + 'tokenType' => ( true === $is_phpcs_4 ? \T_NAME_FULLY_QUALIFIED : \T_STRING ), + 'tokenContent' => ( true === $is_phpcs_4 ? '\PHP_OS' : 'PHP_OS' ), ), 'const_declaration' => array( 'testMarker' => '/* testConstDeclaration */', From 6436e6a9b338d3496e400d94e12f432667338a6b Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 18 Aug 2025 15:34:58 -0300 Subject: [PATCH 18/48] ContextHelper::is_token_namespaced(): update for PHPCS 4.0 Add support for the new namespaced name tokenization used in PHPCS 4.0. Since the tokenization changed between PHPCS 3 and 4, different logic is used depending on the PHPCS version. --- WordPress/Helpers/ContextHelper.php | 52 +++++++++++++++++++ .../IsTokenNamespacedUnitTest.php | 16 +++--- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/WordPress/Helpers/ContextHelper.php b/WordPress/Helpers/ContextHelper.php index c408df722b..880b864381 100644 --- a/WordPress/Helpers/ContextHelper.php +++ b/WordPress/Helpers/ContextHelper.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\Parentheses; use PHPCSUtils\Utils\PassedParameters; @@ -176,6 +177,28 @@ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { return false; } + $isPhpcs3 = version_compare( Helper::getVersion(), '3.99.99', '<=' ); + + if ( true === $isPhpcs3 ) { + return self::is_token_namespaced_phpcs3( $phpcsFile, $stackPtr ); + } + + return self::is_token_namespaced_phpcs4( $phpcsFile, $stackPtr ); + } + + /** + * Check if a particular token is prefixed with a namespace when running PHPCS 3. Different + * methods are necessary because the tokenization of namespaced names changed between PHPCS 3 + * and 4. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool + */ + private static function is_token_namespaced_phpcs3( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); if ( \T_NS_SEPARATOR !== $tokens[ $prev ]['code'] ) { @@ -192,6 +215,35 @@ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { return true; } + /** + * Check if a particular token is prefixed with a namespace when running PHPCS 4. Different + * methods are necessary because the tokenization of namespaced names changed between PHPCS 3 + * and 4. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The index of the token in the stack. + * + * @return bool + */ + private static function is_token_namespaced_phpcs4( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + if ( \T_NAME_QUALIFIED === $tokens[ $stackPtr ]['code'] + || \T_NAME_RELATIVE === $tokens[ $stackPtr ]['code'] + ) { + return true; + } + + // If the token is a fully qualified name, consider it as namespaced if it contains + // more than one namespace separator (i.e., not in the global namespace). + if ( \T_NAME_FULLY_QUALIFIED === $tokens[ $stackPtr ]['code'] + && \substr_count( $tokens[ $stackPtr ]['content'], '\\' ) > 1 ) { + return true; + } + + return false; + } + /** * Check if a token is (part of) a parameter for a function call to a select list of functions. * diff --git a/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.php b/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.php index 4da2da832c..0db72162c9 100644 --- a/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.php +++ b/WordPress/Tests/Helpers/ContextHelper/IsTokenNamespacedUnitTest.php @@ -9,6 +9,8 @@ namespace WordPressCS\WordPress\Tests\Helpers\ContextHelper; +use PHPCSUtils\BackCompat\Helper; +use PHPCSUtils\Tokens\Collections; use PHPCSUtils\TestUtils\UtilityMethodTestCase; use WordPressCS\WordPress\Helpers\ContextHelper; @@ -42,7 +44,7 @@ public function testIsTokenNamespacedReturnsFalseIfTokenDoesNotExist() { * @return void */ public function testIsTokenNamespaced( $testMarker, $expectedResult, $tokenContent ) { - $stackPtr = $this->getTargetToken( $testMarker, \T_STRING, $tokenContent ); + $stackPtr = $this->getTargetToken( $testMarker, Collections::nameTokens(), $tokenContent ); $result = ContextHelper::is_token_namespaced( self::$phpcsFile, $stackPtr ); $this->assertSame( $expectedResult, $result ); @@ -56,6 +58,8 @@ public function testIsTokenNamespaced( $testMarker, $expectedResult, $tokenConte * @return array> */ public static function dataIsTokenNamespaced() { + $isPhpcs3 = version_compare( Helper::getVersion(), '3.99.99', '<=' ); + return array( // Cases that should return false. 'unqualified' => array( @@ -66,29 +70,29 @@ public static function dataIsTokenNamespaced() { 'fully_qualified' => array( 'testMarker' => '/* testFullyQualified */', 'expectedResult' => false, - 'tokenContent' => 'MY_CONSTANT', + 'tokenContent' => ( $isPhpcs3 ? 'MY_CONSTANT' : '\MY_CONSTANT' ), ), // Cases that should return true. 'partially_qualified' => array( 'testMarker' => '/* testPartiallyQualified */', 'expectedResult' => true, - 'tokenContent' => 'my_function', + 'tokenContent' => ( $isPhpcs3 ? 'my_function' : 'MyNamespace\my_function' ), ), 'fully_qualified_namespaced' => array( 'testMarker' => '/* testFullyQualifiedNamespaced */', 'expectedResult' => true, - 'tokenContent' => 'MyClass', + 'tokenContent' => ( $isPhpcs3 ? 'MyClass' : '\MyNamespace\MyClass' ), ), 'namespace_relative' => array( 'testMarker' => '/* testNamespaceRelative */', 'expectedResult' => true, - 'tokenContent' => 'MY_CONSTANT', + 'tokenContent' => ( $isPhpcs3 ? 'MY_CONSTANT' : 'namespace\MY_CONSTANT' ), ), 'namespace_relative_sub' => array( 'testMarker' => '/* testNamespaceRelativeSub */', 'expectedResult' => true, - 'tokenContent' => 'my_function', + 'tokenContent' => ( $isPhpcs3 ? 'my_function' : 'namespace\Sub\my_function' ), ), ); } From 337674dc1770d670f528dc9de64dce6f03d8d6bd Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 28 Oct 2025 15:00:57 -0300 Subject: [PATCH 19/48] ContextHelper::is_in_array_comparison(): update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when searching for the function name in the ContextHelper::$arrayCompareFunctions array. --- WordPress/Helpers/ContextHelper.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WordPress/Helpers/ContextHelper.php b/WordPress/Helpers/ContextHelper.php index 880b864381..78e6e7df70 100644 --- a/WordPress/Helpers/ContextHelper.php +++ b/WordPress/Helpers/ContextHelper.php @@ -461,6 +461,11 @@ public static function is_in_array_comparison( File $phpcsFile, $stackPtr ) { $tokens = $phpcsFile->getTokens(); $function_name = strtolower( $tokens[ $function_ptr ]['content'] ); + + if ( \T_NAME_FULLY_QUALIFIED === $tokens[ $function_ptr ]['code'] ) { + $function_name = \ltrim( $function_name, '\\' ); + } + if ( true === self::$arrayCompareFunctions[ $function_name ] ) { return true; } From f4407c5005f11aed513e56751f3abda7f556c35b Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 7 Aug 2025 12:02:04 -0300 Subject: [PATCH 20/48] WPDBTrait::is_wpdb_method_call(): update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization of fully qualified calls to WPDB methods and also ensure that the method bails when handling `T_NAME_QUALIFIED` and `T_NAME_RELATIVE` tokens. As a side effect, this also fixes a bug where the method would not bail early when the token is not T_STRING, T_VARIABLE, or T_NAME_FULLY_QUALIFIED, which could cause it to return true for any token followed by an object operator and a target method call. --- WordPress/Helpers/WPDBTrait.php | 13 +++++++- .../WPDBTrait/IsWpdbMethodCallUnitTest.inc | 1 + .../WPDBTrait/IsWpdbMethodCallUnitTest.php | 33 +++++++++++++------ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/WordPress/Helpers/WPDBTrait.php b/WordPress/Helpers/WPDBTrait.php index 5843ea4225..05ff326ae0 100644 --- a/WordPress/Helpers/WPDBTrait.php +++ b/WordPress/Helpers/WPDBTrait.php @@ -65,9 +65,20 @@ final protected function is_wpdb_method_call( File $phpcsFile, $stackPtr, array return false; } + $contentLC = strtolower( $tokens[ $stackPtr ]['content'] ); + + // Not one of the possible token types. + if ( \T_VARIABLE !== $tokens[ $stackPtr ]['code'] + && \T_STRING !== $tokens[ $stackPtr ]['code'] + && \T_NAME_FULLY_QUALIFIED !== $tokens[ $stackPtr ]['code'] + ) { + return false; + } + // Check for wpdb. if ( ( \T_VARIABLE === $tokens[ $stackPtr ]['code'] && '$wpdb' !== $tokens[ $stackPtr ]['content'] ) - || ( \T_STRING === $tokens[ $stackPtr ]['code'] && 'wpdb' !== strtolower( $tokens[ $stackPtr ]['content'] ) ) + || ( \T_STRING === $tokens[ $stackPtr ]['code'] && 'wpdb' !== $contentLC ) + || ( \T_NAME_FULLY_QUALIFIED === $tokens[ $stackPtr ]['code'] && '\wpdb' !== $contentLC ) ) { return false; } diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc index 325025d736..de95c66632 100644 --- a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc @@ -31,6 +31,7 @@ MyNamespace\wpdb::esc_like /* testPartiallyQualifiedOpenParen */ ( $find ); namespace\wpdb::esc_like /* testNamespaceRelativeOpenParen */ ( $find ); // The method should start recognizing this as a WPDB method call once it can resolve relative namespaces since this test file is not namespaced. /* testNamespaceRelativeSub */ namespace\Sub\wpdb::esc_like /* testNamespaceRelativeSubOpenParen */ ( $find ); +my_function( /* testNotStringOrVariable */ )->prepare /* testNotStringOrVariableOpenParen */ ( 'SELECT * FROM table' ); /* * The below should be recognized as a $wpdb method call. diff --git a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php index 0f86c7bc05..0d438f7ce5 100644 --- a/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php @@ -9,6 +9,7 @@ namespace WordPressCS\WordPress\Tests\Helpers\WPDBTrait; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\UtilityMethodTestCase; /** @@ -188,6 +189,13 @@ public function testIsWpdbMethodCall( * @return array> */ public static function dataIsWpdbMethodCall() { + $isPhpcs3 = version_compare( Helper::getVersion(), '3.99.99', '<=' ); + $tokenContent = null; + + if ( $isPhpcs3 ) { + $tokenContent = 'wpdb'; + } + return array( // Cases that should return false. 'not_wpdb_variable' => array( @@ -221,26 +229,26 @@ public static function dataIsWpdbMethodCall() { 'partially_qualified' => array( 'testMarker' => '/* testPartiallyQualified */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'wpdb', + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_QUALIFIED, + 'tokenContent' => $tokenContent, ), 'fully_qualified_namespaced' => array( 'testMarker' => '/* testFullyQualifiedNamespaced */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'wpdb', + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_FULLY_QUALIFIED, + 'tokenContent' => $tokenContent, ), 'namespace_relative' => array( 'testMarker' => '/* testNamespaceRelative */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'wpdb', + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_RELATIVE, + 'tokenContent' => $tokenContent, ), 'namespace_relative_sub' => array( 'testMarker' => '/* testNamespaceRelativeSub */', 'expectedResult' => false, - 'tokenType' => \T_STRING, - 'tokenContent' => 'wpdb', + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_RELATIVE, + 'tokenContent' => $tokenContent, ), 'not_target_method' => array( 'testMarker' => '/* testNotTargetMethod */', @@ -263,6 +271,11 @@ public static function dataIsWpdbMethodCall() { 'hasMethodPtr' => false, 'openParenMarker' => '/* testVariableMethodCallOpenParen */', ), + 'not_string_or_variable' => array( + 'testMarker' => '/* testNotStringOrVariable */', + 'expectedResult' => false, + 'tokenType' => \T_CLOSE_PARENTHESIS, + ), // Cases that should return true. 'object_operator' => array( @@ -304,7 +317,7 @@ public static function dataIsWpdbMethodCall() { 'fully_qualified_global_lowercase' => array( 'testMarker' => '/* testFullyQualifiedGlobalLowercase */', 'expectedResult' => true, - 'tokenType' => \T_STRING, + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_FULLY_QUALIFIED, 'tokenContent' => null, 'hasMethodPtr' => true, 'openParenMarker' => '/* testFullyQualifiedGlobalLowercaseOpenParen */', @@ -313,7 +326,7 @@ public static function dataIsWpdbMethodCall() { 'fully_qualified_global_uppercase' => array( 'testMarker' => '/* testFullyQualifiedGlobalUppercase */', 'expectedResult' => true, - 'tokenType' => \T_STRING, + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_FULLY_QUALIFIED, 'tokenContent' => null, 'hasMethodPtr' => true, 'openParenMarker' => '/* testFullyQualifiedGlobalUppercaseOpenParen */', From cddd8d047250cbeaac8d5f1744b48ffcb060a394 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 16:22:55 -0300 Subject: [PATCH 21/48] FormattingFunctionsHelper::is_formatting_function(): update for PHPCS 4.0 Use `ltrim()` to strip the leading backslash from fully qualified function names, ensuring they are correctly recognized when tokenized as `T_NAME_FULLY_QUALIFIED` in PHPCS 4.0. --- WordPress/Helpers/FormattingFunctionsHelper.php | 2 +- .../IsFormattingFunctionUnitTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/Helpers/FormattingFunctionsHelper.php b/WordPress/Helpers/FormattingFunctionsHelper.php index e04298ab46..dafd4aa9d8 100644 --- a/WordPress/Helpers/FormattingFunctionsHelper.php +++ b/WordPress/Helpers/FormattingFunctionsHelper.php @@ -55,6 +55,6 @@ final class FormattingFunctionsHelper { * @return bool */ public static function is_formatting_function( $functionName ) { - return isset( self::$formattingFunctions[ strtolower( $functionName ) ] ); + return isset( self::$formattingFunctions[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } } diff --git a/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php b/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php index e567d0f7c3..66784c7607 100644 --- a/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/FormattingFunctionsHelper/IsFormattingFunctionUnitTest.php @@ -55,6 +55,10 @@ public static function dataIsFormattingFunction() { 'functionName' => 'iMpLoDe', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\sprintf', + 'expectedResult' => true, + ), 'not_a_formatting_function' => array( 'functionName' => 'printf', 'expectedResult' => false, From f2a24bbba0262328f8f3d8ddf1047c5c591d03e6 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 23 Mar 2026 16:57:19 +0000 Subject: [PATCH 22/48] PrintingFunctionsTrait::is_printing_function(): update for PHPCS 4.0 Use `ltrim()` to strip the leading backslash from fully qualified function names, ensuring they are correctly recognized when tokenized as `T_NAME_FULLY_QUALIFIED` in PHPCS 4.0. --- WordPress/Helpers/PrintingFunctionsTrait.php | 2 +- .../PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/Helpers/PrintingFunctionsTrait.php b/WordPress/Helpers/PrintingFunctionsTrait.php index edd6dbe211..508f720b28 100644 --- a/WordPress/Helpers/PrintingFunctionsTrait.php +++ b/WordPress/Helpers/PrintingFunctionsTrait.php @@ -117,6 +117,6 @@ final public function get_printing_functions() { * @return bool */ final public function is_printing_function( $functionName ) { - return isset( $this->get_printing_functions()[ strtolower( $functionName ) ] ); + return isset( $this->get_printing_functions()[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } } diff --git a/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php b/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php index b58bfc9cb2..01515dc26c 100644 --- a/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php @@ -75,6 +75,10 @@ public static function dataIsPrintingFunction() { 'functionName' => 'vPrInTf', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\wp_die', + 'expectedResult' => true, + ), 'not_a_printing_function' => array( 'functionName' => 'echo', 'expectedResult' => false, From d91c3cdcf766595fd3ef4f73d8f815d8a7f988d8 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 23 Mar 2026 16:57:23 +0000 Subject: [PATCH 23/48] WPHookHelper::get_hook_name_param(): update for PHPCS 4.0 Use `ltrim()` to strip the leading backslash from fully qualified function names, ensuring they are correctly recognized when tokenized as `T_NAME_FULLY_QUALIFIED` in PHPCS 4.0. --- WordPress/Helpers/WPHookHelper.php | 2 +- .../WPHookHelper/GetHookNameParamUnitTest.inc | 3 +++ .../WPHookHelper/GetHookNameParamUnitTest.php | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/WordPress/Helpers/WPHookHelper.php b/WordPress/Helpers/WPHookHelper.php index b5b4f4a133..f6ef0de08b 100644 --- a/WordPress/Helpers/WPHookHelper.php +++ b/WordPress/Helpers/WPHookHelper.php @@ -99,7 +99,7 @@ public static function get_functions( $include_deprecated = true ) { * for the format of the returned (single-dimensional) array. */ public static function get_hook_name_param( $function_name, array $parameters ) { - $function_lc = strtolower( $function_name ); + $function_lc = strtolower( ltrim( $function_name, '\\' ) ); if ( isset( self::$hookInvokeFunctions[ $function_lc ] ) === false ) { return false; } diff --git a/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc b/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc index e1992e34b8..83ee36e7ca 100644 --- a/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc +++ b/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.inc @@ -22,3 +22,6 @@ aPpLy_FiLtErS( 'my_filter', $value ); /* testNamedParameter */ do_action_deprecated( args: $args, hook_name: 'my_action' ); + +/* testFullyQualifiedName */ +\apply_filters( 'my_action', $value ); diff --git a/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.php b/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.php index e90cef82a3..de839931d7 100644 --- a/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.php +++ b/WordPress/Tests/Helpers/WPHookHelper/GetHookNameParamUnitTest.php @@ -9,6 +9,7 @@ namespace WordPressCS\WordPress\Tests\Helpers\WPHookHelper; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\UtilityMethodTestCase; use PHPCSUtils\Utils\PassedParameters; use WordPressCS\WordPress\Helpers\WPHookHelper; @@ -30,11 +31,12 @@ final class GetHookNameParamUnitTest extends UtilityMethodTestCase { * @param string $testMarker The comment which prefaces the target token in the test file. * @param string|false $expectedResult The raw content of the expected hook name parameter, * or `false` when no hook name parameter is expected. + * @param int|string $tokenType Optional. The token type to search for. Defaults to `T_STRING`. * * @return void */ - public function testGetHookNameParam( $testMarker, $expectedResult ) { - $stackPtr = $this->getTargetToken( $testMarker, \T_STRING ); + public function testGetHookNameParam( $testMarker, $expectedResult, $tokenType = \T_STRING ) { + $stackPtr = $this->getTargetToken( $testMarker, $tokenType ); $functionName = self::$phpcsFile->getTokens()[ $stackPtr ]['content']; $parameters = PassedParameters::getParameters( self::$phpcsFile, $stackPtr ); @@ -54,9 +56,12 @@ public function testGetHookNameParam( $testMarker, $expectedResult ) { * * @see testGetHookNameParam() * - * @return array> + * @return array> */ public static function dataGetHookNameParam() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + return array( 'not_a_hook_function' => array( 'testMarker' => '/* testNotAHookFunction */', @@ -78,6 +83,11 @@ public static function dataGetHookNameParam() { 'testMarker' => '/* testNamedParameter */', 'expectedResult' => "'my_action'", ), + 'fully_qualified_name' => array( + 'testMarker' => '/* testFullyQualifiedName */', + 'expectedResult' => "'my_action'", + 'tokenType' => ( true === $is_phpcs_4 ? \T_NAME_FULLY_QUALIFIED : \T_STRING ), + ), ); } } From bb916badb455774b5c8587901ad3515365820853 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 12:04:19 -0300 Subject: [PATCH 24/48] ArrayWalkingFunctionsHelper: update for PHPCS 4.0 --- WordPress/Helpers/ArrayWalkingFunctionsHelper.php | 7 ++++++- .../GetCallbackParameterUnitTest.inc | 6 ++++++ .../GetCallbackParameterUnitTest.php | 14 +++++++++++--- .../IsArrayWalkingFunctionUnitTest.php | 8 ++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/WordPress/Helpers/ArrayWalkingFunctionsHelper.php b/WordPress/Helpers/ArrayWalkingFunctionsHelper.php index 76623d7dd9..d82596e436 100644 --- a/WordPress/Helpers/ArrayWalkingFunctionsHelper.php +++ b/WordPress/Helpers/ArrayWalkingFunctionsHelper.php @@ -71,7 +71,7 @@ public static function get_functions() { * @return bool */ public static function is_array_walking_function( $functionName ) { - return isset( self::$arrayWalkingFunctions[ strtolower( $functionName ) ] ); + return isset( self::$arrayWalkingFunctions[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } /** @@ -94,6 +94,11 @@ public static function get_callback_parameter( File $phpcsFile, $stackPtr ) { } $functionName = strtolower( $tokens[ $stackPtr ]['content'] ); + + if ( \T_NAME_FULLY_QUALIFIED === $tokens[ $stackPtr ]['code'] ) { + $functionName = ltrim( $functionName, '\\' ); + } + if ( isset( self::$arrayWalkingFunctions[ $functionName ] ) === false ) { return false; } diff --git a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc index 731fa04732..f148fb9365 100644 --- a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc +++ b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.inc @@ -19,3 +19,9 @@ array_map( 'sanitize_text_field', $array ); /* testMapDeepMixedCase */ Map_Deep( $array, 'esc_html' ); + +/* testArrayMapFullyQualified */ +\array_map( 'esc_attr', $array ); + +/* testMapDeepFullyQualifiedUppercase */ +\MAP_DEEP( $array, 'wp_kses_post' ); diff --git a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.php b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.php index c59af9d8e9..18326a08ad 100644 --- a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.php +++ b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/GetCallbackParameterUnitTest.php @@ -66,20 +66,28 @@ public static function dataGetCallbackParameter() { 'testMarker' => '/* testNotArrayWalkingFunction */', 'expectedContent' => false, ), - 'callback_param_missing' => array( + 'callback_param_missing' => array( 'testMarker' => '/* testCallbackParamMissing */', 'expectedContent' => false, ), // Cases where the callback parameter should be returned. - 'array_map_callback' => array( + 'array_map_callback' => array( 'testMarker' => '/* testArrayMapCallback */', 'expectedContent' => "'sanitize_text_field'", ), - 'map_deep_mixed_case' => array( + 'map_deep_mixed_case' => array( 'testMarker' => '/* testMapDeepMixedCase */', 'expectedContent' => "'esc_html'", ), + 'array_map_fully_qualified' => array( + 'testMarker' => '/* testArrayMapFullyQualified */', + 'expectedContent' => "'esc_attr'", + ), + 'map_deep_fully_qualified_uppercase' => array( + 'testMarker' => '/* testMapDeepFullyQualifiedUppercase */', + 'expectedContent' => "'wp_kses_post'", + ), ); } } diff --git a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php index 16f3e0ac35..ab5e133cdd 100644 --- a/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/ArrayWalkingFunctionsHelper/IsArrayWalkingFunctionUnitTest.php @@ -55,6 +55,14 @@ public static function dataIsArrayWalkingFunction() { 'functionName' => 'mAp_DeEp', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\array_map', + 'expectedResult' => true, + ), + 'fully_qualified_name_uppercase' => array( + 'functionName' => '\MAP_DEEP', + 'expectedResult' => true, + ), 'not_an_array_walking_function' => array( 'functionName' => 'array_filter', 'expectedResult' => false, From e6f0268656e91d58b453e069327e03f86f60daf5 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 8 Dec 2025 15:01:11 -0300 Subject: [PATCH 25/48] UnslashingFunctionsHelper: update for PHPCS 4.0 --- WordPress/Helpers/UnslashingFunctionsHelper.php | 2 +- .../IsUnslashingFunctionUnitTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/Helpers/UnslashingFunctionsHelper.php b/WordPress/Helpers/UnslashingFunctionsHelper.php index 2946b3acac..ed8eef514a 100644 --- a/WordPress/Helpers/UnslashingFunctionsHelper.php +++ b/WordPress/Helpers/UnslashingFunctionsHelper.php @@ -54,6 +54,6 @@ public static function get_functions() { * @return bool */ public static function is_unslashing_function( $functionName ) { - return isset( self::$unslashingFunctions[ strtolower( $functionName ) ] ); + return isset( self::$unslashingFunctions[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } } diff --git a/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php b/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php index c797cb2443..a486b799fd 100644 --- a/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/UnslashingFunctionsHelper/IsUnslashingFunctionUnitTest.php @@ -55,6 +55,10 @@ public static function dataIsUnslashingFunction() { 'functionName' => 'sTrIpSlAsHeS_DeEp', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\stripslashes_from_strings_only', + 'expectedResult' => true, + ), 'not_an_unslashing_function' => array( 'functionName' => 'stripslashes', 'expectedResult' => false, From ce20111378757df2ce3f37efe5c9482ddab9b09f Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 12:06:48 -0300 Subject: [PATCH 26/48] SanitizationHelperTrait: update for PHPCS 4.0 --- WordPress/Helpers/SanitizationHelperTrait.php | 4 ++-- .../IsSanitizedUnitTest.php | 14 ++++++++++---- .../IsSanitizingAndUnslashingFunctionUnitTest.php | 4 ++++ .../IsSanitizingFunctionUnitTest.php | 4 ++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/WordPress/Helpers/SanitizationHelperTrait.php b/WordPress/Helpers/SanitizationHelperTrait.php index 31661d169e..d8e0077677 100644 --- a/WordPress/Helpers/SanitizationHelperTrait.php +++ b/WordPress/Helpers/SanitizationHelperTrait.php @@ -239,7 +239,7 @@ final public function get_sanitizing_and_unslashing_functions() { * @return bool */ final public function is_sanitizing_function( $functionName ) { - return isset( $this->get_sanitizing_functions()[ strtolower( $functionName ) ] ); + return isset( $this->get_sanitizing_functions()[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } /** @@ -252,7 +252,7 @@ final public function is_sanitizing_function( $functionName ) { * @return bool */ final public function is_sanitizing_and_unslashing_function( $functionName ) { - return isset( $this->get_sanitizing_and_unslashing_functions()[ strtolower( $functionName ) ] ); + return isset( $this->get_sanitizing_and_unslashing_functions()[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } /** diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php index aeebab96e8..34271673d7 100644 --- a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php @@ -9,6 +9,7 @@ namespace WordPressCS\WordPress\Tests\Helpers\SanitizationHelperTrait; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\TestUtils\UtilityMethodTestCase; use WordPressCS\WordPress\Helpers\SanitizationHelperTrait; @@ -78,6 +79,9 @@ public function testIsSanitized( $testMarker, $expectedResult, $tokenType = \T_V * @return array> */ public static function dataIsSanitized() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + return array( // Cases where false should be returned. 'not_within_function_call' => array( @@ -168,21 +172,23 @@ public static function dataIsSanitized() { 'testMarker' => '/* testFullyQualifiedGlobalUnslashSanitized */', 'expectedResult' => true, ), + + // Namespaced inner unslash calls: true in PHPCS 3.x, false in 4.x. See the test case file and #2665. 'partially_qualified_unslash_sanitized' => array( 'testMarker' => '/* testPartiallyQualifiedUnslashSanitized */', - 'expectedResult' => true, + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : true, ), 'fully_qualified_namespaced_unslash_sanitized' => array( 'testMarker' => '/* testFullyQualifiedNamespacedUnslashSanitized */', - 'expectedResult' => true, + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : true, ), 'namespace_relative_unslash_sanitized' => array( 'testMarker' => '/* testNamespaceRelativeUnslashSanitized */', - 'expectedResult' => true, + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : true, ), 'namespace_relative_sub_unslash_sanitized' => array( 'testMarker' => '/* testNamespaceRelativeSubUnslashSanitized */', - 'expectedResult' => true, + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : true, ), ); } diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php index 6d0b3685d8..e83e97c9f9 100644 --- a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php @@ -73,6 +73,10 @@ public static function dataIsSanitizingAndUnslashingFunction() { 'functionName' => 'iNtVaL', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\boolval', + 'expectedResult' => true, + ), 'not_a_sanitizing_and_unslashing_function' => array( 'functionName' => 'sanitize_text_field', 'expectedResult' => false, diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php index 03aabf9097..e585e40a79 100644 --- a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php @@ -73,6 +73,10 @@ public static function dataIsSanitizingFunction() { 'functionName' => 'SaNiTiZe_EmAiL', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\sanitize_text_field', + 'expectedResult' => true, + ), 'not_a_sanitizing_function' => array( 'functionName' => 'printf', 'expectedResult' => false, From 9e7aaf23783ee152114282c983bfdd084509fff9 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 16:22:20 -0300 Subject: [PATCH 27/48] EscapingFunctionsTrait: update for PHPCS 4.0 The `is_escaping_function()` and `is_auto_escaped_function()` methods now use `ltrim()` to strip the leading backslash from fully qualified function names, ensuring they are correctly recognized when tokenized as `T_NAME_FULLY_QUALIFIED` in PHPCS 4.0. --- WordPress/Helpers/EscapingFunctionsTrait.php | 4 ++-- .../EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php | 4 ++++ .../EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/WordPress/Helpers/EscapingFunctionsTrait.php b/WordPress/Helpers/EscapingFunctionsTrait.php index b3fbf61d57..18470ced4a 100644 --- a/WordPress/Helpers/EscapingFunctionsTrait.php +++ b/WordPress/Helpers/EscapingFunctionsTrait.php @@ -226,7 +226,7 @@ final public function is_escaping_function( $functionName ) { $this->addedCustomEscapingFunctions['escape'] = $this->customEscapingFunctions; } - return isset( $this->allEscapingFunctions[ strtolower( $functionName ) ] ); + return isset( $this->allEscapingFunctions[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } /** @@ -250,6 +250,6 @@ final public function is_auto_escaped_function( $functionName ) { $this->addedCustomEscapingFunctions['autoescape'] = $this->customAutoEscapedFunctions; } - return isset( $this->allAutoEscapedFunctions[ strtolower( $functionName ) ] ); + return isset( $this->allAutoEscapedFunctions[ strtolower( ltrim( $functionName, '\\' ) ) ] ); } } diff --git a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php index 12d8fddb99..78cfb83a80 100644 --- a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php @@ -75,6 +75,10 @@ public static function dataIsAutoEscapedFunction() { 'functionName' => 'bOdY_ClAsS', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\allowed_tags', + 'expectedResult' => true, + ), 'not_an_auto_escaped_function' => array( 'functionName' => 'esc_html', 'expectedResult' => false, diff --git a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php index 013876030f..d414081391 100644 --- a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php +++ b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php @@ -75,6 +75,10 @@ public static function dataIsEscapingFunction() { 'functionName' => 'eSc_AtTr', 'expectedResult' => true, ), + 'fully_qualified_name' => array( + 'functionName' => '\esc_url', + 'expectedResult' => true, + ), 'not_an_escaping_function' => array( 'functionName' => 'printf', 'expectedResult' => false, From f9e4031a429d941ac4da98f517d2a85c8902202e Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 13 Aug 2025 11:49:27 -0300 Subject: [PATCH 28/48] AbstractFunctionRestrictions: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking if a given fully qualified function is a target function. In those cases, the leading backslash is removed from the token content to allow for the function name comparisons to continue working as they worked in PHPCS 3.x. --- WordPress/AbstractFunctionParameterSniff.php | 3 ++- .../AbstractFunctionRestrictionsSniff.php | 22 +++++++++++++++---- .../Sniffs/Security/EscapeOutputSniff.php | 3 ++- .../Sniffs/WP/AlternativeFunctionsSniff.php | 3 ++- .../Sniffs/WP/DeprecatedFunctionsSniff.php | 3 ++- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/WordPress/AbstractFunctionParameterSniff.php b/WordPress/AbstractFunctionParameterSniff.php index 7b369583ce..e9a2226f1f 100644 --- a/WordPress/AbstractFunctionParameterSniff.php +++ b/WordPress/AbstractFunctionParameterSniff.php @@ -62,7 +62,8 @@ public function getGroups() { * @param int $stackPtr The position of the current token in the stack. * @param string $group_name The name of the group which was matched. * @param string $matched_content The token content (function name) which was matched - * in lowercase. + * in lowercase. For T_NAME_FULLY_QUALIFIED tokens, + * the leading backslash is removed. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. diff --git a/WordPress/AbstractFunctionRestrictionsSniff.php b/WordPress/AbstractFunctionRestrictionsSniff.php index 7f300cbc3f..b9961efc06 100644 --- a/WordPress/AbstractFunctionRestrictionsSniff.php +++ b/WordPress/AbstractFunctionRestrictionsSniff.php @@ -125,6 +125,7 @@ public function register() { return array( \T_STRING, + \T_NAME_FULLY_QUALIFIED, ); } @@ -201,10 +202,16 @@ public function process_token( $stackPtr ) { return; } - // Preliminary check. If the content of the T_STRING is not one of the functions we're + $content = $this->tokens[ $stackPtr ]['content']; + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $stackPtr ]['code'] ) { + $content = \ltrim( $content, '\\' ); + } + + // Preliminary check. If the content of the name token is not one of the functions we're // looking for, we can bow out before doing the heavy lifting of checking whether // this is a function call. - if ( preg_match( $this->prelim_check_regex, $this->tokens[ $stackPtr ]['content'] ) !== 1 ) { + if ( preg_match( $this->prelim_check_regex, $content ) !== 1 ) { return; } @@ -286,7 +293,13 @@ public function is_targetted_token( $stackPtr ) { * normal file processing. */ public function check_for_matches( $stackPtr ) { - $token_content = strtolower( $this->tokens[ $stackPtr ]['content'] ); + $content = $this->tokens[ $stackPtr ]['content']; + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $stackPtr ]['code'] ) { + $content = \ltrim( $content, '\\' ); + } + + $token_content = strtolower( $content ); $skip_to = array(); foreach ( $this->groups as $groupName => $group ) { @@ -319,7 +332,8 @@ public function check_for_matches( $stackPtr ) { * @param int $stackPtr The position of the current token in the stack. * @param string $group_name The name of the group which was matched. * @param string $matched_content The token content (function name) which was matched - * in lowercase. + * in lowercase. For T_NAME_FULLY_QUALIFIED tokens, + * the leading backslash is removed. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. diff --git a/WordPress/Sniffs/Security/EscapeOutputSniff.php b/WordPress/Sniffs/Security/EscapeOutputSniff.php index b05732e823..c021a26239 100644 --- a/WordPress/Sniffs/Security/EscapeOutputSniff.php +++ b/WordPress/Sniffs/Security/EscapeOutputSniff.php @@ -367,7 +367,8 @@ public function process_token( $stackPtr ) { * @param int $stackPtr The position of the current token in the stack. * @param string $group_name The name of the group which was matched. * @param string $matched_content The token content (function name) which was matched - * in lowercase. + * in lowercase. For T_NAME_FULLY_QUALIFIED tokens, + * the leading backslash is removed. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. diff --git a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php index 9a0a2a9d03..fda800cf56 100644 --- a/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php +++ b/WordPress/Sniffs/WP/AlternativeFunctionsSniff.php @@ -213,7 +213,8 @@ public function getGroups() { * @param int $stackPtr The position of the current token in the stack. * @param string $group_name The name of the group which was matched. * @param string $matched_content The token content (function name) which was matched - * in lowercase. + * in lowercase. For T_NAME_FULLY_QUALIFIED tokens, + * the leading backslash is removed. * * @return int|void Integer stack pointer to skip forward or void to continue * normal file processing. diff --git a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php index 76647d9aff..d6fbefc411 100644 --- a/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php +++ b/WordPress/Sniffs/WP/DeprecatedFunctionsSniff.php @@ -1759,7 +1759,8 @@ public function getGroups() { * @param string $group_name The name of the group which was matched. Will * always be 'deprecated_functions'. * @param string $matched_content The token content (function name) which was matched - * in lowercase. + * in lowercase. For T_NAME_FULLY_QUALIFIED tokens, + * the leading backslash is removed. * * @return void */ From 76dad455bc52f67fba8a6a608f6e437487d6b203 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 13 Aug 2025 15:04:27 -0300 Subject: [PATCH 29/48] AbstractClassRestrictions: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking if a given token is a target token. --- WordPress/AbstractClassRestrictionsSniff.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/WordPress/AbstractClassRestrictionsSniff.php b/WordPress/AbstractClassRestrictionsSniff.php index d380522b3a..1fd4d672a6 100644 --- a/WordPress/AbstractClassRestrictionsSniff.php +++ b/WordPress/AbstractClassRestrictionsSniff.php @@ -10,6 +10,7 @@ namespace WordPressCS\WordPress; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\BackCompat\Helper; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\GetTokensAsString; use PHPCSUtils\Utils\Namespaces; @@ -145,13 +146,20 @@ public function is_targetted_token( $stackPtr ) { if ( \T_DOUBLE_COLON === $token['code'] ) { $nameEnd = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); - if ( \T_STRING !== $this->tokens[ $nameEnd ]['code'] ) { + if ( isset( Collections::nameTokens()[ $this->tokens[ $nameEnd ]['code'] ] ) === false ) { // Hierarchy keyword or object stored in variable. return false; } - $nameStart = ( $this->phpcsFile->findPrevious( Collections::namespacedNameTokens(), ( $nameEnd - 1 ), null, true ) + 1 ); - $classname = GetTokensAsString::noEmpties( $this->phpcsFile, $nameStart, $nameEnd ); + $classname = $this->tokens[ $nameEnd ]['content']; + $nameStart = $nameEnd; + + if ( \version_compare( Helper::getVersion(), '3.99.99', '<=' ) === true ) { + // For PHPCS 3.x, a namespaced class name is split over multiple tokens so it is necessary to combine then to get the class name. + $nameStart = ( $this->phpcsFile->findPrevious( Collections::namespacedNameTokens(), ( $nameEnd - 1 ), null, true ) + 1 ); + $classname = GetTokensAsString::noEmpties( $this->phpcsFile, $nameStart, $nameEnd ); + } + $classname = $this->get_namespaced_classname( $classname, ( $nameStart - 1 ) ); } From 0a82bb4046f3ac75f489432451f4f8a8b9788b22 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 7 Aug 2025 10:42:26 -0300 Subject: [PATCH 30/48] DB/DirectDatabaseQuery: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization of namespaced function calls to this sniff when searching for cache function calls. --- WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php b/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php index 3271a41874..203154a6d1 100644 --- a/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php +++ b/WordPress/Sniffs/DB/DirectDatabaseQuerySniff.php @@ -238,7 +238,7 @@ public function process_token( $stackPtr ) { $scopeEnd = $this->tokens[ $scope_function ]['scope_closer']; for ( $i = ( $scopeStart + 1 ); $i < $scopeEnd; $i++ ) { - if ( \T_STRING === $this->tokens[ $i ]['code'] ) { + if ( isset( Collections::nameTokens()[ $this->tokens[ $i ]['code'] ] ) ) { $nextNonEmpty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); if ( \T_OPEN_PARENTHESIS !== $this->tokens[ $nextNonEmpty ]['code'] ) { @@ -247,6 +247,11 @@ public function process_token( $stackPtr ) { $content = strtolower( $this->tokens[ $i ]['content'] ); + if ( strpos( $content, '\\' ) !== false ) { + // Namespaced function call, get only the function name. + $content = substr( $content, strrpos( $content, '\\' ) + 1 ); + } + if ( isset( $this->cacheDeleteFunctions[ $content ] ) ) { if ( \in_array( $method, array( 'query', 'update', 'replace', 'delete' ), true ) ) { From b3c6b4d9672b510ac06ffea38842b332e967668e Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 12 Aug 2025 14:53:59 -0300 Subject: [PATCH 31/48] WP/GlobalVariablesOverride: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization of fully qualified function/method calls when checking if the sniff can determine the key used to retrieve a value from the `$GLOBALS` array. The sniff was already handling this correct by chance due to a check that happens later in the code execution to bow out if `$var_name` is empty. --- WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php b/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php index 934c3e686b..985f5d281c 100644 --- a/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php +++ b/WordPress/Sniffs/WP/GlobalVariablesOverrideSniff.php @@ -219,14 +219,15 @@ protected function process_variable_assignment( $stackPtr, $in_list = false ) { $var_name = ''; $start = ( $bracketPtr + 1 ); for ( $ptr = $start; $ptr < $this->tokens[ $bracketPtr ]['bracket_closer']; $ptr++ ) { + $ignored_tokens = Collections::nameTokens(); + $ignored_tokens[ \T_VARIABLE ] = \T_VARIABLE; + $ignored_tokens[ \T_DOUBLE_QUOTED_STRING ] = \T_DOUBLE_QUOTED_STRING; + /* * If the globals array key contains a variable, constant, function call * or interpolated variable, bow out. */ - if ( \T_VARIABLE === $this->tokens[ $ptr ]['code'] - || \T_STRING === $this->tokens[ $ptr ]['code'] - || \T_DOUBLE_QUOTED_STRING === $this->tokens[ $ptr ]['code'] - ) { + if ( isset( $ignored_tokens[ $this->tokens[ $ptr ]['code'] ] ) === true ) { return; } From 4780acafae4b700c8d6ac0eaf333db9792a19715 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 12 Aug 2025 15:37:14 -0300 Subject: [PATCH 32/48] Security/NonceVerification: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization of fully qualified function calls when checking if a nonce verification function is used in the code. --- WordPress/Sniffs/Security/NonceVerificationSniff.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/Sniffs/Security/NonceVerificationSniff.php b/WordPress/Sniffs/Security/NonceVerificationSniff.php index 06b941fc79..2da979d31a 100644 --- a/WordPress/Sniffs/Security/NonceVerificationSniff.php +++ b/WordPress/Sniffs/Security/NonceVerificationSniff.php @@ -305,12 +305,16 @@ private function has_nonce_check( $stackPtr, array $cache_keys, $allow_nonce_aft } // If this isn't a function name, skip it. - if ( \T_STRING !== $this->tokens[ $i ]['code'] ) { + if ( \T_STRING !== $this->tokens[ $i ]['code'] && \T_NAME_FULLY_QUALIFIED !== $this->tokens[ $i ]['code'] ) { continue; } $content_lc = \strtolower( $this->tokens[ $i ]['content'] ); + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $i ]['code'] ) { + $content_lc = \ltrim( $content_lc, '\\' ); + } + // If this is one of the nonce verification functions, we can bail out. if ( isset( $this->nonceVerificationFunctions[ $content_lc ] ) ) { /* From 7cf5923b79ff89c9770d80db1832898891ca1c16 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 13 Aug 2025 11:12:19 -0300 Subject: [PATCH 33/48] NamingConventions/ValidHookName: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when skipping over function calls while checking the hook name. --- WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php b/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php index 55dee3bd65..42936fca9e 100644 --- a/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php +++ b/WordPress/Sniffs/NamingConventions/ValidHookNameSniff.php @@ -10,6 +10,7 @@ namespace WordPressCS\WordPress\Sniffs\NamingConventions; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\TextStrings; use WordPressCS\WordPress\AbstractFunctionParameterSniff; use WordPressCS\WordPress\Helpers\WPHookHelper; @@ -137,7 +138,7 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p // Skip over parameters passed to function calls. if ( \T_OPEN_PARENTHESIS === $this->tokens[ $i ]['code'] - && ( \T_STRING === $this->tokens[ $last_non_empty ]['code'] + && ( isset( Collections::nameTokens()[ $this->tokens[ $last_non_empty ]['code'] ] ) || \T_VARIABLE === $this->tokens[ $last_non_empty ]['code'] ) && isset( $this->tokens[ $i ]['parenthesis_closer'] ) ) { From ee9048d5bcc71dfaac45f4004200cfd57e8b2f69 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 13 Aug 2025 11:54:15 -0300 Subject: [PATCH 34/48] NamingConventions/PrefixAllGlobals: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking for calls to `define()` and hook functions to handle fully qualified calls correctly. --- WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php index 85c79f2c09..df613e6f17 100644 --- a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php +++ b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php @@ -468,6 +468,7 @@ public function register() { $parent = parent::register(); if ( ! empty( $parent ) ) { $targets[] = \T_STRING; + $targets[] = \T_NAME_FULLY_QUALIFIED; } return $targets; @@ -560,7 +561,9 @@ public function process_token( $stackPtr ) { return $this->tokens[ $stackPtr ]['scope_closer']; } - if ( \T_STRING === $this->tokens[ $stackPtr ]['code'] ) { + if ( \T_STRING === $this->tokens[ $stackPtr ]['code'] + || \T_NAME_FULLY_QUALIFIED === $this->tokens[ $stackPtr ]['code'] + ) { // Disallow excluding function groups for this sniff. $this->exclude = array(); From e97318fd5be33189484b5dacfa04663f98a3e04d Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Wed, 13 Aug 2025 15:24:27 -0300 Subject: [PATCH 35/48] PHP/NoSilencedErrors: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking function calls after the `T_ASPERAND` token. --- WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php index 06cc8879be..b57a22de7f 100644 --- a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php +++ b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCFile; +use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\GetTokensAsString; use WordPressCS\WordPress\Helpers\RulesetPropertyHelper; use WordPressCS\WordPress\Sniff; @@ -193,10 +194,15 @@ public function process_token( $stackPtr ) { * to allow the metrics to be more informative. */ $next_non_empty = $this->phpcsFile->findNext( $this->empty_tokens, ( $stackPtr + 1 ), null, true, null, true ); - if ( false !== $next_non_empty && \T_STRING === $this->tokens[ $next_non_empty ]['code'] ) { + if ( false !== $next_non_empty && isset( Collections::nameTokens()[ $this->tokens[ $next_non_empty ]['code'] ] ) === true ) { $has_parenthesis = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next_non_empty + 1 ), null, true, null, true ); if ( false !== $has_parenthesis && \T_OPEN_PARENTHESIS === $this->tokens[ $has_parenthesis ]['code'] ) { $function_name = strtolower( $this->tokens[ $next_non_empty ]['content'] ); + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $next_non_empty ]['code'] ) { + $function_name = \ltrim( $function_name, '\\' ); + } + if ( ( true === $this->usePHPFunctionsList && isset( $this->allowedFunctionsList[ $function_name ] ) === true ) || ( ! empty( $this->customAllowedFunctionsList ) From b4ba52dbf48396cae34faf3ea6ac4f9efdd9997a Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 14 Aug 2025 10:07:34 -0300 Subject: [PATCH 36/48] WP/CronInterval: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking if a WP time constant was used and when checking the name of a first-class callable. It also updates a few test expectation to account for the difference in the sniff behavior when running with PHPCS 3.x and 4.x. --- WordPress/Sniffs/WP/CronIntervalSniff.php | 25 +++++++++++++++++---- WordPress/Tests/WP/CronIntervalUnitTest.inc | 14 ++++++------ WordPress/Tests/WP/CronIntervalUnitTest.php | 8 +++++++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/WordPress/Sniffs/WP/CronIntervalSniff.php b/WordPress/Sniffs/WP/CronIntervalSniff.php index 886eee073f..e0dd51cc5b 100644 --- a/WordPress/Sniffs/WP/CronIntervalSniff.php +++ b/WordPress/Sniffs/WP/CronIntervalSniff.php @@ -152,9 +152,20 @@ public function process_token( $stackPtr ) { && ( false !== $after && \T_CLOSE_PARENTHESIS === $this->tokens[ $after ]['code'] ) ) { // Ok, now see if we can find the function name. - $beforeOpen = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $before - 1 ), null, true ); - if ( false !== $beforeOpen && \T_STRING === $this->tokens[ $beforeOpen ]['code'] ) { - $found_function = $this->find_function_by_name( $this->tokens[ $beforeOpen ]['content'] ); + $beforeOpen = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $before - 1 ), null, true ); + $global_function_tokens = array( + \T_STRING => true, + \T_NAME_FULLY_QUALIFIED => true, + ); + + if ( false !== $beforeOpen && isset( $global_function_tokens[ $this->tokens[ $beforeOpen ]['code'] ] ) ) { + $function_name = $this->tokens[ $beforeOpen ]['content']; + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $beforeOpen ]['code'] ) { + $function_name = \ltrim( $function_name, '\\' ); + } + + $found_function = $this->find_function_by_name( $function_name ); if ( false !== $found_function ) { $functionPtr = $found_function; } @@ -233,7 +244,13 @@ public function process_token( $stackPtr ) { continue; } - $value .= $this->tokens[ $j ]['content']; + $content = $this->tokens[ $j ]['content']; + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $j ]['code'] ) { + $content = \ltrim( $content, '\\' ); + } + + $value .= $content; } if ( $parentheses_count > 0 ) { diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.inc b/WordPress/Tests/WP/CronIntervalUnitTest.inc index b4b81fc714..9e2011c92d 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.inc +++ b/WordPress/Tests/WP/CronIntervalUnitTest.inc @@ -316,7 +316,7 @@ function first_class_weekly_schedule( $schedules ) { } add_filter( 'cron_schedules', 'first_class_weekly_schedule'(...)); // Ok: > 15 min. add_filter( 'cron_schedules', first_class_weekly_schedule(...)); // Ok: > 15 min. -add_filter( 'cron_schedules', namespace\first_class_weekly_schedule(...)); // Ok: > 15 min. +add_filter( 'cron_schedules', namespace\first_class_weekly_schedule(...)); // Ok: > 15 min in PHPCS 3.x and warning in PHPCS 4.x. Should become ok in PHPCS 4.x when the sniff is able to resolve relative namespaces. function first_class_six_min_schedule( $schedules ) { $schedules['every_6_mins'] = array( @@ -332,15 +332,15 @@ add_filter( 'cron_schedules', \FIRST_CLASS_SIX_MIN_SCHEDULE(...)); // Warning: 6 add_filter( 'cron_schedules', namespace\first_class_six_min_schedule(...)); // Warning: 6 min. /* - * The tests below document the current behavior of the sniff, even though they are false negatives. The sniff treats - * the first-class callable examples below as if referencing the global function first_class_six_min_schedule() - * and not a namespaced function with the same name. + * The tests below document the current behavior of the sniff, even though they are false negatives when running with + * PHPCS 3.x (with PHPCS 4.x this problem doesn't occur). The sniff treats the first-class callable examples below as if + * referencing the global function first_class_six_min_schedule() and not a namespaced function with the same name. * * Related to: https://github.com/WordPress/WordPress-Coding-Standards/issues/2644. */ -add_filter( 'cron_schedules', MyNamespace\first_class_weekly_schedule(...)); // False negative - Ok: > 15 min, but should be marked `ChangeDetected`. -add_filter( 'cron_schedules', \MyNamespace\first_class_weekly_schedule(...)); // False negative - Ok: > 15 min, but should be marked `ChangeDetected`. -add_filter( 'cron_schedules', namespace\Sub\first_class_weekly_schedule(...)); // False negative - Ok: > 15 min, but should be marked `ChangeDetected`. +add_filter( 'cron_schedules', MyNamespace\first_class_weekly_schedule(...)); // PHPCS 3.x false negative - Ok: > 15 min, but should be marked `ChangeDetected`. +add_filter( 'cron_schedules', \MyNamespace\first_class_weekly_schedule(...)); // PHPCS 3.x false negative - Ok: > 15 min, but should be marked `ChangeDetected`. +add_filter( 'cron_schedules', namespace\Sub\first_class_weekly_schedule(...)); // PHPCS 3.x false negative - Ok: > 15 min, but should be marked `ChangeDetected`. /* * The tests below document the current behavior of the sniff, even though they are false positives. The sniff treats diff --git a/WordPress/Tests/WP/CronIntervalUnitTest.php b/WordPress/Tests/WP/CronIntervalUnitTest.php index e2e2d4291b..cd01753cca 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.php +++ b/WordPress/Tests/WP/CronIntervalUnitTest.php @@ -10,6 +10,7 @@ namespace WordPressCS\WordPress\Tests\WP; use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the CronInterval sniff. @@ -37,6 +38,9 @@ public function getErrorList() { * @return array Key is the line number, value is the number of expected warnings. */ public function getWarningList() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + return array( 12 => 1, 17 => 1, @@ -65,11 +69,15 @@ public function getWarningList() { 286 => 1, 288 => 1, 290 => 1, + 319 => ( true === $is_phpcs_4 ) ? 1 : 0, 328 => 1, 329 => 1, 330 => 1, 331 => 1, 332 => 1, + 341 => ( true === $is_phpcs_4 ) ? 1 : 0, + 342 => ( true === $is_phpcs_4 ) ? 1 : 0, + 343 => ( true === $is_phpcs_4 ) ? 1 : 0, 352 => 1, 353 => 1, 354 => 1, From 5b3b0ef9c3e85707b9ac97a43b971399e8e6d5c6 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 18 Aug 2025 16:06:25 -0300 Subject: [PATCH 37/48] WP/DiscouragedConstants: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization when checking the name of a given constants to see if it is one of the discouraged constants and when checking if `define()` was called. --- WordPress/Sniffs/WP/DiscouragedConstantsSniff.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php index bd3486205a..d0027614c6 100644 --- a/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php +++ b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php @@ -72,7 +72,13 @@ final class DiscouragedConstantsSniff extends AbstractFunctionParameterSniff { * normal file processing. */ public function process_token( $stackPtr ) { - if ( isset( $this->target_functions[ strtolower( $this->tokens[ $stackPtr ]['content'] ) ] ) ) { + $content_lc = strtolower( $this->tokens[ $stackPtr ]['content'] ); + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $stackPtr ]['code'] ) { + $content_lc = \ltrim( $content_lc, '\\' ); + } + + if ( isset( $this->target_functions[ $content_lc ] ) ) { // Disallow excluding function groups for this sniff. $this->exclude = array(); @@ -84,7 +90,8 @@ public function process_token( $stackPtr ) { } /** - * Process an arbitrary T_STRING token to determine whether it is one of the target constants. + * Process an arbitrary T_STRING or T_NAME_FULLY_QUALIFIED token to determine whether it is one + * of the target constants. * * @since 0.14.0 * @@ -95,6 +102,10 @@ public function process_token( $stackPtr ) { public function process_arbitrary_tstring( $stackPtr ) { $content = $this->tokens[ $stackPtr ]['content']; + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $stackPtr ]['code'] ) { + $content = \ltrim( $content, '\\' ); + } + if ( ! isset( $this->discouraged_constants[ $content ] ) ) { return; } From 0bdc61ad8dc81fdcd93314ae47801c54f3a9076f Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 19 Aug 2025 15:23:01 -0300 Subject: [PATCH 38/48] Security/EscapeOutput: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization to this sniff. --- WordPress/Sniffs/Security/EscapeOutputSniff.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/WordPress/Sniffs/Security/EscapeOutputSniff.php b/WordPress/Sniffs/Security/EscapeOutputSniff.php index c021a26239..2350b000e7 100644 --- a/WordPress/Sniffs/Security/EscapeOutputSniff.php +++ b/WordPress/Sniffs/Security/EscapeOutputSniff.php @@ -187,6 +187,7 @@ public function process_token( $stackPtr ) { $start = ( $stackPtr + 1 ); switch ( $this->tokens[ $stackPtr ]['code'] ) { + case \T_NAME_FULLY_QUALIFIED: case \T_STRING: // Prevent exclusion of any of the function groups. $this->exclude = array(); @@ -575,9 +576,15 @@ protected function check_code_is_escaped( $start, $end, $code = 'OutputNotEscape continue; } + $content = $this->tokens[ $i ]['content']; + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $i ]['code'] ) { + $content = \ltrim( $content, '\\' ); + } + // Ignore safe PHP native constants. - if ( \T_STRING === $this->tokens[ $i ]['code'] - && isset( $this->safe_php_constants[ $this->tokens[ $i ]['content'] ] ) + if ( ( \T_STRING === $this->tokens[ $i ]['code'] || \T_NAME_FULLY_QUALIFIED === $this->tokens[ $i ]['code'] ) + && isset( $this->safe_php_constants[ $content ] ) && ConstantsHelper::is_use_of_global_constant( $this->phpcsFile, $i ) ) { continue; @@ -613,7 +620,7 @@ protected function check_code_is_escaped( $start, $end, $code = 'OutputNotEscape } // Check for use of *::class. - if ( \T_STRING === $this->tokens[ $i ]['code'] + if ( isset( Collections::nameTokens()[ $this->tokens[ $i ]['code'] ] ) || \T_VARIABLE === $this->tokens[ $i ]['code'] || isset( Collections::ooHierarchyKeywords()[ $this->tokens[ $i ]['code'] ] ) || \T_NAMESPACE === $this->tokens[ $i ]['code'] @@ -679,9 +686,9 @@ protected function check_code_is_escaped( $start, $end, $code = 'OutputNotEscape } // Now check that the next token is a function call. - if ( \T_STRING === $this->tokens[ $i ]['code'] ) { + if ( \T_STRING === $this->tokens[ $i ]['code'] || \T_NAME_FULLY_QUALIFIED === $this->tokens[ $i ]['code'] ) { $ptr = $i; - $functionName = $this->tokens[ $i ]['content']; + $functionName = $content; $function_opener = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); $is_formatting_function = FormattingFunctionsHelper::is_formatting_function( $functionName ); From 019680898d3e30a482ebfd9281f0b0720363e8a6 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Mon, 20 Oct 2025 14:42:11 -0300 Subject: [PATCH 39/48] DateTime/CurrentTimeTimestamp: update for PHPCS 4.0 Correctly handle changing fully qualified `\current_time()` function name. --- WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php b/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php index 34bfe514f7..b08abe8bd8 100644 --- a/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php +++ b/WordPress/Sniffs/DateTime/CurrentTimeTimestampSniff.php @@ -161,7 +161,13 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p $this->phpcsFile->fixer->replaceToken( $i, '' ); } - $this->phpcsFile->fixer->replaceToken( $stackPtr, 'time(' ); + $replacement_content = 'time('; + + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $stackPtr ]['code'] ) { + $replacement_content = '\time('; + } + + $this->phpcsFile->fixer->replaceToken( $stackPtr, $replacement_content ); $this->phpcsFile->fixer->endChangeset(); } } From 7da5a6a3fd21cfc004d77f86ff0249b03347a5b0 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 21 Oct 2025 10:52:37 -0300 Subject: [PATCH 40/48] Utils/I18nTextDomainFixer: update for PHPCS 4.0 Update the README.md file to document the limitations of running the sniff together with PHPCS >= 4.0. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8cc41d9cd6..6ca8b4c0f5 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,8 @@ At this moment, WordPressCS offer the following tools: - `old_text_domain`: an array with one or more (old) text domain names which need to be replaced; - `new_text_domain`: the correct (new) text domain as a string. + **Note**: When using PHP_CodeSniffer 4.0+, this sniff will fix the text domain everywhere for plugins, but it will **not** fix the text domain in the CSS file header for themes, as PHP_CodeSniffer 4.0+ does not support scanning CSS files. Manual intervention will be required to update the `Text Domain` header in the theme's CSS file. + ## Contributing From 78e79499e9c2f2fbd2f8fb0181da27b8e2508855 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 7 Aug 2025 15:32:28 -0300 Subject: [PATCH 41/48] DB/PreparedSQL: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization of fully qualified function calls and static method calls to this sniff. It also updates a few of the test expectations as the number of errors vary depending on whether running PHPCS 3.x or 4.x. --- WordPress/Sniffs/DB/PreparedSQLSniff.php | 11 +++++++++-- WordPress/Tests/DB/PreparedSQLUnitTest.1.inc | 2 +- WordPress/Tests/DB/PreparedSQLUnitTest.php | 18 +++++++++++------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/WordPress/Sniffs/DB/PreparedSQLSniff.php b/WordPress/Sniffs/DB/PreparedSQLSniff.php index 05f0efc0db..7c49c53350 100644 --- a/WordPress/Sniffs/DB/PreparedSQLSniff.php +++ b/WordPress/Sniffs/DB/PreparedSQLSniff.php @@ -145,6 +145,7 @@ public function register() { return array( \T_VARIABLE, \T_STRING, + \T_NAME_FULLY_QUALIFIED, ); } @@ -206,9 +207,15 @@ static function ( $symbol ) { } } - if ( \T_STRING === $this->tokens[ $this->i ]['code'] ) { + if ( \T_STRING === $this->tokens[ $this->i ]['code'] + || \T_NAME_FULLY_QUALIFIED === $this->tokens[ $this->i ]['code'] + ) { $content_lowercase = strtolower( $this->tokens[ $this->i ]['content'] ); + if ( \T_NAME_FULLY_QUALIFIED === $this->tokens[ $this->i ]['code'] ) { + $content_lowercase = \ltrim( $content_lowercase, '\\' ); + } + if ( isset( $this->SQLEscapingFunctions[ $content_lowercase ] ) || isset( $this->SQLAutoEscapedFunctions[ $content_lowercase ] ) @@ -225,7 +232,7 @@ static function ( $symbol ) { $this->i = $this->tokens[ $opening_paren ]['parenthesis_closer']; continue; } - } elseif ( FormattingFunctionsHelper::is_formatting_function( $this->tokens[ $this->i ]['content'] ) ) { + } elseif ( FormattingFunctionsHelper::is_formatting_function( $content_lowercase ) ) { continue; } } diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc index 3ef695fbbf..6f5ca0f8c9 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc @@ -173,7 +173,7 @@ namespace\Sub\wpdb::prepare( "SELECT * FROM $wpdb->posts WHERE post_title LIKE ' */ $wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \absint( $foo ) ); $wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . MyNamespace\esc_sql( $foo ) ); -$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \MyNamespace\intval( $foo ) ); +$wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . \MyNamespace\intval( $foo, $bar ) ); $wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . namespace\floatval( $foo ) ); // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces. $wpdb->query( "SELECT * FROM $wpdb->posts WHERE ID = " . namespace\Sub\like_escape( $foo ) ); diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.php b/WordPress/Tests/DB/PreparedSQLUnitTest.php index a464deae18..76e20851e0 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.php @@ -10,6 +10,7 @@ namespace WordPressCS\WordPress\Tests\DB; use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the PreparedSQL sniff. @@ -32,6 +33,9 @@ final class PreparedSQLUnitTest extends AbstractSniffTestCase { * @return array Key is the line number, value is the number of expected errors. */ public function getErrorList( $testFile = '' ) { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + switch ( $testFile ) { case 'PreparedSQLUnitTest.1.inc': return array( @@ -67,18 +71,18 @@ public function getErrorList( $testFile = '' ) { 132 => 2, 154 => 1, 155 => 1, - 175 => 1, - 176 => 1, - 177 => 1, + 175 => ( true === $is_phpcs_4 ? 2 : 1 ), + 176 => ( true === $is_phpcs_4 ? 3 : 1 ), + 177 => ( true === $is_phpcs_4 ? 2 : 1 ), 178 => 2, - 187 => 1, - 188 => 1, - 189 => 1, + 187 => ( true === $is_phpcs_4 ? 2 : 1 ), + 188 => ( true === $is_phpcs_4 ? 2 : 1 ), + 189 => ( true === $is_phpcs_4 ? 2 : 1 ), 190 => 2, 198 => 1, 199 => 1, 200 => 1, - 201 => 2, + 201 => ( true === $is_phpcs_4 ? 1 : 2 ), ); case 'PreparedSQLUnitTest.2.inc': From 42147be009e95377940bafe809f93ec6aaa95dc8 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 7 Aug 2025 12:03:42 -0300 Subject: [PATCH 42/48] DB/PreparedSQLPlaceholders: update for PHPCS 4.0 The tokenization of (namespaced) "names" has changed in PHP 8.0, and this new tokenization will come into effect for PHP_CodeSniffer as of version 4.0.0. This commit adds handling for the new tokenization of fully qualified calls to `\wpdb::prepare()`, `\sprintf()`, `\implode()` and `\array_fill()`. --- .../DB/PreparedSQLPlaceholdersSniff.php | 170 ++++++++++-------- .../DB/PreparedSQLPlaceholdersUnitTest.inc | 4 +- .../DB/PreparedSQLPlaceholdersUnitTest.php | 15 ++ 3 files changed, 111 insertions(+), 78 deletions(-) diff --git a/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php b/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php index ba72cabc3c..4e4fed1903 100644 --- a/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php +++ b/WordPress/Sniffs/DB/PreparedSQLPlaceholdersSniff.php @@ -165,6 +165,7 @@ public function register() { return array( \T_VARIABLE, \T_STRING, + \T_NAME_FULLY_QUALIFIED, ); } @@ -224,92 +225,89 @@ public function process_token( $stackPtr ) { } // Detect a specific pattern for variable replacements in combination with `IN`. - if ( \T_STRING === $this->tokens[ $i ]['code'] ) { - - if ( 'sprintf' === strtolower( $this->tokens[ $i ]['content'] ) ) { - $sprintf_parameters = PassedParameters::getParameters( $this->phpcsFile, $i ); + if ( $this->is_global_function_call( $i, 'sprintf' ) ) { + $sprintf_parameters = PassedParameters::getParameters( $this->phpcsFile, $i ); + + if ( ! empty( $sprintf_parameters ) ) { + /* + * Check for named params. sprintf() does not support this due to its variadic nature, + * and we cannot analyze the code correctly if it is used, so skip the whole sprintf() + * in that case. + */ + $valid_sprintf = true; + foreach ( $sprintf_parameters as $param ) { + if ( isset( $param['name'] ) ) { + $valid_sprintf = false; + break; + } + } - if ( ! empty( $sprintf_parameters ) ) { - /* - * Check for named params. sprintf() does not support this due to its variadic nature, - * and we cannot analyze the code correctly if it is used, so skip the whole sprintf() - * in that case. - */ - $valid_sprintf = true; - foreach ( $sprintf_parameters as $param ) { - if ( isset( $param['name'] ) ) { - $valid_sprintf = false; - break; - } + if ( false === $valid_sprintf ) { + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] + && isset( $this->tokens[ $next ]['parenthesis_closer'] ) + ) { + $skip_from = ( $i + 1 ); + $skip_to = $this->tokens[ $next ]['parenthesis_closer']; } - if ( false === $valid_sprintf ) { - $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); - if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] - && isset( $this->tokens[ $next ]['parenthesis_closer'] ) - ) { - $skip_from = ( $i + 1 ); - $skip_to = $this->tokens[ $next ]['parenthesis_closer']; - } + continue; + } - continue; - } + // We know for sure this sprintf() uses positional parameters, so this will be fine. + $skip_from = ( $sprintf_parameters[1]['end'] + 1 ); + $last_param = end( $sprintf_parameters ); + $skip_to = ( $last_param['end'] + 1 ); - // We know for sure this sprintf() uses positional parameters, so this will be fine. - $skip_from = ( $sprintf_parameters[1]['end'] + 1 ); - $last_param = end( $sprintf_parameters ); - $skip_to = ( $last_param['end'] + 1 ); + $valid_in_clauses['implode_fill'] += $this->analyse_sprintf( $sprintf_parameters ); + $valid_in_clauses['adjustment_count'] += ( \count( $sprintf_parameters ) - 1 ); + } + unset( $sprintf_parameters, $valid_sprintf, $last_param ); - $valid_in_clauses['implode_fill'] += $this->analyse_sprintf( $sprintf_parameters ); - $valid_in_clauses['adjustment_count'] += ( \count( $sprintf_parameters ) - 1 ); - } - unset( $sprintf_parameters, $valid_sprintf, $last_param ); + } elseif ( $this->is_global_function_call( $i, 'implode' ) ) { + $ignore_tokens = Tokens::$emptyTokens + array( + \T_STRING_CONCAT => \T_STRING_CONCAT, + \T_NS_SEPARATOR => \T_NS_SEPARATOR, + ); - } elseif ( 'implode' === strtolower( $this->tokens[ $i ]['content'] ) ) { - $ignore_tokens = Tokens::$emptyTokens + array( - \T_STRING_CONCAT => \T_STRING_CONCAT, - \T_NS_SEPARATOR => \T_NS_SEPARATOR, - ); + $prev = $this->phpcsFile->findPrevious( + $ignore_tokens, + ( $i - 1 ), + $query['start'], + true + ); - $prev = $this->phpcsFile->findPrevious( - $ignore_tokens, - ( $i - 1 ), - $query['start'], - true - ); + if ( isset( Tokens::$textStringTokens[ $this->tokens[ $prev ]['code'] ] ) ) { + $prev_content = TextStrings::stripQuotes( $this->tokens[ $prev ]['content'] ); + $regex_quote = $this->get_regex_quote_snippet( $prev_content, $this->tokens[ $prev ]['content'] ); - if ( isset( Tokens::$textStringTokens[ $this->tokens[ $prev ]['code'] ] ) ) { - $prev_content = TextStrings::stripQuotes( $this->tokens[ $prev ]['content'] ); - $regex_quote = $this->get_regex_quote_snippet( $prev_content, $this->tokens[ $prev ]['content'] ); + // Only examine the implode if preceded by an ` IN (`. + if ( preg_match( '`\s+IN\s*\(\s*(' . $regex_quote . ')?$`i', $prev_content, $match ) > 0 ) { - // Only examine the implode if preceded by an ` IN (`. - if ( preg_match( '`\s+IN\s*\(\s*(' . $regex_quote . ')?$`i', $prev_content, $match ) > 0 ) { + if ( isset( $match[1] ) && $regex_quote !== $this->regex_quote ) { + $this->phpcsFile->addError( + 'Dynamic placeholder generation should not have surrounding quotes.', + $prev, + 'QuotedDynamicPlaceholderGeneration' + ); + } - if ( isset( $match[1] ) && $regex_quote !== $this->regex_quote ) { - $this->phpcsFile->addError( - 'Dynamic placeholder generation should not have surrounding quotes.', - $prev, - 'QuotedDynamicPlaceholderGeneration' - ); - } + if ( $this->analyse_implode( $i ) === true ) { + ++$valid_in_clauses['uses_in']; + ++$valid_in_clauses['implode_fill']; - if ( $this->analyse_implode( $i ) === true ) { - ++$valid_in_clauses['uses_in']; - ++$valid_in_clauses['implode_fill']; - - $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); - if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] - && isset( $this->tokens[ $next ]['parenthesis_closer'] ) - ) { - $skip_from = ( $i + 1 ); - $skip_to = $this->tokens[ $next ]['parenthesis_closer']; - } + $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true ); + if ( \T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] + && isset( $this->tokens[ $next ]['parenthesis_closer'] ) + ) { + $skip_from = ( $i + 1 ); + $skip_to = $this->tokens[ $next ]['parenthesis_closer']; } } - unset( $next, $prev_content, $regex_quote, $match ); } - unset( $prev ); + unset( $next, $prev_content, $regex_quote, $match ); } + unset( $prev ); } continue; @@ -679,9 +677,7 @@ protected function analyse_sprintf( $sprintf_params ) { $sprintf_param['end'], true ); - if ( \T_STRING === $this->tokens[ $implode ]['code'] - && 'implode' === strtolower( $this->tokens[ $implode ]['content'] ) - ) { + if ( $this->is_global_function_call( $implode, 'implode' ) ) { if ( $this->analyse_implode( $implode ) === true ) { ++$found; } @@ -737,9 +733,7 @@ protected function analyse_implode( $implode_token ) { true ); - if ( \T_STRING !== $this->tokens[ $array_fill ]['code'] - || 'array_fill' !== strtolower( $this->tokens[ $array_fill ]['content'] ) - ) { + if ( ! $this->is_global_function_call( $array_fill, 'array_fill' ) ) { return false; } @@ -763,4 +757,28 @@ protected function analyse_implode( $implode_token ) { return (bool) preg_match( '`^(["\'])%[dfFs]\1$`', $array_fill_value_param['clean'] ); } + + /** + * Check whether a token is an unqualified or fully qualified call to a given global function. + * + * Temporary, simplified helper: it only checks the token type and the normalized function name, + * reproducing this sniff's existing behavior. It does not verify call context, nor exclude method + * calls or partially qualified / namespace-relative names. + * + * As part of fixing https://github.com/WordPress/WordPress-Coding-Standards/issues/2720, this + * method should be replaced with a more robust and generic alternative. + * + * @param int $token_ptr The position of the token to examine. + * @param string $function_name The lowercase name of the global function to check for. + * + * @return bool + */ + private function is_global_function_call( $token_ptr, $function_name ) { + $code = $this->tokens[ $token_ptr ]['code']; + if ( \T_STRING !== $code && \T_NAME_FULLY_QUALIFIED !== $code ) { + return false; + } + + return ltrim( strtolower( $this->tokens[ $token_ptr ]['content'] ), '\\' ) === $function_name; + } } diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc index d9f586e3fa..73817620ea 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc @@ -661,8 +661,8 @@ $where = $wpdb->prepare( * Safeguard correct handling of all types of namespaced calls to sprintf() except fully qualified calls to the * global sprintf() function, which is already handled above. * - * The calls below are currently false negatives. The sniff incorrectly treats namespaced sprintf() calls as - * calls to the global sprintf() function and does not flag them. + * On PHPCS 3.x these are false negatives: the sniff mistakes the namespaced sprintf() calls for the global + * sprintf() function and does not flag them. On PHPCS 4.x, the changed name tokenization means they are flagged. * See: https://github.com/WordPress/WordPress-Coding-Standards/issues/2720 */ $where = $wpdb->prepare( diff --git a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php index e0bd5d17d5..bb09f4cf60 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php @@ -10,6 +10,7 @@ namespace WordPressCS\WordPress\Tests\DB; use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the PreparedSQLPlaceholders sniff. @@ -119,6 +120,9 @@ public function getErrorList() { * @return array Key is the line number, value is the number of expected warnings. */ public function getWarningList() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + return array( 12 => 1, 16 => 1, @@ -184,6 +188,17 @@ public function getWarningList() { 647 => 1, 653 => 1, + /* + * Namespaced sprintf() calls. + * + * False negatives on PHPCS 3.x. Flagged on PHPCS 4.x due to the changed tokenization of + * namespaced names. See https://github.com/WordPress/WordPress-Coding-Standards/issues/2720. + */ + 668 => ( true === $is_phpcs_4 ? 1 : 0 ), + 675 => ( true === $is_phpcs_4 ? 1 : 0 ), + 682 => ( true === $is_phpcs_4 ? 1 : 0 ), + 689 => ( true === $is_phpcs_4 ? 1 : 0 ), + // Method sprintf/implode/array_fill calls. 728 => 1, 735 => 1, From 05371c9c240a18ebd371305fff10a1b6109e1f4e Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 4 Dec 2025 10:46:44 -0300 Subject: [PATCH 43/48] Security/ValidatedSanitizedInput: updates for PHPCS 4.0 Update the expectation for three test cases as they are different between PHPCS 3.x and PHPCS 4.x (see 2665). --- .../Security/ValidatedSanitizedInputUnitTest.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php index dba38a0a3a..192a3f9ca4 100644 --- a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php +++ b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php @@ -10,6 +10,7 @@ namespace WordPressCS\WordPress\Tests\Security; use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the ValidatedSanitizedInput sniff. @@ -33,6 +34,9 @@ final class ValidatedSanitizedInputUnitTest extends AbstractSniffTestCase { * @return array Key is the line number, value is the number of expected errors. */ public function getErrorList( $testFile = '' ) { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + switch ( $testFile ) { case 'ValidatedSanitizedInputUnitTest.1.inc': return array( @@ -125,10 +129,10 @@ public function getErrorList( $testFile = '' ) { 539 => 2, // The error counts below differ depending on whether running PHPCS 3.x or PHPCS 4.x. See the comment in the test case file. - 554 => 1, - 557 => 1, - 560 => 1, - 563 => 1, + 554 => ( true === $is_phpcs_4 ) ? 2 : 1, + 557 => ( true === $is_phpcs_4 ) ? 2 : 1, + 560 => ( true === $is_phpcs_4 ) ? 2 : 1, + 563 => ( true === $is_phpcs_4 ) ? 2 : 1, 575 => 1, 578 => 1, From 2681006ce5b1bbd674d7de4dccaeadcc2fc0de6f Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 29 Jan 2026 15:27:06 -0300 Subject: [PATCH 44/48] PHPCS 4.0: handle converting empty string array values to null PHPCS 4.0 introduced a change where empty string values in array properties are converted to `null`. This is documented in the Version 4.0 Developer Upgrade Guide (https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Version-4.0-Developer-Upgrade-Guide#property-type-casting-has-been-made-more-consistent): > Array elements can now no longer contain an empty string value > as that value will be cast to `null`. This affected the PrefixAllGlobals and NoSilencedErrors sniffs and the IsUnitTestTrait helper. They pass array values to string functions (`strtolower()` and `ltrim()`), which trigger a deprecation notice on PHP 8.1 and higher when `null` is passed. The fix is different for each case: - PrefixAllGlobals: Convert `null` back to empty string at the start of the validation loop. This preserves the "prefix too short" error that alerts users about invalid configuration. - NoSilencedErrors: Filter out `null` values before processing. Unlike PrefixAllGlobals, this sniff has no validation that provides user feedback for invalid entries, so filtering is cleaner. A test case was added to safeguard against the deprecation. - IsUnitTestTrait: Filter out `null` values before processing, for the same reason as NoSilencedErrors. A test case was added to the PrefixAllGlobals unit test, which covers this trait, to safeguard against the deprecation. --- WordPress/Helpers/IsUnitTestTrait.php | 4 +++- .../Sniffs/NamingConventions/PrefixAllGlobalsSniff.php | 3 +++ WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php | 3 +++ .../NamingConventions/PrefixAllGlobalsUnitTest.1.inc | 10 ++++++++++ WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc | 8 ++++++++ 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/WordPress/Helpers/IsUnitTestTrait.php b/WordPress/Helpers/IsUnitTestTrait.php index 0c9c77db13..b25ce2ccb3 100644 --- a/WordPress/Helpers/IsUnitTestTrait.php +++ b/WordPress/Helpers/IsUnitTestTrait.php @@ -138,7 +138,9 @@ final protected function get_all_test_classes() { */ $custom_test_classes = array(); if ( ! empty( $this->custom_test_classes ) ) { - foreach ( $this->custom_test_classes as $v ) { + // PHPCS >= 4.0 converts empty string values in array properties to null. + // Filter out null values to avoid passing them to ltrim(). + foreach ( array_filter( $this->custom_test_classes ) as $v ) { $custom_test_classes[] = ltrim( $v, '\\' ); } } diff --git a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php index df613e6f17..3869db7ba6 100644 --- a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php +++ b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php @@ -1236,6 +1236,9 @@ private function validate_prefixes() { $prefixes = array(); $ns_prefixes = array(); foreach ( $this->prefixes as $key => $prefix ) { + // PHPCS >= 4.0 converts empty string values in array properties to null. + // Convert null back to empty string to preserve the "prefix too short" error. + $prefix = $prefix ?? ''; $prefixLC = strtolower( $prefix ); if ( isset( $this->prefix_blocklist[ $prefixLC ] ) ) { diff --git a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php index b57a22de7f..552bbbb9c1 100644 --- a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php +++ b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php @@ -185,6 +185,9 @@ public function register() { public function process_token( $stackPtr ) { // Handle the user-defined custom function list. $this->customAllowedFunctionsList = RulesetPropertyHelper::merge_custom_array( $this->customAllowedFunctionsList, array(), false ); + // PHPCS >= 4.0 converts empty string values in array properties to null. + // Filter out null values to avoid passing them to strtolower(). + $this->customAllowedFunctionsList = array_filter( $this->customAllowedFunctionsList ); $this->customAllowedFunctionsList = array_map( 'strtolower', $this->customAllowedFunctionsList ); /* diff --git a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc index 707468c137..83e20721ad 100644 --- a/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc +++ b/WordPress/Tests/NamingConventions/PrefixAllGlobalsUnitTest.1.inc @@ -696,3 +696,13 @@ MyNamespace\do_action( 'plugin_action' ); // Ok. namespace\do_action_ref_array( 'plugin_action', array( $variable ) ); // Ok. The sniff should start flagging this once it can resolve relative namespaces. // phpcs:set WordPress.NamingConventions.PrefixAllGlobals prefixes[] + +/* + * Safeguard handling of empty custom test class values (PHPCS 4.x compatibility). + * PHPCS >= 4.0 converts empty string values in array properties to null. + */ +// phpcs:set WordPress.NamingConventions.PrefixAllGlobals prefixes[] acronym +// phpcs:set WordPress.NamingConventions.PrefixAllGlobals custom_test_classes[] , +class Test_Empty_Custom_Test_Class extends WP_UnitTestCase {} +// phpcs:set WordPress.NamingConventions.PrefixAllGlobals custom_test_classes[] +// phpcs:set WordPress.NamingConventions.PrefixAllGlobals prefixes[] diff --git a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc index 27b3598537..88c312744e 100644 --- a/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc +++ b/WordPress/Tests/PHP/NoSilencedErrorsUnitTest.inc @@ -92,3 +92,11 @@ $file = @MyNS\MyClass::file_get_contents( $file ); // Bad. $file = @MyNS\MyClass\file_exists( $file ); // Bad. $file = @namespace\MyNS\MyClass::file( $file ); // Bad. $file = @namespace\is_dir( $dir ); // The sniff should stop flagging this once it can resolve relative namespaces. + +/* + * Safeguard handling of empty values in the custom allowed functions list (PHPCS 4.x compatibility). + * PHPCS >= 4.0 converts empty string values in array properties to null. + */ +// phpcs:set WordPress.PHP.NoSilencedErrors customAllowedFunctionsList[] , +$unserialized = @unserialize( $str ); +// phpcs:set WordPress.PHP.NoSilencedErrors customAllowedFunctionsList[] From 2615b268d869ea0cb4007f15a633784d83fcac5e Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Tue, 14 Jul 2026 17:34:22 +0000 Subject: [PATCH 45/48] Composer: make PHPCS 4.x support official --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2107a8bd49..223b21baef 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "ext-libxml": "*", "ext-tokenizer": "*", "ext-xmlreader": "*", - "squizlabs/php_codesniffer": "^3.13.5", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1", "phpcsstandards/phpcsutils": "^1.2.2", "phpcsstandards/phpcsextra": "^1.5.0" }, From 92dea2c3ea387c466ec2ba8e2b013a46b936de3d Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 16 Jul 2026 21:53:11 +0000 Subject: [PATCH 46/48] WordPress-Docs: allow variable comment tags flagged under new PHPCS 4.0 error codes The `WordPress-Docs` ruleset excludes `Squiz.Commenting.VariableComment.TagNotAllowed` to allow tags such tags in class variable docblocks, as used by the WordPress documentation standards. PHPCS 4.0 replaced this single generic error code with a per-tag `[TagName]TagNotAllowed` error code (https://github.com/PHPCSStandards/PHP_CodeSniffer/pull/1039), so the existing exclusion no longer suppresses these warnings when running on PHPCS 4.x. This commit adds exclusions for the new per-tag error codes (`SinceTagNotAllowed`, `LinkTagNotAllowed` and `DeprecatedTagNotAllowed`), while keeping the existing `TagNotAllowed` exclusion for compatibility with PHPCS 3.x. --- WordPress-Docs/ruleset.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/WordPress-Docs/ruleset.xml b/WordPress-Docs/ruleset.xml index bc64617386..2d30812df7 100644 --- a/WordPress-Docs/ruleset.xml +++ b/WordPress-Docs/ruleset.xml @@ -82,8 +82,15 @@ + + + + + + + From 3074ccc14d5f75f01b13f438303ad8b018227ea5 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Thu, 16 Jul 2026 22:25:01 +0000 Subject: [PATCH 47/48] AbstractArrayAssignmentRestrictions: reword a comment to avoid a false positive on PHPCS 4.0 The `Squiz.PHP.CommentedOutCode` sniff estimates how much of a comment looks like commented-out code and warns above a threshold. WPCS sets its `maxPercentage` property to 40. PHPCS 4.0 tokenizes the example array assignments in this comment slightly differently than PHPCS 3.x, which pushed the estimate for this comment from 39% (PHPCS 3.x) to 41% (PHPCS 4.x). That crossed the 40% threshold and triggered a false positive when running on PHPCS 4.x. As a workaroudn, this commit rewords the comment so it contains more prose, lowering the "valid code" percentage back below the threshold (33%) on both PHPCS versions. --- WordPress/AbstractArrayAssignmentRestrictionsSniff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/AbstractArrayAssignmentRestrictionsSniff.php b/WordPress/AbstractArrayAssignmentRestrictionsSniff.php index 9ca127b06b..b0bd93b9e0 100644 --- a/WordPress/AbstractArrayAssignmentRestrictionsSniff.php +++ b/WordPress/AbstractArrayAssignmentRestrictionsSniff.php @@ -158,7 +158,7 @@ public function process_token( $stackPtr ) { $inst = array(); /* - * Covers array assignments: + * Covers following forms of array assignments: * `$foo = array( 'bar' => 'taz' );` * `$foo['bar'] = $taz;` */ From 0491d4b3bc00837e170bd3db69d95796c71d8759 Mon Sep 17 00:00:00 2001 From: Rodrigo Primo Date: Fri, 17 Jul 2026 18:34:53 +0000 Subject: [PATCH 48/48] DELME Temporarily exclude Squiz.Functions.FunctionDuplicateArgument while upstream PHPCS issue is not fixed This commit should be dropped once WPCS starts using a PHPCS version that includes a fix for PHPCSStandards/PHP_CodeSniffer 1463 --- .github/workflows/basic-qa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/basic-qa.yml b/.github/workflows/basic-qa.yml index 11bbb993e2..78a3e47b8d 100644 --- a/.github/workflows/basic-qa.yml +++ b/.github/workflows/basic-qa.yml @@ -229,7 +229,7 @@ jobs: continue-on-error: true run: | set +e - $(pwd)/vendor/bin/phpcbf -pq ./WordPress/Tests/ --standard=WordPress --extensions=inc --exclude=Generic.PHP.Syntax --report=summary --ignore=/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.7.inc + $(pwd)/vendor/bin/phpcbf -pq ./WordPress/Tests/ --standard=WordPress --extensions=inc --exclude=Generic.PHP.Syntax,Squiz.Functions.FunctionDuplicateArgument --report=summary --ignore=/WordPress/Tests/WP/GlobalVariablesOverrideUnitTest.7.inc exitcode="$?" echo "EXITCODE=$exitcode" >> "$GITHUB_OUTPUT" exit "$exitcode"