├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── 10up-Default ├── Sniffs │ └── WP │ │ └── PostsPerPageNoUnlimitedSniff.php ├── ruleset-test.inc ├── ruleset-test.php └── ruleset.xml ├── LICENSE ├── README.md ├── bin ├── ruleset-tests └── xml-lint ├── composer.json ├── composer.lock └── tests └── RulesetTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | [*.{json,yml,md}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | # Run on pushes to `master` and on all pull requests. 5 | # Prevent the "push" build from running when there are only irrelevant changes. 6 | push: 7 | branches: 8 | - master 9 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | 13 | # Allow manually triggering the workflow. 14 | workflow_dispatch: 15 | 16 | # Cancels all previous workflow runs for the same branch that have not yet completed. 17 | concurrency: 18 | # The concurrency group contains the workflow name and the branch name. 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | 24 | checkxml: 25 | name: 'Check XML Validates' 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v3 31 | 32 | - name: Install xmllint 33 | run: | 34 | sudo apt-get update 35 | sudo apt-get install --no-install-recommends -y libxml2-utils 36 | 37 | # Show XML violations inline in the file diff. 38 | # @link https://github.com/marketplace/actions/xmllint-problem-matcher 39 | - uses: korelstar/xmllint-problem-matcher@v1 40 | 41 | - name: 'Validate XML against schema and check code style' 42 | run: ./bin/xml-lint 43 | 44 | test: 45 | runs-on: ubuntu-latest 46 | 47 | strategy: 48 | # Keys: 49 | # - php: The PHP versions to test against. 50 | # - dependencies: The PHPCS dependencies versions to test against. 51 | # IMPORTANT: test runs shouldn't fail because of PHPCS being incompatible with a PHP version. 52 | # - PHPCS will run without errors on PHP 5.4 - 7.4 on any supported version. 53 | # - PHP 8.0 needs PHPCS 3.5.7+ to run without errors, and we require a higher minimum version. 54 | # - PHP 8.1 needs PHPCS 3.6.1+ to run without errors, but works best with 3.7.1+, and we require at least this minimum version. 55 | matrix: 56 | php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] 57 | dependencies: ['stable'] 58 | name: "Test: PHP ${{ matrix.php }} - PHPCS" 59 | 60 | steps: 61 | - name: Checkout code 62 | uses: actions/checkout@v3 63 | 64 | # With stable PHPCS dependencies, allow for PHP deprecation notices. 65 | # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore. 66 | - name: Setup ini config 67 | id: set_ini 68 | run: | 69 | echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On' >> $GITHUB_OUTPUT 70 | 71 | - name: Install PHP 72 | uses: shivammathur/setup-php@v2 73 | with: 74 | php-version: ${{ matrix.php }} 75 | ini-values: ${{ steps.set_ini.outputs.PHP_INI }} 76 | coverage: none 77 | 78 | # Install dependencies and handle caching in one go. 79 | # @link https://github.com/marketplace/actions/install-composer-dependencies 80 | - name: Install Composer dependencies - normal 81 | if: ${{ startsWith( matrix.php, '8' ) == false }} 82 | uses: "ramsey/composer-install@v2" 83 | with: 84 | # Bust the cache at least once a month - output format: YYYY-MM. 85 | custom-cache-suffix: $(date -u "+%Y-%m") 86 | 87 | # PHPUnit 7.x does not allow for installation on PHP 8, so ignore platform 88 | # requirements to get PHPUnit 7.x to install on nightly. 89 | - name: Install Composer dependencies - with ignore platform 90 | if: ${{ startsWith( matrix.php, '8' ) }} 91 | uses: "ramsey/composer-install@v2" 92 | with: 93 | composer-options: --ignore-platform-req=php+ 94 | custom-cache-suffix: $(date -u "+%Y-%m") 95 | 96 | - name: Run the ruleset tests 97 | run: ./bin/ruleset-tests 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | 3 | # IDE 4 | .history 5 | -------------------------------------------------------------------------------- /10up-Default/Sniffs/WP/PostsPerPageNoUnlimitedSniff.php: -------------------------------------------------------------------------------- 1 | array( 42 | 'type' => 'warning', 43 | 'keys' => array( 44 | 'posts_per_page', 45 | 'numberposts', 46 | ), 47 | ), 48 | ); 49 | } 50 | 51 | /** 52 | * Callback to process each confirmed key, to check value. 53 | * 54 | * @param string $key Array index / key. 55 | * @param mixed $val Assigned value. 56 | * @param int $line Token line. 57 | * @param array $group Group definition. 58 | * @return mixed FALSE if no match, TRUE if matches, STRING if matches 59 | * with custom error message passed to ->process(). 60 | */ 61 | public function callback( $key, $val, $line, $group ) { 62 | $this->posts_per_page = (int) $this->posts_per_page; 63 | 64 | if ( strval( $val ) === '-1' ) { 65 | return 'Detected unlimited pagination, `%s` is set to `%s`'; 66 | } 67 | 68 | return false; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /10up-Default/ruleset-test.inc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 999, 48 | ); 49 | 50 | // Warnings: WordPress.WP.PostsPerPage.posts_per_page_posts_per_page 51 | _query_posts( 'posts_per_page=999' ); 52 | 53 | // Warnings: WordPress.WP.PostsPerPage.posts_per_page_posts_per_page 54 | $query_args['posts_per_page'] = 999; 55 | 56 | // Errors: WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set) 57 | date_default_timezone_set( 'FooBar' ); 58 | 59 | $b = function () { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable 60 | global $wpdb; 61 | $listofthings = wp_cache_get( 'foo' ); 62 | if ( ! $listofthings ) { 63 | $foo = "column = 'test'"; 64 | 65 | // Errors: WordPress.DB.PreparedSQL.NotPrepared 66 | // Warnings: WordPress.DB.DirectDatabaseQuery.DirectQuery 67 | $listofthings = $wpdb->query( 'SELECT something FROM somewhere WHERE ' . $foo ); 68 | wp_cache_set( 'foo', $listofthings ); 69 | } 70 | }; 71 | 72 | // Warnings: WordPress.DB.DirectDatabaseQuery.DirectQuery & WordPress.DB.PreparedSQLPlaceholders.UnnecessaryPrepare & WordPress.DB.DirectDatabaseQuery.NoCaching 73 | $baz = $wpdb->get_results( $wpdb->prepare( 'SELECT X FROM Y ' ) ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Warning x 2. 74 | 75 | $test = [ 76 | // Warnings: WordPress.DB.SlowDBQuery.slow_db_query_tax_query 77 | 'tax_query' => [], 78 | ]; 79 | 80 | // Errors: PEAR.Functions.FunctionCallSignature.ContentAfterOpenBracket 81 | new WP_Query( array( 82 | // Warnings: WordPress.DB.SlowDBQuery.slow_db_query_meta_query 83 | 'meta_query' => [], 84 | // Warnings: WordPress.DB.SlowDBQuery.slow_db_query_meta_key & WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned 85 | 'meta_key' => 'foo', 86 | // Warnings: Warnings: WordPress.DB.SlowDBQuery.slow_db_query_meta_value 87 | 'meta_value' => 'bar', 88 | // Errors: PEAR.Functions.FunctionCallSignature.CloseBracketLine 89 | ) ); 90 | 91 | // WordPress.WP.GlobalVariablesOverride 92 | $GLOBALS['wpdb'] = 'test'; 93 | 94 | // Errors: Generic.CodeAnalysis.EmptyStatement.DetectedIf 95 | // Warnings: Universal.Operators.StrictComparisons.LooseEqual 96 | if ( true == $true ) { 97 | } 98 | 99 | // Errors: Generic.CodeAnalysis.EmptyStatement.DetectedIf & Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure 100 | // Warnings: Generic.CodeAnalysis.AssignmentInCondition.Found 101 | if ( $test = get_post( $post ) ) { 102 | } 103 | 104 | // Errors: Generic.CodeAnalysis.EmptyStatement.DetectedIf) 105 | // Warnings: WordPress.PHP.StrictInArray.MissingTrueStrict 106 | if ( true === in_array( $foo, $bar ) ) { 107 | } 108 | 109 | // Errors: WordPress.PHP.DontExtract.extract_extract 110 | extract( $foobar ); 111 | 112 | // WordPress.WP.CronInterval 113 | function my_add_weekly( $schedules ) { 114 | $schedules['every_6_mins'] = array( 115 | 'interval' => 360, 116 | // Errors: NormalizedArrays.Arrays.CommaAfterLast.MissingMultiLine 117 | // Warnings: WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned 118 | 'display' => __( 'Once every 6 minutes' ) 119 | ); 120 | return $schedules; 121 | } 122 | 123 | // Errors: PEAR.Functions.FunctionCallSignature.SpaceBeforeCloseBracket 124 | // Warnings:WordPress.WP.CronInterval.CronSchedulesInterval 125 | add_filter( 'cron_schedules', 'my_add_weekly'); 126 | 127 | // Errors: Universal.Files.SeparateFunctionsFromOO.Mixed 128 | class TestClass extends MyClass 129 | // Errors: Generic.Classes.OpeningBraceSameLine.BraceOnNewLine 130 | { 131 | // Errors: Squiz.Scope.MethodScope.Missing 132 | function __construct() { 133 | parent::MYCLASS(); 134 | parent::__construct(); 135 | } 136 | } 137 | 138 | // Errors: Generic.Files.OneObjectStructurePerFile.MultipleFound 139 | class OldClass 140 | // Errors: Generic.Classes.OpeningBraceSameLine.BraceOnNewLine 141 | { 142 | 143 | // Errors: Squiz.Scope.MethodScope.Missing 144 | function OldClass() 145 | // Errors: (Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine 146 | { 147 | } 148 | } 149 | 150 | // Errors: Generic.Files.OneObjectStructurePerFile.MultipleFound 151 | // Warnings: Generic.Classes.DuplicateClassName.Found 152 | class TestClass extends MyClass { 153 | 154 | // Errors: Squiz.Scope.MethodScope.Missing 155 | function TestClass() { 156 | parent::MyClass(); 157 | parent::__construct(); 158 | } 159 | } 160 | 161 | // Errors: Squiz.PHP.EmbeddedPhp.ContentAfterEnd & Generic.PHP.DisallowShortOpenTag.EchoFound 162 | ?> 165 | // if (empty($this)) {echo 'This is will not work';} 166 | 167 | // Errors: PEAR.Functions.FunctionCallSignature.SpaceAfterOpenBracket & Squiz.PHP.Eval.Discouraged 168 | eval('$var = 4;'); // Error + Message. 169 | 170 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode 171 | base64_decode( 'VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==' ); 172 | 173 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode 174 | base64_encode( 'This is an encoded string' ); 175 | 176 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.obfuscation_convert_uudecode 177 | convert_uudecode( "+22!L;W9E(%!(4\"$`\n`" ); 178 | 179 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.obfuscation_convert_uudecode 180 | convert_uuencode( "test\ntext text\r\n" ); 181 | 182 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.obfuscation_str_rot13 183 | str_rot13( 'The quick brown fox jumps over the lazy dog.' ); 184 | 185 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize 186 | serialize(); 187 | 188 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize 189 | unserialize(); 190 | 191 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode 192 | urlencode(); 193 | 194 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.system_calls_passthru 195 | passthru( 'cat myfile.zip', $err ); 196 | 197 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.system_calls_proc_open 198 | $process = proc_open( 'php', $descriptorspec, $pipes, $cwd, $env ); 199 | 200 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.system_calls_system 201 | $last_line = system( 'ls', $retval ); 202 | 203 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.system_calls_popen 204 | $handle = popen( '/bin/ls', 'r' ); 205 | 206 | // Warnings: WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting & WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting 207 | error_reporting(); 208 | 209 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_ini_restore 210 | ini_restore(); 211 | 212 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_apache_setenv 213 | apache_setenv(); 214 | 215 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv 216 | putenv(); 217 | 218 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_set_include_path 219 | set_include_path(); 220 | 221 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_restore_include_path && PHPCompatibility.FunctionUse.RemovedFunctions.restore_include_pathDeprecated 222 | restore_include_path(); 223 | 224 | // Errors: PHPCompatibility.FunctionUse.RemovedFunctions.magic_quotes_runtimeDeprecatedRemoved 225 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_magic_quotes_runtime 226 | magic_quotes_runtime(); 227 | 228 | // Errors: PHPCompatibility.FunctionUse.RemovedFunctions.set_magic_quotes_runtimeDeprecatedRemoved 229 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_set_magic_quotes_runtime 230 | set_magic_quotes_runtime(); 231 | 232 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_dl & PHPCompatibility.FunctionUse.RemovedFunctions.dlDeprecated 233 | dl(); 234 | 235 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.system_calls_exec 236 | exec( 'whoami' ); 237 | 238 | // Warnings: WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec 239 | $output = shell_exec( 'ls -lart' ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Error. 240 | 241 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_var_dump 242 | var_dump(); 243 | 244 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_var_export 245 | var_export(); 246 | 247 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_print_r 248 | print_r(); 249 | 250 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_trigger_error 251 | trigger_error( 'message' ); 252 | 253 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler 254 | set_error_handler(); 255 | 256 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace 257 | debug_backtrace(); 258 | 259 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_debug_print_backtrace 260 | debug_print_backtrace(); 261 | 262 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary 263 | wp_debug_backtrace_summary(); 264 | 265 | // Warnings: WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_phpinfo 266 | phpinfo(); 267 | 268 | // Warnings: WordPress.PHP.DevelopmentFunctions.error_log_error_log 269 | error_log(); 270 | 271 | // OK to set 272 | ini_set( 'auto_detect_line_endings', true ); 273 | 274 | // Errors: PHPCompatibility.IniDirectives.RemovedIniDirectives.highlight_bgDeprecatedRemoved 275 | ini_set( 'highlight.bg', '#000000' ); 276 | 277 | // OK to set 278 | ini_set( 'highlight.comment', '#000000' ); 279 | 280 | // OK to set 281 | ini_set( 'highlight.default', '#000000' ); 282 | 283 | // OK to set 284 | ini_set( 'highlight.html', '#000000' ); 285 | 286 | // OK to set 287 | ini_set( 'highlight.keyword', '#000000' ); 288 | 289 | // OK to set 290 | ini_set( 'highlight.string', '#000000' ); 291 | 292 | // OK to set 293 | ini_set( 'short_open_tag', 1 ); 294 | 295 | // Errors: WordPress.PHP.IniSet.bcmath_scale_Disallowed 296 | ini_set( 'bcmath.scale', 1 ); 297 | 298 | // Errors: WordPress.PHP.IniSet.display_errors_Disallowed 299 | ini_set( 'display_errors', 1 ); 300 | 301 | // Errors: WordPress.PHP.IniSet.error_reporting_Disallowed 302 | ini_set( 'error_reporting', 1 ); 303 | 304 | // Errors: WordPress.PHP.IniSet.filter_default_Disallowed 305 | ini_set( 'filter.default', 1 ); 306 | 307 | // Errors: WordPress.PHP.IniSet.filter_default_flags_Disallowed 308 | ini_set( 'filter.default_flags', 1 ); 309 | 310 | // Errors: WordPress.PHP.IniSet.iconv_input_encoding_Disallowed 311 | // Warnings: PHPCompatibility.IniDirectives.RemovedIniDirectives.iconv_input_encodingDeprecated 312 | ini_set( 'iconv.input_encoding', 1 ); 313 | 314 | // Errors: WordPress.PHP.IniSet.iconv_internal_encoding_Disallowed 315 | // Warnings: PHPCompatibility.IniDirectives.RemovedIniDirectives.iconv_internal_encodingDeprecated 316 | ini_set( 'iconv.internal_encoding', 1 ); 317 | 318 | // Errors: WordPress.PHP.IniSet.iconv_output_encoding_Disallowed 319 | // Warnings: PHPCompatibility.IniDirectives.RemovedIniDirectives.iconv_output_encodingDeprecated 320 | ini_set( 'iconv.output_encoding', 1 ); 321 | 322 | // Errors: WordPress.PHP.IniSet.ignore_user_abort_Disallowed 323 | ini_set( 'ignore_user_abort', 1 ); 324 | 325 | // Errors: WordPress.PHP.IniSet.log_errors_Disallowed 326 | ini_set( 'log_errors', 1 ); 327 | 328 | // Errors: WordPress.PHP.IniSet.max_execution_time_Disallowed 329 | ini_set( 'max_execution_time', 1 ); 330 | 331 | // Errors: WordPress.PHP.IniSet.memory_limit_Disallowed 332 | ini_set( 'memory_limit', 1 ); 333 | 334 | // Errors: WordPress.PHP.IniSet.short_open_tag_Disallowed 335 | ini_set( 'short_open_tag', 'off' ); 336 | 337 | // Warnings: WordPress.PHP.IniSet.Risky 338 | ini_set( 'foo', true ); 339 | 340 | // OK to set 341 | ini_alter( 'auto_detect_line_endings', true ); 342 | 343 | // OK to set 344 | ini_alter( 'highlight.bg', '#000000' ); 345 | 346 | // OK to set 347 | ini_alter( 'highlight.comment', '#000000' ); 348 | 349 | // OK to set 350 | ini_alter( 'highlight.default', '#000000' ); 351 | 352 | // OK to set 353 | ini_alter( 'highlight.html', '#000000' ); 354 | 355 | // OK to set 356 | ini_alter( 'highlight.keyword', '#000000' ); 357 | 358 | // OK to set 359 | ini_alter( 'highlight.string', '#000000' ); 360 | 361 | // OK to set 362 | ini_alter( 'short_open_tag', 1 ); 363 | 364 | // Errors: WordPress.PHP.IniSet.bcmath_scale_Disallowed 365 | ini_alter( 'bcmath.scale', 1 ); 366 | 367 | // Errors: WordPress.PHP.IniSet.display_errors_Disallowed 368 | ini_alter( 'display_errors', 1 ); 369 | 370 | // Errors: WordPress.PHP.IniSet.error_reporting_Disallowed 371 | ini_alter( 'error_reporting', 1 ); 372 | 373 | // Errors: WordPress.PHP.IniSet.filter_default_Disallowed 374 | ini_alter( 'filter.default', 1 ); 375 | 376 | // Errors: WordPress.PHP.IniSet.filter_default_flags_Disallowed 377 | ini_alter( 'filter.default_flags', 1 ); 378 | 379 | // Errors: WordPress.PHP.IniSet.iconv_input_encoding_Disallowed 380 | ini_alter( 'iconv.input_encoding', 1 ); 381 | 382 | // Errors: WordPress.PHP.IniSet.iconv_internal_encoding_Disallowed 383 | ini_alter( 'iconv.internal_encoding', 1 ); 384 | 385 | // Errors: WordPress.PHP.IniSet.iconv_output_encoding_Disallowed 386 | ini_alter( 'iconv.output_encoding', 1 ); 387 | 388 | // Errors: WordPress.PHP.IniSet.ignore_user_abort_Disallowed 389 | ini_alter( 'ignore_user_abort', 1 ); 390 | 391 | // Errors: WordPress.PHP.IniSet.log_errors_Disallowed 392 | ini_alter( 'log_errors', 1 ); 393 | 394 | // Errors: WordPress.PHP.IniSet.max_execution_time_Disallowed 395 | ini_alter( 'max_execution_time', 1 ); 396 | 397 | // Errors: WordPress.PHP.IniSet.memory_limit_Disallowed 398 | ini_alter( 'memory_limit', 1 ); 399 | 400 | // Errors: WordPress.PHP.IniSet.short_open_tag_Disallowed 401 | ini_alter( 'short_open_tag', 'off' ); 402 | 403 | // Warnings: WordPress.PHP.IniSet.Risky 404 | ini_alter( 'foo', true ); 405 | 406 | // Warnings: WordPress.WP.AlternativeFunctions.curl_curl_init 407 | curl_init(); 408 | 409 | // Warnings: WordPress.WP.AlternativeFunctions.curl_curl_close 410 | curl_close( $ch ); 411 | 412 | // Warnings: WordPress.WP.AlternativeFunctions.curl_curl_getinfo 413 | CURL_getinfo(); 414 | 415 | // Warnings: WordPress.WP.AlternativeFunctions.parse_url_parse_url 416 | parse_url( 'http://example.com/' ); 417 | 418 | // Warnings: WordPress.WP.AlternativeFunctions.json_encode_json_encode 419 | $json = json_encode( $thing ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Warning. 420 | 421 | // Warnings: WordPress.WP.AlternativeFunctions.file_system_operations_readfile 422 | readfile(); 423 | 424 | // Warnings: WordPress.WP.AlternativeFunctions.file_system_operations_fclose 425 | fclose(); 426 | 427 | // Warnings: WordPress.WP.AlternativeFunctions.file_system_operations_fopen 428 | fopen(); 429 | 430 | // Warnings: WordPress.WP.AlternativeFunctions.file_system_operations_fread 431 | fread(); 432 | 433 | // Warnings: WordPress.WP.AlternativeFunctions.file_system_operations_fsockopen 434 | fsockopen(); 435 | 436 | // Warnings: WordPress.WP.AlternativeFunctions.file_system_operations_pfsockopen 437 | pfsockopen(); 438 | 439 | // Warnings: WordPress.WP.AlternativeFunctions.rand_seeding_srand 440 | srand(); 441 | 442 | // Warnings: WordPress.WP.AlternativeFunctions.rand_seeding_mt_srand 443 | mt_srand(); 444 | 445 | // Warnings: WordPress.WP.AlternativeFunctions.rand_rand 446 | rand(); 447 | 448 | // Warnings: WordPress.WP.AlternativeFunctions.rand_mt_rand 449 | mt_rand(); 450 | 451 | // Errors: Generic.Files.OneObjectStructurePerFile.MultipleFound 452 | class MyClass { 453 | 454 | // Errors: Squiz.Scope.MethodScope.Missing 455 | function my_function() { 456 | // Errors: Squiz.Functions.MultiLineFunctionDeclaration.SpaceAfterFunction 457 | return function() { 458 | $this->my_callback(); // OK - new VariableAnalysis doesn't flag $this as undefined in closure. 459 | }; 460 | } 461 | 462 | // Errors: Squiz.Scope.MethodScope.Missing 463 | function my_callback() {} 464 | } 465 | 466 | // Errors: Generic.VersionControl.GitMergeConflict.OpenerFound 467 | ?> 468 | <<<<<<< HEAD 469 | 470 | >>>>>>> 471 | 479 | 480 | 481 | [ 16 | 4 => 1, 17 | 5 => 1, 18 | 6 => 41, 19 | 10 => 5, 20 | 15 => 2, 21 | 20 => 1, 22 | 22 => 1, 23 | 26 => 1, 24 | 31 => 1, 25 | 33 => 3, 26 | 35 => 1, 27 | 40 => 1, 28 | 43 => 2, 29 | 57 => 1, 30 | 67 => 1, 31 | 81 => 1, 32 | 89 => 1, 33 | 96 => 1, 34 | 101 => 2, 35 | 106 => 1, 36 | 110 => 1, 37 | 118 => 1, 38 | 125 => 1, 39 | 128 => 1, 40 | 130 => 1, 41 | 132 => 1, 42 | 139 => 1, 43 | 141 => 1, 44 | 144 => 1, 45 | 146 => 1, 46 | 152 => 1, 47 | 155 => 1, 48 | 162 => 2, 49 | 168 => 3, 50 | 222 => 1, 51 | 226 => 1, 52 | 230 => 1, 53 | 275 => 1, 54 | 296 => 1, 55 | 299 => 1, 56 | 302 => 1, 57 | 305 => 1, 58 | 308 => 1, 59 | 312 => 1, 60 | 316 => 1, 61 | 320 => 1, 62 | 323 => 1, 63 | 326 => 1, 64 | 329 => 1, 65 | 332 => 1, 66 | 335 => 1, 67 | 365 => 1, 68 | 368 => 1, 69 | 371 => 1, 70 | 374 => 1, 71 | 377 => 1, 72 | 380 => 1, 73 | 383 => 1, 74 | 386 => 1, 75 | 389 => 1, 76 | 392 => 1, 77 | 395 => 1, 78 | 398 => 1, 79 | 401 => 1, 80 | 452 => 1, 81 | 455 => 1, 82 | 457 => 1, 83 | 463 => 1, 84 | 468 => 1, 85 | 475 => 1, 86 | 478 => 1, 87 | 483 => 4, 88 | 486 => 1, 89 | ], 90 | 'warnings' => [ 91 | 15 => 1, 92 | 40 => 2, 93 | 47 => 1, 94 | 51 => 1, 95 | 54 => 1, 96 | 67 => 1, 97 | 73 => 3, 98 | 77 => 1, 99 | 83 => 1, 100 | 85 => 2, 101 | 87 => 1, 102 | 96 => 1, 103 | 101 => 1, 104 | 106 => 1, 105 | 118 => 1, 106 | 125 => 1, 107 | 152 => 1, 108 | 171 => 1, 109 | 174 => 1, 110 | 177 => 1, 111 | 180 => 1, 112 | 183 => 1, 113 | 186 => 1, 114 | 189 => 1, 115 | 192 => 1, 116 | 195 => 1, 117 | 198 => 1, 118 | 201 => 1, 119 | 204 => 1, 120 | 207 => 2, 121 | 210 => 1, 122 | 213 => 1, 123 | 216 => 1, 124 | 219 => 1, 125 | 222 => 1, 126 | 226 => 1, 127 | 230 => 1, 128 | 233 => 2, 129 | 236 => 1, 130 | 239 => 1, 131 | 242 => 1, 132 | 245 => 1, 133 | 248 => 1, 134 | 251 => 1, 135 | 254 => 1, 136 | 257 => 1, 137 | 260 => 1, 138 | 263 => 1, 139 | 266 => 1, 140 | 269 => 1, 141 | 272 => 1, 142 | 305 => 1, 143 | 312 => 1, 144 | 316 => 1, 145 | 320 => 1, 146 | 338 => 1, 147 | 404 => 1, 148 | 407 => 1, 149 | 410 => 1, 150 | 413 => 1, 151 | 416 => 1, 152 | 419 => 1, 153 | 422 => 1, 154 | 425 => 1, 155 | 428 => 1, 156 | 431 => 1, 157 | 434 => 1, 158 | 437 => 1, 159 | 440 => 1, 160 | 443 => 1, 161 | 446 => 1, 162 | 449 => 1, 163 | 475 => 1, 164 | 486 => 1, 165 | ], 166 | 'messages' => [], 167 | ]; 168 | 169 | // If we're running on PHP 7.4, we need to account for the error thrown because `restore_include_path()` is deprecated. 170 | // See https://www.php.net/manual/en/function.restore-include-path.php 171 | if ( version_compare( PHP_VERSION, '7.4.0', '>=' ) && version_compare( PHP_VERSION, '8.0.0', '<' ) ) { 172 | $expected['errors'][ 222 ] = 2; 173 | } 174 | 175 | // We have some specific errors that are only thrown on PHP 8.2+. 176 | if ( version_compare( PHP_VERSION, '8.2.0', '<' ) ) { 177 | $expected['errors'][ 486 ] = 0; 178 | } 179 | 180 | if ( version_compare( PHP_VERSION, '7.2', '<' ) ) { 181 | $expected['errors'][ 483 ] = 4; 182 | $expected['warnings'][ 1 ] = 1; 183 | } 184 | 185 | require __DIR__ . '/../tests/RulesetTest.php'; 186 | 187 | // Run the tests! 188 | $test = new \PhpcsComposer\RulesetTest( 'TenUpDefault', $expected ); 189 | if ( $test->passes() ) { 190 | printf( 'All TenUpDefault tests passed!' . PHP_EOL ); 191 | exit( 0 ); 192 | } 193 | 194 | exit( 1 ); 195 | -------------------------------------------------------------------------------- /10up-Default/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A base ruleset that all other 10up rulesets should extend. 4 | 5 | */phpunit.xml* 6 | */dist/* 7 | */languages/* 8 | 9 | 10 | */bower-components/* 11 | */node_modules/* 12 | */vendor/* 13 | 14 | 15 | *\.(css|js) 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 0 25 | 26 | 27 | 28 | 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | warning 87 | 88 | 89 | 90 | 91 | warning 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 10up, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 10up PHPCS Configuration 2 | 3 | > Composer library to provide drop in installation and configuration of [WPCS](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) and [PHPCompatibilityWP](https://github.com/PHPCompatibility/PHPCompatibilityWP), setting reasonable defaults for WordPress development with nearly zero configuration. 4 | 5 | [![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) [![MIT License](https://img.shields.io/github/license/10up/phpcs-composer.svg)](https://github.com/10up/phpcs-composer/blob/master/LICENSE) 6 | 7 | ## Installation 8 | 9 | Install the library via Composer: 10 | 11 | ```bash 12 | $ composer require --dev 10up/phpcs-composer:"^3.0" 13 | ``` 14 | 15 | That's it! 16 | 17 | ## Usage 18 | 19 | Lint your PHP files with the following command: 20 | 21 | ```bash 22 | $ ./vendor/bin/phpcs . 23 | ``` 24 | 25 | If relying on Composer, edited the `composer.json` file by adding the following: 26 | 27 | ```json 28 | "scripts": { 29 | "lint": [ 30 | "phpcs ." 31 | ], 32 | } 33 | ``` 34 | 35 | Then lint via: 36 | 37 | ```bash 38 | $ composer run lint 39 | ``` 40 | 41 | ### Continuous Integration 42 | 43 | PHPCS Configuration plays nicely with Continuous Integration solutions. Out of the box, the library loads the `10up-Default` ruleset, and checks for syntax errors for PHP 8.2 or higher. 44 | 45 | To override the default PHP version check, set the `--runtime-set testVersion 7.0-` configuration option. Example for PHP version 7.2 and above: 46 | 47 | ```bash 48 | $ ./vendor/bin/phpcs --runtime-set testVersion 7.2- 49 | ``` 50 | 51 | See more [information about specifying PHP version](https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions). 52 | 53 | Note that you can only overrule PHP version check from the command-line. 54 | 55 | ### IDE Integration 56 | 57 | Some IDE integrations of PHPCS fail to register the `10up-Default` ruleset. In order to rectify this, place `.phpcs.xml.dist` at your project root: 58 | 59 | ```xml 60 | 61 | 62 | 63 | 64 | ``` 65 | 66 | ## Support Level 67 | 68 | **Active:** 10up is actively working on this, and we expect to continue work for the foreseeable future including keeping tested up to the most recent version of WordPress. Bug reports, feature requests, questions, and pull requests are welcome. 69 | 70 | ## Like what you see? 71 | 72 |

73 | 74 |

75 | -------------------------------------------------------------------------------- /bin/ruleset-tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Run ruleset tests. 4 | # 5 | # This will check that each ruleset correctly includes the expected sniffs from this and other packages. If a sniff 6 | # reference has changed (moved category, or been renamed, etc.) then the expected errors, warnings, and messages will 7 | # not match what the output is, when the ruleset-test.inc is checked with PHPCS against the relevant ruleset. 8 | # 9 | # To run the tests, make sure you have the PHPCS, including the TenUpDefault standard, installed and executable 10 | # * using the `phpcs --standard=TenUpDefault` command. 11 | # 12 | # From the root of this VIP-Coding-Standards package, you can then run: 13 | # 14 | # ./bin/ruleset-tests 15 | 16 | # Set PHPCS_BIN, which is used in the tests/RulesetTest.php files. 17 | PHPCS_BIN="$(pwd)/vendor/bin/phpcs" 18 | export PHPCS_BIN 19 | 20 | php ./10up-Default/ruleset-test.php 21 | -------------------------------------------------------------------------------- /bin/xml-lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Check XML files. 4 | # 5 | # @link http://xmlsoft.org/xmllint.html 6 | # 7 | # EXAMPLE TO RUN LOCALLY: 8 | # 9 | # ./bin/xml-lint 10 | 11 | # Validate the ruleset XML files. 12 | xmllint --noout --schema ./vendor/squizlabs/php_codesniffer/phpcs.xsd ./*/ruleset.xml 13 | 14 | # Check the code-style consistency of the XML files. 15 | export XMLLINT_INDENT=" " # This is a tab character. 16 | diff -B --tabsize=4 ./10up-Default/ruleset.xml <(xmllint --format "./10up-Default/ruleset.xml") 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "10up/phpcs-composer", 3 | "description": "10up's PHP CodeSniffer Ruleset", 4 | "type": "phpcodesniffer-standard", 5 | "license": "MIT", 6 | "require": { 7 | "automattic/vipwpcs": "^3.0", 8 | "phpcompatibility/phpcompatibility-wp": "^2" 9 | }, 10 | "prefer-stable": true, 11 | "authors": [ 12 | { 13 | "name": "10up", 14 | "homepage": "https://10up.com/" 15 | } 16 | ], 17 | "scripts": { 18 | "config-cs": [ 19 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", 20 | "\"vendor/bin/phpcs\" --config-set default_standard 10up-Default" 21 | ], 22 | "post-install-cmd": "@config-cs", 23 | "post-update-cmd": "@config-cs", 24 | "lint": "\"vendor/bin/phpcs\" . " 25 | }, 26 | "config": { 27 | "allow-plugins": { 28 | "dealerdirect/phpcodesniffer-composer-installer": true 29 | } 30 | }, 31 | "minimum-stability": "dev", 32 | "require-dev": { 33 | "dealerdirect/phpcodesniffer-composer-installer": "*", 34 | "phpcompatibility/php-compatibility": "dev-develop as 9.99.99" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "5a7e2b9a63bc91d26ccd8c8109b246e8", 8 | "packages": [ 9 | { 10 | "name": "automattic/vipwpcs", 11 | "version": "3.0.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/Automattic/VIP-Coding-Standards.git", 15 | "reference": "1b8960ebff9ea3eb482258a906ece4d1ee1e25fd" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/1b8960ebff9ea3eb482258a906ece4d1ee1e25fd", 20 | "reference": "1b8960ebff9ea3eb482258a906ece4d1ee1e25fd", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.4", 25 | "phpcsstandards/phpcsextra": "^1.1.0", 26 | "phpcsstandards/phpcsutils": "^1.0.8", 27 | "sirbrillig/phpcs-variable-analysis": "^2.11.17", 28 | "squizlabs/php_codesniffer": "^3.7.2", 29 | "wp-coding-standards/wpcs": "^3.0" 30 | }, 31 | "require-dev": { 32 | "php-parallel-lint/php-console-highlighter": "^1.0.0", 33 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 34 | "phpcompatibility/php-compatibility": "^9", 35 | "phpcsstandards/phpcsdevtools": "^1.0", 36 | "phpunit/phpunit": "^4 || ^5 || ^6 || ^7" 37 | }, 38 | "type": "phpcodesniffer-standard", 39 | "notification-url": "https://packagist.org/downloads/", 40 | "license": [ 41 | "MIT" 42 | ], 43 | "authors": [ 44 | { 45 | "name": "Contributors", 46 | "homepage": "https://github.com/Automattic/VIP-Coding-Standards/graphs/contributors" 47 | } 48 | ], 49 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress VIP minimum coding conventions", 50 | "keywords": [ 51 | "phpcs", 52 | "standards", 53 | "static analysis", 54 | "wordpress" 55 | ], 56 | "support": { 57 | "issues": "https://github.com/Automattic/VIP-Coding-Standards/issues", 58 | "source": "https://github.com/Automattic/VIP-Coding-Standards", 59 | "wiki": "https://github.com/Automattic/VIP-Coding-Standards/wiki" 60 | }, 61 | "time": "2023-09-05T11:01:05+00:00" 62 | }, 63 | { 64 | "name": "dealerdirect/phpcodesniffer-composer-installer", 65 | "version": "v1.0.0", 66 | "source": { 67 | "type": "git", 68 | "url": "https://github.com/PHPCSStandards/composer-installer.git", 69 | "reference": "4be43904336affa5c2f70744a348312336afd0da" 70 | }, 71 | "dist": { 72 | "type": "zip", 73 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", 74 | "reference": "4be43904336affa5c2f70744a348312336afd0da", 75 | "shasum": "" 76 | }, 77 | "require": { 78 | "composer-plugin-api": "^1.0 || ^2.0", 79 | "php": ">=5.4", 80 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" 81 | }, 82 | "require-dev": { 83 | "composer/composer": "*", 84 | "ext-json": "*", 85 | "ext-zip": "*", 86 | "php-parallel-lint/php-parallel-lint": "^1.3.1", 87 | "phpcompatibility/php-compatibility": "^9.0", 88 | "yoast/phpunit-polyfills": "^1.0" 89 | }, 90 | "type": "composer-plugin", 91 | "extra": { 92 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" 93 | }, 94 | "autoload": { 95 | "psr-4": { 96 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" 97 | } 98 | }, 99 | "notification-url": "https://packagist.org/downloads/", 100 | "license": [ 101 | "MIT" 102 | ], 103 | "authors": [ 104 | { 105 | "name": "Franck Nijhof", 106 | "email": "franck.nijhof@dealerdirect.com", 107 | "homepage": "http://www.frenck.nl", 108 | "role": "Developer / IT Manager" 109 | }, 110 | { 111 | "name": "Contributors", 112 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" 113 | } 114 | ], 115 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin", 116 | "homepage": "http://www.dealerdirect.com", 117 | "keywords": [ 118 | "PHPCodeSniffer", 119 | "PHP_CodeSniffer", 120 | "code quality", 121 | "codesniffer", 122 | "composer", 123 | "installer", 124 | "phpcbf", 125 | "phpcs", 126 | "plugin", 127 | "qa", 128 | "quality", 129 | "standard", 130 | "standards", 131 | "style guide", 132 | "stylecheck", 133 | "tests" 134 | ], 135 | "support": { 136 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues", 137 | "source": "https://github.com/PHPCSStandards/composer-installer" 138 | }, 139 | "time": "2023-01-05T11:28:13+00:00" 140 | }, 141 | { 142 | "name": "phpcompatibility/php-compatibility", 143 | "version": "dev-develop", 144 | "source": { 145 | "type": "git", 146 | "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", 147 | "reference": "8770f4f4677cc649a1df5e73dc6195d9887f4641" 148 | }, 149 | "dist": { 150 | "type": "zip", 151 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/8770f4f4677cc649a1df5e73dc6195d9887f4641", 152 | "reference": "8770f4f4677cc649a1df5e73dc6195d9887f4641", 153 | "shasum": "" 154 | }, 155 | "require": { 156 | "php": ">=5.4", 157 | "phpcsstandards/phpcsutils": "^1.0.5", 158 | "squizlabs/php_codesniffer": "^3.7.1" 159 | }, 160 | "replace": { 161 | "wimg/php-compatibility": "*" 162 | }, 163 | "require-dev": { 164 | "php-parallel-lint/php-console-highlighter": "^1.0.0", 165 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 166 | "phpcsstandards/phpcsdevcs": "^1.1.3", 167 | "phpcsstandards/phpcsdevtools": "^1.2.0", 168 | "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4 || ^10.1.0", 169 | "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" 170 | }, 171 | "suggest": { 172 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 173 | }, 174 | "default-branch": true, 175 | "type": "phpcodesniffer-standard", 176 | "extra": { 177 | "branch-alias": { 178 | "dev-master": "9.x-dev", 179 | "dev-develop": "10.x-dev" 180 | } 181 | }, 182 | "notification-url": "https://packagist.org/downloads/", 183 | "license": [ 184 | "LGPL-3.0-or-later" 185 | ], 186 | "authors": [ 187 | { 188 | "name": "Wim Godden", 189 | "homepage": "https://github.com/wimg", 190 | "role": "lead" 191 | }, 192 | { 193 | "name": "Juliette Reinders Folmer", 194 | "homepage": "https://github.com/jrfnl", 195 | "role": "lead" 196 | }, 197 | { 198 | "name": "Contributors", 199 | "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" 200 | } 201 | ], 202 | "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", 203 | "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", 204 | "keywords": [ 205 | "compatibility", 206 | "phpcs", 207 | "standards", 208 | "static analysis" 209 | ], 210 | "support": { 211 | "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", 212 | "source": "https://github.com/PHPCompatibility/PHPCompatibility" 213 | }, 214 | "time": "2023-11-13T01:28:23+00:00" 215 | }, 216 | { 217 | "name": "phpcompatibility/phpcompatibility-paragonie", 218 | "version": "1.3.2", 219 | "source": { 220 | "type": "git", 221 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", 222 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" 223 | }, 224 | "dist": { 225 | "type": "zip", 226 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", 227 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", 228 | "shasum": "" 229 | }, 230 | "require": { 231 | "phpcompatibility/php-compatibility": "^9.0" 232 | }, 233 | "require-dev": { 234 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7", 235 | "paragonie/random_compat": "dev-master", 236 | "paragonie/sodium_compat": "dev-master" 237 | }, 238 | "suggest": { 239 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", 240 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 241 | }, 242 | "type": "phpcodesniffer-standard", 243 | "notification-url": "https://packagist.org/downloads/", 244 | "license": [ 245 | "LGPL-3.0-or-later" 246 | ], 247 | "authors": [ 248 | { 249 | "name": "Wim Godden", 250 | "role": "lead" 251 | }, 252 | { 253 | "name": "Juliette Reinders Folmer", 254 | "role": "lead" 255 | } 256 | ], 257 | "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", 258 | "homepage": "http://phpcompatibility.com/", 259 | "keywords": [ 260 | "compatibility", 261 | "paragonie", 262 | "phpcs", 263 | "polyfill", 264 | "standards", 265 | "static analysis" 266 | ], 267 | "support": { 268 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", 269 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" 270 | }, 271 | "time": "2022-10-25T01:46:02+00:00" 272 | }, 273 | { 274 | "name": "phpcompatibility/phpcompatibility-wp", 275 | "version": "2.1.4", 276 | "source": { 277 | "type": "git", 278 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", 279 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" 280 | }, 281 | "dist": { 282 | "type": "zip", 283 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", 284 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", 285 | "shasum": "" 286 | }, 287 | "require": { 288 | "phpcompatibility/php-compatibility": "^9.0", 289 | "phpcompatibility/phpcompatibility-paragonie": "^1.0" 290 | }, 291 | "require-dev": { 292 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7" 293 | }, 294 | "suggest": { 295 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", 296 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." 297 | }, 298 | "type": "phpcodesniffer-standard", 299 | "notification-url": "https://packagist.org/downloads/", 300 | "license": [ 301 | "LGPL-3.0-or-later" 302 | ], 303 | "authors": [ 304 | { 305 | "name": "Wim Godden", 306 | "role": "lead" 307 | }, 308 | { 309 | "name": "Juliette Reinders Folmer", 310 | "role": "lead" 311 | } 312 | ], 313 | "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", 314 | "homepage": "http://phpcompatibility.com/", 315 | "keywords": [ 316 | "compatibility", 317 | "phpcs", 318 | "standards", 319 | "static analysis", 320 | "wordpress" 321 | ], 322 | "support": { 323 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", 324 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" 325 | }, 326 | "time": "2022-10-24T09:00:36+00:00" 327 | }, 328 | { 329 | "name": "phpcsstandards/phpcsextra", 330 | "version": "1.1.2", 331 | "source": { 332 | "type": "git", 333 | "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", 334 | "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5" 335 | }, 336 | "dist": { 337 | "type": "zip", 338 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/746c3190ba8eb2f212087c947ba75f4f5b9a58d5", 339 | "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5", 340 | "shasum": "" 341 | }, 342 | "require": { 343 | "php": ">=5.4", 344 | "phpcsstandards/phpcsutils": "^1.0.8", 345 | "squizlabs/php_codesniffer": "^3.7.1" 346 | }, 347 | "require-dev": { 348 | "php-parallel-lint/php-console-highlighter": "^1.0", 349 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 350 | "phpcsstandards/phpcsdevcs": "^1.1.6", 351 | "phpcsstandards/phpcsdevtools": "^1.2.1", 352 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" 353 | }, 354 | "type": "phpcodesniffer-standard", 355 | "extra": { 356 | "branch-alias": { 357 | "dev-stable": "1.x-dev", 358 | "dev-develop": "1.x-dev" 359 | } 360 | }, 361 | "notification-url": "https://packagist.org/downloads/", 362 | "license": [ 363 | "LGPL-3.0-or-later" 364 | ], 365 | "authors": [ 366 | { 367 | "name": "Juliette Reinders Folmer", 368 | "homepage": "https://github.com/jrfnl", 369 | "role": "lead" 370 | }, 371 | { 372 | "name": "Contributors", 373 | "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" 374 | } 375 | ], 376 | "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", 377 | "keywords": [ 378 | "PHP_CodeSniffer", 379 | "phpcbf", 380 | "phpcodesniffer-standard", 381 | "phpcs", 382 | "standards", 383 | "static analysis" 384 | ], 385 | "support": { 386 | "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", 387 | "source": "https://github.com/PHPCSStandards/PHPCSExtra" 388 | }, 389 | "time": "2023-09-20T22:06:18+00:00" 390 | }, 391 | { 392 | "name": "phpcsstandards/phpcsutils", 393 | "version": "1.0.8", 394 | "source": { 395 | "type": "git", 396 | "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", 397 | "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" 398 | }, 399 | "dist": { 400 | "type": "zip", 401 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", 402 | "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", 403 | "shasum": "" 404 | }, 405 | "require": { 406 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", 407 | "php": ">=5.4", 408 | "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" 409 | }, 410 | "require-dev": { 411 | "ext-filter": "*", 412 | "php-parallel-lint/php-console-highlighter": "^1.0", 413 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 414 | "phpcsstandards/phpcsdevcs": "^1.1.6", 415 | "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" 416 | }, 417 | "type": "phpcodesniffer-standard", 418 | "extra": { 419 | "branch-alias": { 420 | "dev-stable": "1.x-dev", 421 | "dev-develop": "1.x-dev" 422 | } 423 | }, 424 | "autoload": { 425 | "classmap": [ 426 | "PHPCSUtils/" 427 | ] 428 | }, 429 | "notification-url": "https://packagist.org/downloads/", 430 | "license": [ 431 | "LGPL-3.0-or-later" 432 | ], 433 | "authors": [ 434 | { 435 | "name": "Juliette Reinders Folmer", 436 | "homepage": "https://github.com/jrfnl", 437 | "role": "lead" 438 | }, 439 | { 440 | "name": "Contributors", 441 | "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" 442 | } 443 | ], 444 | "description": "A suite of utility functions for use with PHP_CodeSniffer", 445 | "homepage": "https://phpcsutils.com/", 446 | "keywords": [ 447 | "PHP_CodeSniffer", 448 | "phpcbf", 449 | "phpcodesniffer-standard", 450 | "phpcs", 451 | "phpcs3", 452 | "standards", 453 | "static analysis", 454 | "tokens", 455 | "utility" 456 | ], 457 | "support": { 458 | "docs": "https://phpcsutils.com/", 459 | "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", 460 | "source": "https://github.com/PHPCSStandards/PHPCSUtils" 461 | }, 462 | "time": "2023-07-16T21:39:41+00:00" 463 | }, 464 | { 465 | "name": "sirbrillig/phpcs-variable-analysis", 466 | "version": "v2.11.17", 467 | "source": { 468 | "type": "git", 469 | "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", 470 | "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049" 471 | }, 472 | "dist": { 473 | "type": "zip", 474 | "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/3b71162a6bf0cde2bff1752e40a1788d8273d049", 475 | "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049", 476 | "shasum": "" 477 | }, 478 | "require": { 479 | "php": ">=5.4.0", 480 | "squizlabs/php_codesniffer": "^3.5.6" 481 | }, 482 | "require-dev": { 483 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", 484 | "phpcsstandards/phpcsdevcs": "^1.1", 485 | "phpstan/phpstan": "^1.7", 486 | "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0", 487 | "sirbrillig/phpcs-import-detection": "^1.1", 488 | "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0@beta" 489 | }, 490 | "type": "phpcodesniffer-standard", 491 | "autoload": { 492 | "psr-4": { 493 | "VariableAnalysis\\": "VariableAnalysis/" 494 | } 495 | }, 496 | "notification-url": "https://packagist.org/downloads/", 497 | "license": [ 498 | "BSD-2-Clause" 499 | ], 500 | "authors": [ 501 | { 502 | "name": "Sam Graham", 503 | "email": "php-codesniffer-variableanalysis@illusori.co.uk" 504 | }, 505 | { 506 | "name": "Payton Swick", 507 | "email": "payton@foolord.com" 508 | } 509 | ], 510 | "description": "A PHPCS sniff to detect problems with variables.", 511 | "keywords": [ 512 | "phpcs", 513 | "static analysis" 514 | ], 515 | "support": { 516 | "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", 517 | "source": "https://github.com/sirbrillig/phpcs-variable-analysis", 518 | "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" 519 | }, 520 | "time": "2023-08-05T23:46:11+00:00" 521 | }, 522 | { 523 | "name": "squizlabs/php_codesniffer", 524 | "version": "3.7.2", 525 | "source": { 526 | "type": "git", 527 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 528 | "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" 529 | }, 530 | "dist": { 531 | "type": "zip", 532 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", 533 | "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", 534 | "shasum": "" 535 | }, 536 | "require": { 537 | "ext-simplexml": "*", 538 | "ext-tokenizer": "*", 539 | "ext-xmlwriter": "*", 540 | "php": ">=5.4.0" 541 | }, 542 | "require-dev": { 543 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 544 | }, 545 | "bin": [ 546 | "bin/phpcs", 547 | "bin/phpcbf" 548 | ], 549 | "type": "library", 550 | "extra": { 551 | "branch-alias": { 552 | "dev-master": "3.x-dev" 553 | } 554 | }, 555 | "notification-url": "https://packagist.org/downloads/", 556 | "license": [ 557 | "BSD-3-Clause" 558 | ], 559 | "authors": [ 560 | { 561 | "name": "Greg Sherwood", 562 | "role": "lead" 563 | } 564 | ], 565 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 566 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", 567 | "keywords": [ 568 | "phpcs", 569 | "standards", 570 | "static analysis" 571 | ], 572 | "support": { 573 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", 574 | "source": "https://github.com/squizlabs/PHP_CodeSniffer", 575 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" 576 | }, 577 | "time": "2023-02-22T23:07:41+00:00" 578 | }, 579 | { 580 | "name": "wp-coding-standards/wpcs", 581 | "version": "3.0.1", 582 | "source": { 583 | "type": "git", 584 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", 585 | "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" 586 | }, 587 | "dist": { 588 | "type": "zip", 589 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", 590 | "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", 591 | "shasum": "" 592 | }, 593 | "require": { 594 | "ext-filter": "*", 595 | "ext-libxml": "*", 596 | "ext-tokenizer": "*", 597 | "ext-xmlreader": "*", 598 | "php": ">=5.4", 599 | "phpcsstandards/phpcsextra": "^1.1.0", 600 | "phpcsstandards/phpcsutils": "^1.0.8", 601 | "squizlabs/php_codesniffer": "^3.7.2" 602 | }, 603 | "require-dev": { 604 | "php-parallel-lint/php-console-highlighter": "^1.0.0", 605 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 606 | "phpcompatibility/php-compatibility": "^9.0", 607 | "phpcsstandards/phpcsdevtools": "^1.2.0", 608 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 609 | }, 610 | "suggest": { 611 | "ext-iconv": "For improved results", 612 | "ext-mbstring": "For improved results" 613 | }, 614 | "type": "phpcodesniffer-standard", 615 | "notification-url": "https://packagist.org/downloads/", 616 | "license": [ 617 | "MIT" 618 | ], 619 | "authors": [ 620 | { 621 | "name": "Contributors", 622 | "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" 623 | } 624 | ], 625 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", 626 | "keywords": [ 627 | "phpcs", 628 | "standards", 629 | "static analysis", 630 | "wordpress" 631 | ], 632 | "support": { 633 | "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", 634 | "source": "https://github.com/WordPress/WordPress-Coding-Standards", 635 | "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" 636 | }, 637 | "funding": [ 638 | { 639 | "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", 640 | "type": "custom" 641 | } 642 | ], 643 | "time": "2023-09-14T07:06:09+00:00" 644 | } 645 | ], 646 | "packages-dev": [], 647 | "aliases": [ 648 | { 649 | "package": "phpcompatibility/php-compatibility", 650 | "version": "dev-develop", 651 | "alias": "9.99.99", 652 | "alias_normalized": "9.99.99.0" 653 | } 654 | ], 655 | "minimum-stability": "dev", 656 | "stability-flags": { 657 | "phpcompatibility/php-compatibility": 20 658 | }, 659 | "prefer-stable": true, 660 | "prefer-lowest": false, 661 | "platform": [], 662 | "platform-dev": [], 663 | "plugin-api-version": "2.3.0" 664 | } 665 | -------------------------------------------------------------------------------- /tests/RulesetTest.php: -------------------------------------------------------------------------------- 1 | '10up-Default', 90 | ]; 91 | 92 | /** 93 | * String returned by PHP_CodeSniffer report for an Error. 94 | */ 95 | const ERROR_TYPE = 'ERROR'; 96 | 97 | /** 98 | * Init the object by processing the test file. 99 | * 100 | * @param string $ruleset Name of the ruleset e.g. TenUpDefault. 101 | * @param array $expected The array of expected errors, warnings and messages. 102 | */ 103 | public function __construct( $ruleset, $expected = [] ) { 104 | $this->ruleset = $ruleset; 105 | $this->expected = $expected; 106 | 107 | if ( isset( $this->directory_mapping[ $ruleset ] ) ) { 108 | $this->ruleset_directory = $this->directory_mapping[ $ruleset ]; 109 | } else { 110 | $this->ruleset_directory = $ruleset; 111 | } 112 | 113 | // Travis and Windows support. 114 | $phpcs_bin = getenv( 'PHPCS_BIN' ); 115 | if ( $phpcs_bin === false ) { 116 | // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is test code, not production. 117 | putenv( 'PHPCS_BIN=phpcs' ); 118 | } else { 119 | $this->phpcs_bin = realpath( $phpcs_bin ); 120 | } 121 | 122 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 123 | printf( 'Testing the ' . $this->ruleset . ' ruleset.' . PHP_EOL ); 124 | 125 | $output = $this->collect_phpcs_result(); 126 | 127 | if ( empty( $output ) || ! is_object( $output ) ) { 128 | printf( 'The PHPCS command checking the ruleset hasn\'t returned any issues. Bailing ...' . PHP_EOL ); 129 | exit( 1 ); // Die early, if we don't have any output. 130 | } 131 | 132 | $this->process_output( $output ); 133 | } 134 | 135 | /** 136 | * Run all the tests and return whether test was successful. 137 | * 138 | * @return bool 139 | */ 140 | public function passes() { 141 | $this->run(); 142 | 143 | return ! $this->found_issue; 144 | } 145 | 146 | /** 147 | * Run all the tests. 148 | * 149 | * @return void 150 | */ 151 | private function run() { 152 | // Check for missing expected values. 153 | $this->check_missing_expected_values(); 154 | // Check for extra values which were not expected. 155 | $this->check_unexpected_values(); 156 | // Check for expected messages. 157 | $this->check_messages(); 158 | } 159 | 160 | /** 161 | * Collect the PHP_CodeSniffer result. 162 | * 163 | * @return array Returns an associative array with keys of `totals` and `files`. 164 | */ 165 | private function collect_phpcs_result() { 166 | $php = ''; 167 | if ( \PHP_BINARY && in_array( \PHP_SAPI, [ 'cgi-fcgi', 'cli', 'cli-server', 'phpdbg' ], true ) ) { 168 | $php = \PHP_BINARY . ' '; 169 | } 170 | 171 | $shell = sprintf( 172 | '%1$s%2$s --severity=1 --standard=%3$s --report=json ./%3$s/ruleset-test.inc', 173 | $php, // Current PHP executable if available. 174 | $this->phpcs_bin, 175 | $this->ruleset_directory 176 | ); 177 | 178 | // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec -- This is test code, not production. 179 | $output = shell_exec( $shell ); 180 | 181 | return json_decode( $output ); 182 | } 183 | 184 | /** 185 | * Process the Decoded JSON output from PHP_CodeSniffer. 186 | * 187 | * @param \stdClass $output Decoded JSON output from PHP_CodeSniffer. 188 | * 189 | * @return void 190 | */ 191 | private function process_output( $output ) { 192 | foreach ( $output->files as $file ) { 193 | $this->process_file( $file ); 194 | } 195 | } 196 | 197 | /** 198 | * Process single file of within PHP_CodeSniffer results. 199 | * 200 | * @param \stdClass $file File output. 201 | * 202 | * @return void 203 | */ 204 | private function process_file( $file ) { 205 | foreach ( $file->messages as $violation ) { 206 | $this->process_violation( $violation ); 207 | } 208 | } 209 | 210 | /** 211 | * Process single violation within PHP_CodeSniffer results. 212 | * 213 | * @param \stdClass $violation Violation data. 214 | * 215 | * @return void 216 | */ 217 | private function process_violation( $violation ) { 218 | if ( $this->violation_type_is_error( $violation ) ) { 219 | $this->add_error_for_line( $violation->line ); 220 | } else { 221 | $this->add_warning_for_line( $violation->line ); 222 | } 223 | 224 | $this->add_message_for_line( $violation->line, $violation->message ); 225 | } 226 | 227 | /** 228 | * Check if violation is an error. 229 | * 230 | * @param \stdClass $violation Violation data. 231 | * 232 | * @return bool True if string matches error type. 233 | */ 234 | private function violation_type_is_error( $violation ) { 235 | return $violation->type === self::ERROR_TYPE; 236 | } 237 | 238 | /** 239 | * Add 1 to the number of errors for the given line. 240 | * 241 | * @param int $line Line number. 242 | * 243 | * @return void 244 | */ 245 | private function add_error_for_line( $line ) { 246 | $this->errors[ $line ] = isset( $this->errors[ $line ] ) ? ++ $this->errors[ $line ] : 1; 247 | } 248 | 249 | /** 250 | * Add 1 to the number of errors for the given line. 251 | * 252 | * @param int $line Line number. 253 | * 254 | * @return void 255 | */ 256 | private function add_warning_for_line( $line ) { 257 | $this->warnings[ $line ] = isset( $this->warnings[ $line ] ) ? ++ $this->warnings[ $line ] : 1; 258 | } 259 | 260 | /** 261 | * Add message for the given line. 262 | * 263 | * @param int $line Line number. 264 | * @param string $message Message. 265 | * 266 | * @return void 267 | */ 268 | private function add_message_for_line( $line, $message ) { 269 | $this->messages[ $line ] = ( ! isset( $this->messages[ $line ] ) || ! is_array( $this->messages[ $line ] ) ) ? [ $message ] : array_merge( $this->messages[ $line ], 270 | [ $message ] ); 271 | } 272 | 273 | /** 274 | * Check whether all expected numbers of errors and warnings are present in the output. 275 | * 276 | * @return void 277 | */ 278 | private function check_missing_expected_values() { 279 | foreach ( $this->expected as $type => $lines ) { 280 | if ( $type === 'messages' ) { 281 | continue; 282 | } 283 | 284 | foreach ( $lines as $line_number => $expected_count_of_type_violations ) { 285 | if ( $expected_count_of_type_violations === 0 ) { 286 | continue; 287 | } 288 | 289 | if ( ! isset( $this->{$type}[ $line_number ] ) ) { 290 | $this->error_warning_message( $expected_count_of_type_violations, $type, 0, $line_number ); 291 | } elseif ( $this->{$type}[ $line_number ] !== $expected_count_of_type_violations ) { 292 | $this->error_warning_message( $expected_count_of_type_violations, $type, 293 | $this->{$type}[ $line_number ], $line_number ); 294 | } 295 | 296 | unset( $this->{$type}[ $line_number ] ); 297 | } 298 | } 299 | } 300 | 301 | /** 302 | * Check whether there are no unexpected numbers of errors and warnings. 303 | * 304 | * @return void 305 | */ 306 | private function check_unexpected_values() { 307 | foreach ( [ 'errors', 'warnings' ] as $type ) { 308 | foreach ( $this->$type as $line_number => $actual_count_of_type_violations ) { 309 | if ( $actual_count_of_type_violations === 0 ) { 310 | continue; 311 | } 312 | 313 | if ( ! isset( $this->expected[ $type ][ $line_number ] ) ) { 314 | $this->error_warning_message( 0, $type, $actual_count_of_type_violations, $line_number ); 315 | } elseif ( $actual_count_of_type_violations !== $this->expected[ $type ][ $line_number ] ) { 316 | $this->error_warning_message( $this->expected[ $type ][ $line_number ], $type, 317 | $actual_count_of_type_violations, $line_number ); 318 | } 319 | } 320 | } 321 | } 322 | 323 | /** 324 | * Check whether all expected messages are present and whether there are no unexpected messages. 325 | * 326 | * @return void 327 | */ 328 | private function check_messages() { 329 | foreach ( $this->expected['messages'] as $line_number => $messages ) { 330 | foreach ( $messages as $message ) { 331 | if ( ! isset( $this->messages[ $line_number ] ) ) { 332 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 333 | printf( 'Expected "%s" but found no message for line %d' . PHP_EOL, $message, $line_number ); 334 | $this->found_issue = true; 335 | } elseif ( ! in_array( $message, $this->messages[ $line_number ], true ) ) { 336 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 337 | printf( 'Expected message "%s" was not found for line %d.' . PHP_EOL, $message, $line_number ); 338 | $this->found_issue = true; 339 | } 340 | } 341 | } 342 | foreach ( $this->messages as $line_number => $messages ) { 343 | foreach ( $messages as $message ) { 344 | if ( isset( $this->expected['messages'][ $line_number ] ) ) { 345 | if ( ! in_array( $message, $this->expected['messages'][ $line_number ], true ) ) { 346 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 347 | printf( 'Unexpected message "%s" was found for line %d.' . PHP_EOL, $message, $line_number ); 348 | $this->found_issue = true; 349 | } 350 | } 351 | } 352 | } 353 | } 354 | 355 | /** 356 | * Print out the message reporting found issues. 357 | * 358 | * @param int $expected Expected number of issues. 359 | * @param string $type The type of the issue. 360 | * @param int $number Real number of issues. 361 | * @param int $line Line number. 362 | * 363 | * @return void 364 | */ 365 | private function error_warning_message( $expected, $type, $number, $line ) { 366 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 367 | printf( 'Expected %d %s, found %d on line %d.' . PHP_EOL, $expected, $type, $number, $line ); 368 | $this->found_issue = true; 369 | } 370 | } 371 | --------------------------------------------------------------------------------