diff --git a/.github/workflows/basic-qa.yml b/.github/workflows/basic-qa.yml index 9b489f7a7e..78a3e47b8d 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,21 +221,26 @@ 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: | 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" - 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/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 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-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 @@ + + + + + + + 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;` */ 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 ) ); } 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/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/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/Helpers/ContextHelper.php b/WordPress/Helpers/ContextHelper.php index 1da3286715..78e6e7df70 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; @@ -113,6 +114,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. * @@ -163,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'] ) { @@ -179,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. * @@ -186,7 +251,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 +280,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 +297,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; @@ -382,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; } 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/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/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/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/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/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/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; } 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/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/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 ) ) { 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/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/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(); } } diff --git a/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php b/WordPress/Sniffs/NamingConventions/PrefixAllGlobalsSniff.php index 85c79f2c09..3869db7ba6 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(); @@ -1233,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/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'] ) ) { diff --git a/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php b/WordPress/Sniffs/PHP/NoSilencedErrorsSniff.php index 06cc8879be..552bbbb9c1 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; @@ -184,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 ); /* @@ -193,10 +197,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 ) diff --git a/WordPress/Sniffs/Security/EscapeOutputSniff.php b/WordPress/Sniffs/Security/EscapeOutputSniff.php index b05732e823..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(); @@ -367,7 +368,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. @@ -574,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; @@ -612,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'] @@ -678,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 ); 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 ] ) ) { /* 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/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/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 */ 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; } 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; } 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.inc b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc index 791df5a509..73817620ea 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. + * + * 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( + 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..bb09f4cf60 100644 --- a/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php @@ -9,7 +9,8 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the PreparedSQLPlaceholders sniff. @@ -18,7 +19,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\DB\PreparedSQLPlaceholdersSniff */ -final class PreparedSQLPlaceholdersUnitTest extends AbstractSniffUnitTest { +final class PreparedSQLPlaceholdersUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. @@ -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, @@ -165,6 +169,49 @@ 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, + + /* + * 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, + 742 => 1, + 751 => 1, + 757 => 1, + 763 => 1, + 771 => 1, + 778 => 1, + 785 => 1, + 794 => 1, + 800 => 1, + 806 => 1, ); } } diff --git a/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc b/WordPress/Tests/DB/PreparedSQLUnitTest.1.inc index ac97d71ce8..6f5ca0f8c9 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, $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 ) ); + +/* + * 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..76e20851e0 100644 --- a/WordPress/Tests/DB/PreparedSQLUnitTest.php +++ b/WordPress/Tests/DB/PreparedSQLUnitTest.php @@ -9,7 +9,8 @@ namespace WordPressCS\WordPress\Tests\DB; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the PreparedSQL sniff. @@ -20,10 +21,9 @@ * * @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 { +final class PreparedSQLUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. @@ -33,6 +33,9 @@ final class PreparedSQLUnitTest extends AbstractSniffUnitTest { * @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( @@ -68,6 +71,18 @@ public function getErrorList( $testFile = '' ) { 132 => 2, 154 => 1, 155 => 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 => ( 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 => ( true === $is_phpcs_4 ? 1 : 2 ), ); case 'PreparedSQLUnitTest.2.inc': 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/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 c56d3661f2..7444544fb2 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. @@ -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/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, 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..e75b4104fb --- /dev/null +++ b/WordPress/Tests/Helpers/ConstantsHelper/IsUseOfGlobalConstantUnitTest.php @@ -0,0 +1,252 @@ +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() { + $phpcs_version = Helper::getVersion(); + $is_phpcs_4 = version_compare( $phpcs_version, '3.99.99', '>' ); + + 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' => ( 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' => ( 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' => ( 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 */', + '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, + '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 */', + '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/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; 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, Collections::nameTokens(), $tokenContent ); + $result = ContextHelper::is_token_namespaced( self::$phpcsFile, $stackPtr ); + + $this->assertSame( $expectedResult, $result ); + } + + /** + * Data provider. + * + * @see testIsTokenNamespaced() + * + * @return array> + */ + public static function dataIsTokenNamespaced() { + $isPhpcs3 = version_compare( Helper::getVersion(), '3.99.99', '<=' ); + + return array( + // Cases that should return false. + 'unqualified' => array( + 'testMarker' => '/* testUnqualified */', + 'expectedResult' => false, + 'tokenContent' => 'my_function', + ), + 'fully_qualified' => array( + 'testMarker' => '/* testFullyQualified */', + 'expectedResult' => false, + 'tokenContent' => ( $isPhpcs3 ? 'MY_CONSTANT' : '\MY_CONSTANT' ), + ), + + // Cases that should return true. + 'partially_qualified' => array( + 'testMarker' => '/* testPartiallyQualified */', + 'expectedResult' => true, + 'tokenContent' => ( $isPhpcs3 ? 'my_function' : 'MyNamespace\my_function' ), + ), + 'fully_qualified_namespaced' => array( + 'testMarker' => '/* testFullyQualifiedNamespaced */', + 'expectedResult' => true, + 'tokenContent' => ( $isPhpcs3 ? 'MyClass' : '\MyNamespace\MyClass' ), + ), + 'namespace_relative' => array( + 'testMarker' => '/* testNamespaceRelative */', + 'expectedResult' => true, + 'tokenContent' => ( $isPhpcs3 ? 'MY_CONSTANT' : 'namespace\MY_CONSTANT' ), + ), + 'namespace_relative_sub' => array( + 'testMarker' => '/* testNamespaceRelativeSub */', + 'expectedResult' => true, + 'tokenContent' => ( $isPhpcs3 ? 'my_function' : 'namespace\Sub\my_function' ), + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php new file mode 100644 index 0000000000..78cfb83a80 --- /dev/null +++ b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsAutoEscapedFunctionUnitTest.php @@ -0,0 +1,115 @@ +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, + ), + 'fully_qualified_name' => array( + 'functionName' => '\allowed_tags', + '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..d414081391 --- /dev/null +++ b/WordPress/Tests/Helpers/EscapingFunctionsTrait/IsEscapingFunctionUnitTest.php @@ -0,0 +1,115 @@ +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, + ), + 'fully_qualified_name' => array( + 'functionName' => '\esc_url', + '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' ) ); + } +} 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, diff --git a/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php b/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php new file mode 100644 index 0000000000..01515dc26c --- /dev/null +++ b/WordPress/Tests/Helpers/PrintingFunctionsTrait/IsPrintingFunctionUnitTest.php @@ -0,0 +1,88 @@ +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, + ), + 'fully_qualified_name' => array( + 'functionName' => '\wp_die', + 'expectedResult' => true, + ), + 'not_a_printing_function' => array( + 'functionName' => 'echo', + 'expectedResult' => false, + ), + ); + } +} 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 ); + } +} 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/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..34271673d7 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizedUnitTest.php @@ -0,0 +1,276 @@ +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() { + $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( + '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, + ), + + // 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 === $is_phpcs_4 ) ? false : true, + ), + 'fully_qualified_namespaced_unslash_sanitized' => array( + 'testMarker' => '/* testFullyQualifiedNamespacedUnslashSanitized */', + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : true, + ), + 'namespace_relative_unslash_sanitized' => array( + 'testMarker' => '/* testNamespaceRelativeUnslashSanitized */', + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : true, + ), + 'namespace_relative_sub_unslash_sanitized' => array( + 'testMarker' => '/* testNamespaceRelativeSubUnslashSanitized */', + 'expectedResult' => ( true === $is_phpcs_4 ) ? false : 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, + ), + ); + } +} diff --git a/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php new file mode 100644 index 0000000000..e83e97c9f9 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingAndUnslashingFunctionUnitTest.php @@ -0,0 +1,86 @@ +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, + ), + '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 new file mode 100644 index 0000000000..e585e40a79 --- /dev/null +++ b/WordPress/Tests/Helpers/SanitizationHelperTrait/IsSanitizingFunctionUnitTest.php @@ -0,0 +1,86 @@ +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, + ), + 'fully_qualified_name' => array( + 'functionName' => '\sanitize_text_field', + 'expectedResult' => true, + ), + 'not_a_sanitizing_function' => array( + 'functionName' => 'printf', + 'expectedResult' => false, + ), + ); + } +} 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, 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 @@ + 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..de95c66632 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.inc @@ -0,0 +1,51 @@ +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 ); +my_function( /* testNotStringOrVariable */ )->prepare /* testNotStringOrVariableOpenParen */ ( 'SELECT * FROM table' ); + +/* + * 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..0d438f7ce5 --- /dev/null +++ b/WordPress/Tests/Helpers/WPDBTrait/IsWpdbMethodCallUnitTest.php @@ -0,0 +1,337 @@ +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() { + $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( + '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' => $isPhpcs3 ? \T_STRING : \T_NAME_QUALIFIED, + 'tokenContent' => $tokenContent, + ), + 'fully_qualified_namespaced' => array( + 'testMarker' => '/* testFullyQualifiedNamespaced */', + 'expectedResult' => false, + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_FULLY_QUALIFIED, + 'tokenContent' => $tokenContent, + ), + 'namespace_relative' => array( + 'testMarker' => '/* testNamespaceRelative */', + 'expectedResult' => false, + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_RELATIVE, + 'tokenContent' => $tokenContent, + ), + 'namespace_relative_sub' => array( + 'testMarker' => '/* testNamespaceRelativeSub */', + 'expectedResult' => false, + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_RELATIVE, + 'tokenContent' => $tokenContent, + ), + '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 */', + ), + 'not_string_or_variable' => array( + 'testMarker' => '/* testNotStringOrVariable */', + 'expectedResult' => false, + 'tokenType' => \T_CLOSE_PARENTHESIS, + ), + + // 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' => $isPhpcs3 ? \T_STRING : \T_NAME_FULLY_QUALIFIED, + 'tokenContent' => null, + 'hasMethodPtr' => true, + 'openParenMarker' => '/* testFullyQualifiedGlobalLowercaseOpenParen */', + 'hasEnd' => true, + ), + 'fully_qualified_global_uppercase' => array( + 'testMarker' => '/* testFullyQualifiedGlobalUppercase */', + 'expectedResult' => true, + 'tokenType' => $isPhpcs3 ? \T_STRING : \T_NAME_FULLY_QUALIFIED, + '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 ); + } +} 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 ), + ), ); } } 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/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.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[] 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 7111a1ff13..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. @@ -20,12 +20,11 @@ * * @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 */ -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 a5428b1560..192a3f9ca4 100644 --- a/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php +++ b/WordPress/Tests/Security/ValidatedSanitizedInputUnitTest.php @@ -9,7 +9,8 @@ namespace WordPressCS\WordPress\Tests\Security; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the ValidatedSanitizedInput sniff. @@ -19,13 +20,11 @@ * @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\ValidationHelper * @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. @@ -35,6 +34,9 @@ final class ValidatedSanitizedInputUnitTest extends AbstractSniffUnitTest { * @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( @@ -127,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, 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 872050aa97..0a4636281f 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. @@ -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 ); 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.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 4bd6add5b3..cd01753cca 100644 --- a/WordPress/Tests/WP/CronIntervalUnitTest.php +++ b/WordPress/Tests/WP/CronIntervalUnitTest.php @@ -9,7 +9,8 @@ namespace WordPressCS\WordPress\Tests\WP; -use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; +use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase; +use PHPCSUtils\BackCompat\Helper; /** * Unit test class for the CronInterval sniff. @@ -20,7 +21,7 @@ * * @covers \WordPressCS\WordPress\Sniffs\WP\CronIntervalSniff */ -final class CronIntervalUnitTest extends AbstractSniffUnitTest { +final class CronIntervalUnitTest extends AbstractSniffTestCase { /** * Returns the lines where errors should occur. @@ -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, 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 67031257c3..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. @@ -19,10 +19,9 @@ * * @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 { +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..223b21baef 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,13 @@ "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" }, "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."