diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 022236e7af..0bd6e41e24 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -2,9 +2,7 @@ name: Style on: push: - branches-ignore: [master] pull_request: - branches: [master] jobs: php: @@ -12,42 +10,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 - - - name: Get changed files - uses: dorny/paths-filter@v2 - id: filter - with: - list-files: shell - base: ${{ github.ref }} - filters: | - addedOrModified: - - added|modified: '**.php' + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 - if: ${{ steps.filter.outputs.addedOrModified == 'true' }} with: php-version: '7.4' - - name: Get Composer cache directory path - id: composer-cache-dir - if: ${{ steps.filter.outputs.addedOrModified == 'true' }} - run: | - echo "::set-output name=path::$(composer config cache-files-dir)" - - - name: Cache Composer dependencies - uses: actions/cache@v2 - if: ${{ steps.filter.outputs.addedOrModified == 'true' }} + - name: PHP-CS-Fixer cache + uses: actions/cache@v4 with: - path: ${{ steps.composer-cache-dir.outputs.path }} - key: composer-${{ hashFiles('composer.lock') }} - restore-keys: composer- - - - name: Install PHP-CS-Fixer - if: ${{ steps.filter.outputs.addedOrModified == 'true' }} - run: composer global require friendsofphp/php-cs-fixer:^3.4 + path: .php-cs-fixer.cache + key: php-cs-fixer-${{ github.sha }} + restore-keys: php-cs-fixer- - name: Run PHP-CS-Fixer - if: ${{ steps.filter.outputs.addedOrModified == 'true' }} - run: composer global exec -- php-cs-fixer fix --config=.php-cs-fixer.dist.php --dry-run --diff --path-mode=intersection ${{ steps.filter.outputs.addedOrModified_files }} + run: vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 0f783c0e38..daba1050f1 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,186 +1,56 @@ notPath('mobile/lib/smarty') - ->notPath('okapi') - ->notPath('src/Libs') - ->notPath('vendor') ->in(__DIR__) - ->name('*.php') + ->path('src') + ->notPath('src/Libs') + ->notPath('src/Utils/Database/Updates/template.php') ->ignoreDotFiles(true) ->ignoreVCS(true); return (new PhpCsFixer\Config()) + ->setParallelConfig(ParallelConfigFactory::detect()) + ->setFinder($finder) + ->setRiskyAllowed(true) ->setRules([ '@PSR12' => true, - - // Alias - 'backtick_to_shell_exec' => true, - 'no_alias_language_construct_call' => true, - 'no_mixed_echo_print' => true, - - // Array Notation - 'array_syntax' => ['syntax' => 'short'], - 'no_multiline_whitespace_around_double_arrow' => true, - 'no_trailing_comma_in_singleline_array' => true, - 'no_whitespace_before_comma_in_array' => true, - 'normalize_index_brace' => true, - 'trailing_comma_in_multiline' => ['elements' => ['arrays']], - 'trim_array_spaces' => true, - 'whitespace_after_comma_in_array' => true, - - // Basic - 'braces' => [ - 'allow_single_line_anonymous_class_with_empty_body' => true, - 'allow_single_line_closure' => true, - ], - - // Casing - 'magic_constant_casing' => true, - 'magic_method_casing' => true, - 'native_function_casing' => true, - 'native_function_type_declaration_casing' => true, - - // Cast Notation - 'cast_spaces' => true, - 'no_short_bool_cast' => true, - 'no_unset_cast' => true, - - // Class Notation - 'class_attributes_separation' => true, - 'ordered_class_elements' => ['order' => [ - 'use_trait', 'constant', 'property', 'construct', 'method', 'magic', - ]], - 'self_static_accessor' => true, - - // Comment - 'multiline_comment_opening_closing' => true, - 'single_line_comment_style' => true, - - // Control Structure - 'include' => true, - 'no_alternative_syntax' => true, - 'no_superfluous_elseif' => true, - 'no_trailing_comma_in_list_call' => true, - 'no_unneeded_control_parentheses' => true, - 'no_unneeded_curly_braces' => true, - 'no_useless_else' => true, - 'simplified_if_return' => true, - 'switch_continue_to_break' => true, - - // Function Notation - 'function_typehint_space' => true, - 'lambda_not_used_import' => true, - 'nullable_type_declaration_for_default_null_value' => [ - 'use_nullable_type_declaration' => false, - ], - - // Import - 'fully_qualified_strict_types' => true, - 'global_namespace_import' => true, - 'no_unused_imports' => true, - 'ordered_imports' => [ - 'imports_order' => ['class', 'function', 'const'], - 'sort_algorithm' => 'alpha', - ], - - // Language Construct - 'combine_consecutive_issets' => true, - 'combine_consecutive_unsets' => true, - 'explicit_indirect_variable' => true, - 'single_space_after_construct' => true, - - // List Notation - 'list_syntax' => ['syntax' => 'short'], - - // Namespace Notation - 'clean_namespace' => true, - 'no_leading_namespace_whitespace' => true, - - // Operator - 'binary_operator_spaces' => ['operators' => ['|' => null]], - 'concat_space' => ['spacing' => 'one'], - 'increment_style' => ['style' => 'post'], - 'not_operator_with_successor_space' => true, - 'object_operator_without_whitespace' => true, - 'operator_linebreak' => true, - 'standardize_increment' => true, - 'standardize_not_equals' => true, - 'ternary_to_null_coalescing' => true, - 'unary_operator_spaces' => true, - - // PHP Tag - // 'echo_tag_syntax' => true, - 'linebreak_after_opening_tag' => true, - - // PHPDoc - 'align_multiline_comment' => true, - 'general_phpdoc_tag_rename' => [ - 'replacements' => ['inheritDocs' => 'inheritDoc'], - ], - // 'no_blank_lines_after_phpdoc' => true, - 'no_empty_phpdoc' => true, - 'no_superfluous_phpdoc_tags' => [ - 'allow_mixed' => true, - 'allow_unused_params' => true, - ], - 'phpdoc_align' => [ - 'tags' => ['param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var', 'method'], - 'align' => 'left', - ], - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_indent' => true, - 'phpdoc_inline_tag_normalizer' => true, - 'phpdoc_line_span' => ['const' => 'single', 'property' => 'single'], - 'phpdoc_no_access' => true, - 'phpdoc_no_alias_tag' => ['replacements' => ['type' => 'var', 'link' => 'see']], - 'phpdoc_no_empty_return' => true, - 'phpdoc_no_package' => true, - 'phpdoc_no_useless_inheritdoc' => true, - 'phpdoc_return_self_reference' => true, - 'phpdoc_scalar' => true, - 'phpdoc_single_line_var_spacing' => true, - // 'phpdoc_summary' => true, - 'phpdoc_tag_casing' => true, - // 'phpdoc_to_comment' => true, - 'phpdoc_trim_consecutive_blank_line_separation' => true, - 'phpdoc_trim' => true, - 'phpdoc_types' => true, - 'phpdoc_types_order' => [ - 'sort_algorithm' => 'none', - 'null_adjustment' => 'always_last', - ], - 'phpdoc_var_annotation_correct_order' => true, - 'phpdoc_var_without_name' => true, - - // Return Notation - 'no_useless_return' => true, - 'simplified_null_return' => true, - - // Semicolon - 'multiline_whitespace_before_semicolons' => true, - 'no_empty_statement' => true, - 'no_singleline_whitespace_before_semicolons' => true, - 'semicolon_after_instruction' => true, - 'space_after_semicolon' => ['remove_in_empty_for_expressions' => true], - - // String Notation - 'explicit_string_variable' => true, - 'heredoc_to_nowdoc' => true, - 'no_binary_string' => true, - 'simple_to_complex_string_variable' => true, - 'single_quote' => true, - 'escape_implicit_backslashes' => ['single_quoted' => false], - - // Whitespace - 'array_indentation' => true, - 'blank_line_before_statement' => ['statements' => [ - 'continue', 'declare', 'do', 'exit', 'for', 'foreach', 'if', 'include', 'include_once', 'require', 'require_once', 'return', 'switch', 'throw', 'try', 'while', 'yield', 'yield_from', - ]], - 'heredoc_indentation' => true, - 'no_extra_blank_lines' => ['tokens' => [ - 'break', 'case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'switch', 'throw', 'use', - ]], - 'no_spaces_around_offset' => true, - ]) - ->setFinder($finder); + '@PSR12:risky' => true, + + // To be enabled later + 'binary_operator_spaces' => false, + 'blank_line_after_namespace' => false, + 'blank_line_after_opening_tag' => false, + 'blank_lines_before_namespace' => false, + 'braces_position' => false, + 'class_definition' => false, + 'constant_case' => false, + 'control_structure_braces' => false, + 'control_structure_continuation_position' => false, + 'elseif' => false, + 'function_declaration' => false, + 'line_ending' => false, + 'lowercase_keywords' => false, + 'method_argument_space' => false, + 'new_with_parentheses' => false, + 'no_blank_lines_after_class_opening' => false, + 'no_break_comment' => false, + 'no_closing_tag' => false, + 'no_leading_import_slash' => false, + 'no_multiple_statements_per_line' => false, + 'no_spaces_after_function_name' => false, + 'no_trailing_whitespace_in_comment' => false, + 'no_whitespace_in_blank_line' => false, + 'return_type_declaration' => false, + 'short_scalar_cast' => false, + 'single_blank_line_at_eof' => false, + 'single_class_element_per_statement' => false, + 'single_line_after_imports' => false, + 'spaces_inside_parentheses' => false, + 'statement_indentation' => false, + 'switch_case_space' => false, + 'ternary_operator_spaces' => false, + 'unary_operator_spaces' => false, + 'visibility_required' => false, + ]); diff --git a/composer.json b/composer.json index c3c7cdd61f..4f6eae7353 100755 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "ezyang/htmlpurifier": "^4.13", "kint-php/kint": "^3.3", "npm-asset/js-cookie": "^2.1.3", - "friendsofphp/php-cs-fixer": "^3.4" + "friendsofphp/php-cs-fixer": "^3.61" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index b403fe353f..2b2159696b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "82763b0d455f0f9f4b03cb9e6d4946c3", + "content-hash": "c988c7b22bbea22154665735a55f4800", "packages": [ { "name": "aferrandini/phpqrcode", @@ -55,6 +55,70 @@ "abandoned": "endroid/qr-code", "time": "2013-07-08T09:39:08+00:00" }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, { "name": "composer/pcre", "version": "3.0.0", @@ -128,16 +192,16 @@ }, { "name": "composer/semver", - "version": "3.3.1", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/5d8e574bb0e69188786b8ef77d43341222a41a71", - "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -187,9 +251,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.1" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -205,7 +269,7 @@ "type": "tidelift" } ], - "time": "2022-03-16T11:22:07+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/xdebug-handler", @@ -274,36 +338,29 @@ "time": "2022-02-25T21:32:43+00:00" }, { - "name": "doctrine/annotations", - "version": "1.13.2", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" + "php": ">=7.0" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -312,66 +369,277 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + }, + "time": "2021-12-25T01:21:49+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.61.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "94a87189f55814e6cabca2d9a33b06de384a2ab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/94a87189f55814e6cabca2d9a33b06de384a2ab8", + "reference": "94a87189f55814e6cabca2d9a33b06de384a2ab8", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.0", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" } ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "description": "A tool to automatically fix PHP code style", "keywords": [ - "annotations", - "docblock", - "parser" + "Static code analysis", + "fixer", + "standards", + "static analysis" ], "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.2" + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.61.1" }, - "time": "2021-08-05T19:00:23+00:00" + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2024-07-31T14:33:15+00:00" }, { - "name": "doctrine/lexer", - "version": "1.2.3", + "name": "kint-php/kint", + "version": "3.3", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "url": "https://github.com/kint-php/kint.git", + "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/kint-php/kint/zipball/335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", + "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": ">=5.3.6" }, "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "friendsofphp/php-cs-fixer": "^2.0", + "phpunit/phpunit": "^4.0", + "seld/phar-utils": "^1.0", + "symfony/finder": "^2.0 || ^3.0 || ^4.0", + "vimeo/psalm": "^3.0" + }, + "suggest": { + "ext-ctype": "Simple data type tests", + "ext-iconv": "Provides fallback detection for ambiguous legacy string encodings such as the Windows and ISO 8859 code pages", + "ext-mbstring": "Provides string encoding detection", + "kint-php/kint-js": "Provides a simplified dump to console.log()", + "kint-php/kint-twig": "Provides d() and s() functions in twig templates", + "symfony/polyfill-ctype": "Replacement for ext-ctype if missing", + "symfony/polyfill-iconv": "Replacement for ext-iconv if missing", + "symfony/polyfill-mbstring": "Replacement for ext-mbstring if missing" }, "type": "library", "autoload": { + "files": [ + "init.php" + ], "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Kint\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -380,158 +648,292 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Jonathan Vollebregt", + "homepage": "https://github.com/jnvsor" }, { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Rokas Šleinius", + "homepage": "https://github.com/raveren" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Contributors", + "homepage": "https://github.com/kint-php/kint/graphs/contributors" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "Kint - debugging tool for PHP developers", + "homepage": "https://kint-php.github.io/kint/", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", + "debug", + "kint", "php" ], "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" + "issues": "https://github.com/kint-php/kint/issues", + "source": "https://github.com/kint-php/kint/tree/master" }, - "funding": [ + "time": "2019-10-17T18:05:24+00:00" + }, + { + "name": "npm-asset/js-cookie", + "version": "2.1.3", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.1.3.tgz" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "time": "2022-02-28T11:07:21+00:00" + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" }, { - "name": "ezyang/htmlpurifier", - "version": "v4.14.0", + "name": "react/cache", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "php": ">=5.2" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { - "files": [ - "library/HTMLPurifier.composer.php" - ], - "psr-0": { - "HTMLPurifier": "library/" - }, - "exclude-from-classmap": [ - "/library/HTMLPurifier/Language/" - ] + "psr-4": { + "React\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1-or-later" + "MIT" ], "authors": [ { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", + "description": "Async, Promise-based cache interface for ReactPHP", "keywords": [ - "html" + "cache", + "caching", + "promise", + "reactphp" ], "support": { - "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" }, - "time": "2021-12-25T01:21:49+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.8.0", + "name": "react/child-process", + "version": "v0.6.5", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" + "url": "https://github.com/reactphp/child-process.git", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", "shasum": "" }, "require": { - "composer/semver": "^3.2", - "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^1.13", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0", - "php-cs-fixer/diff": "^2.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.25", - "symfony/polyfill-php81": "^1.25", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.2" }, "require-dev": { - "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.10", - "php-coveralls/php-coveralls": "^2.5.2", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^6.0", - "symfony/yaml": "^5.4 || ^6.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", + "type": "library", "autoload": { "psr-4": { - "PhpCsFixer\\": "src/" + "React\\ChildProcess\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -540,68 +942,77 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "A tool to automatically fix PHP code style", + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.5" }, "funding": [ { - "url": "https://github.com/keradus", + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", "type": "github" } ], - "time": "2022-03-18T17:20:59+00:00" + "time": "2022-09-16T13:41:56+00:00" }, { - "name": "kint-php/kint", - "version": "3.3", + "name": "react/dns", + "version": "v1.13.0", "source": { "type": "git", - "url": "https://github.com/kint-php/kint.git", - "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b" + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kint-php/kint/zipball/335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", - "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { - "php": ">=5.3.6" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "phpunit/phpunit": "^4.0", - "seld/phar-utils": "^1.0", - "symfony/finder": "^2.0 || ^3.0 || ^4.0", - "vimeo/psalm": "^3.0" - }, - "suggest": { - "ext-ctype": "Simple data type tests", - "ext-iconv": "Provides fallback detection for ambiguous legacy string encodings such as the Windows and ISO 8859 code pages", - "ext-mbstring": "Provides string encoding detection", - "kint-php/kint-js": "Provides a simplified dump to console.log()", - "kint-php/kint-twig": "Provides d() and s() functions in twig templates", - "symfony/polyfill-ctype": "Replacement for ext-ctype if missing", - "symfony/polyfill-iconv": "Replacement for ext-iconv if missing", - "symfony/polyfill-mbstring": "Replacement for ext-mbstring if missing" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { - "files": [ - "init.php" - ], "psr-4": { - "Kint\\": "src/" + "React\\Dns\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -610,121 +1021,145 @@ ], "authors": [ { - "name": "Jonathan Vollebregt", - "homepage": "https://github.com/jnvsor" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Rokas Šleinius", - "homepage": "https://github.com/raveren" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" }, { - "name": "Contributors", - "homepage": "https://github.com/kint-php/kint/graphs/contributors" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Kint - debugging tool for PHP developers", - "homepage": "https://kint-php.github.io/kint/", + "description": "Async DNS resolver for ReactPHP", "keywords": [ - "debug", - "kint", - "php" + "async", + "dns", + "dns-resolver", + "reactphp" ], "support": { - "issues": "https://github.com/kint-php/kint/issues", - "source": "https://github.com/kint-php/kint/tree/master" - }, - "time": "2019-10-17T18:05:24+00:00" - }, - { - "name": "npm-asset/js-cookie", - "version": "2.1.3", - "dist": { - "type": "tar", - "url": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.1.3.tgz" + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" }, - "type": "npm-asset", - "license": [ - "MIT" - ] + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" }, { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", + "name": "react/event-loop", + "version": "v1.5.0", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0 || ^8.0" + "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\EventLoop\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ - "diff" + "asynchronous", + "event-loop" ], "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, - "time": "2020-10-14T08:32:19+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" }, { - "name": "psr/cache", - "version": "1.0.1", + "name": "react/promise", + "version": "v3.2.0", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, + "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Psr\\Cache\\": "src/" + "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -733,42 +1168,75 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common interface for caching libraries", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "cache", - "psr", - "psr-6" + "promise", + "promises" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, - "time": "2016-08-06T20:24:11+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" }, { - "name": "psr/container", - "version": "1.1.2", + "name": "react/socket", + "version": "v1.16.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { - "php": ">=7.4.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -777,51 +1245,73 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "Connection", + "Socket", + "async", + "reactphp", + "stream" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, - "time": "2021-11-05T16:50:12+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "react/stream", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "php": ">=7.2.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\EventDispatcher\\": "src/" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -830,71 +1320,114 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Standard interfaces for event handling.", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ - "events", - "psr", - "psr-14" + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, - "time": "2019-01-08T18:20:26+00:00" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" }, { - "name": "psr/log", - "version": "1.1.4", + "name": "sebastian/diff", + "version": "4.0.6", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "log", - "psr", - "psr-3" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, - "time": "2021-05-03T11:20:27+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" }, { "name": "symfony/console", @@ -1671,16 +2204,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -1694,9 +2227,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1734,7 +2264,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -1750,7 +2280,7 @@ "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php73", @@ -1833,16 +2363,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.25.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -1850,9 +2380,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1896,7 +2423,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -1912,20 +2439,20 @@ "type": "tidelift" } ], - "time": "2022-03-04T08:16:47+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.25.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", "shasum": "" }, "require": { @@ -1933,9 +2460,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1975,7 +2499,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" }, "funding": [ { @@ -1991,7 +2515,7 @@ "type": "tidelift" } ], - "time": "2021-09-13T13:58:11+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/process", @@ -2294,7 +2818,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.4", + "php": ">=7.4", "ext-json": "*", "ext-gettext": "*", "ext-gd": "*", @@ -2308,5 +2832,5 @@ "ext-openssl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/vendor/autoload.php b/vendor/autoload.php index 174cce4d54..97594f6ec6 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -2,6 +2,24 @@ // autoload.php @generated by Composer +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit2532e47646cbd0ac2fbb391e55b03321::getLoader(); diff --git a/vendor/bin/php-cs-fixer b/vendor/bin/php-cs-fixer old mode 100644 new mode 100755 index 897061a4fc..d40411aa93 --- a/vendor/bin/php-cs-fixer +++ b/vendor/bin/php-cs-fixer @@ -108,10 +108,12 @@ if (PHP_VERSION_ID < 80000) { } } - if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer'); - exit(0); + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer'); } } -include __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer'; +return include __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer'; diff --git a/vendor/bin/php-cs-fixer.bat b/vendor/bin/php-cs-fixer.bat deleted file mode 100644 index 83db5d5987..0000000000 --- a/vendor/bin/php-cs-fixer.bat +++ /dev/null @@ -1,5 +0,0 @@ -@ECHO OFF -setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/php-cs-fixer -SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 -php "%BIN_TARGET%" %* diff --git a/vendor/clue/ndjson-react/.github/FUNDING.yml b/vendor/clue/ndjson-react/.github/FUNDING.yml new file mode 100644 index 0000000000..9c09fb8784 --- /dev/null +++ b/vendor/clue/ndjson-react/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: clue +custom: https://clue.engineering/support diff --git a/vendor/clue/ndjson-react/CHANGELOG.md b/vendor/clue/ndjson-react/CHANGELOG.md new file mode 100644 index 0000000000..bc4faf6939 --- /dev/null +++ b/vendor/clue/ndjson-react/CHANGELOG.md @@ -0,0 +1,75 @@ +# Changelog + +## 1.3.0 (2022-12-23) + +* Feature: Add support for PHP 8.1 and PHP 8.2. + (#31 by @clue and #30 by @SimonFring) + +* Feature: Check type of incoming `data` before trying to decode NDJSON. + (#29 by @SimonFrings) + +* Improve documentation and examples and update to new [default loop](https://reactphp.org/event-loop/#loop). + (#26 by @clue, #27 by @SimonFrings and #25 by @PaulRotmann) + +* Improve test suite, report failed assertions and ensure 100% code coverage. + (#32 and #33 by @clue and #28 by @SimonFrings) + +## 1.2.0 (2020-12-09) + +* Improve test suite and add `.gitattributes` to exclude dev files from exports. + Add PHP 8 support, update to PHPUnit 9 and simplify test setup. + (#18 by @clue and #19, #22 and #23 by @SimonFrings) + +## 1.1.0 (2020-02-04) + +* Feature: Improve error reporting and add parsing error message to Exception and + ignore `JSON_THROW_ON_ERROR` option (available as of PHP 7.3). + (#14 by @clue) + +* Feature: Add bechmarking script and import all global function references. + (#16 by @clue) + +* Improve documentation and add NDJSON format description and + add support / sponsorship info. + (#12 and #17 by @clue) + +* Improve test suite to run tests on PHP 7.4 and simplify test matrix and + apply minor code style adjustments to make phpstan happy. + (#13 and #15 by @clue) + +## 1.0.0 (2018-05-17) + +* First stable release, now following SemVer + +* Improve documentation and usage examples + +> Contains no other changes, so it's actually fully compatible with the v0.1.2 release. + +## 0.1.2 (2018-05-11) + +* Feature: Limit buffer size to 64 KiB by default. + (#10 by @clue) + +* Feature: Forward compatiblity with EventLoop v0.5 and upcoming v1.0. + (#8 by @clue) + +* Fix: Return bool `false` if encoding fails due to invalid value to pause source. + (#9 by @clue) + +* Improve test suite by supporting PHPUnit v6 and test against legacy PHP 5.3 through PHP 7.2. + (#7 by @clue) + +* Update project homepage. + (#11 by @clue) + +## 0.1.1 (2017-05-22) + +* Feature: Forward compatibility with Stream v0.7, v0.6, v0.5 and upcoming v1.0 (while keeping BC) + (#6 by @thklein) + +* Improved test suite by adding PHPUnit to `require-dev` + (#5 by @thklein) + +## 0.1.0 (2016-11-24) + +* First tagged release diff --git a/vendor/doctrine/lexer/LICENSE b/vendor/clue/ndjson-react/LICENSE similarity index 58% rename from vendor/doctrine/lexer/LICENSE rename to vendor/clue/ndjson-react/LICENSE index e8fdec4afb..7baae8e9a5 100644 --- a/vendor/doctrine/lexer/LICENSE +++ b/vendor/clue/ndjson-react/LICENSE @@ -1,11 +1,13 @@ -Copyright (c) 2006-2018 Doctrine Project +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +Copyright (c) 2016 Christian Lück + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -15,5 +17,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/clue/ndjson-react/README.md b/vendor/clue/ndjson-react/README.md new file mode 100644 index 0000000000..0ca4eabe1e --- /dev/null +++ b/vendor/clue/ndjson-react/README.md @@ -0,0 +1,365 @@ +# clue/reactphp-ndjson + +[![CI status](https://github.com/clue/reactphp-ndjson/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-ndjson/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/clue/ndjson-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/ndjson-react) +[![code coverage](https://img.shields.io/badge/code%20coverage-100%25-success)](#tests) + +Streaming newline-delimited JSON ([NDJSON](http://ndjson.org/)) parser and encoder for [ReactPHP](https://reactphp.org/). + +[NDJSON](http://ndjson.org/) can be used to store multiple JSON records in a +file to store any kind of (uniform) structured data, such as a list of user +objects or log entries. It uses a simple newline character between each +individual record and as such can be both used for efficient persistence and +simple append-style operations. This also allows it to be used in a streaming +context, such as a simple inter-process communication (IPC) protocol or for a +remote procedure call (RPC) mechanism. This library provides a simple +streaming API to process very large NDJSON files with thousands or even millions +of rows efficiently without having to load the whole file into memory at once. + +* **Standard interfaces** - + Allows easy integration with existing higher-level components by implementing + ReactPHP's standard streaming interfaces. +* **Lightweight, SOLID design** - + Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough) + and does not get in your way. + Builds on top of well-tested components and well-established concepts instead of reinventing the wheel. +* **Good test coverage** - + Comes with an [automated tests suite](#tests) and is regularly tested in the *real world*. + +**Table of contents** + +* [Support us](#support-us) +* [NDJSON format](#ndjson-format) +* [Usage](#usage) + * [Decoder](#decoder) + * [Encoder](#encoder) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [More](#more) + +## Support us + +We invest a lot of time developing, maintaining, and updating our awesome +open-source projects. You can help us sustain this high-quality of our work by +[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get +numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue) +for details. + +Let's take these projects to the next level together! 🚀 + +## NDJSON format + +NDJSON ("Newline-Delimited JSON" or sometimes referred to as "JSON lines") is a +very simple text-based format for storing a large number of records, such as a +list of user records or log entries. + +```JSON +{"name":"Alice","age":30,"comment":"Yes, I like cheese"} +{"name":"Bob","age":50,"comment":"Hello\nWorld!"} +``` + +If you understand JSON and you're now looking at this newline-delimited JSON for +the first time, you should already know everything you need to know to +understand NDJSON: As the name implies, this format essentially consists of +individual lines where each individual line is any valid JSON text and each line +is delimited with a newline character. + +This example uses a list of user objects where each user has some arbitrary +properties. This can easily be adjusted for many different use cases, such as +storing for example products instead of users, assigning additional properties +or having a significantly larger number of records. You can edit NDJSON files in +any text editor or use them in a streaming context where individual records +should be processed. Unlike normal JSON files, adding a new log entry to this +NDJSON file does not require modification of this file's structure (note there's +no "outer array" to be modified). This makes it a perfect fit for a streaming +context, for line-oriented CLI tools (such as `grep` and others) or for a logging +context where you want to append records at a later time. Additionally, this +also allows it to be used in a streaming context, such as a simple inter-process +communication (IPC) protocol or for a remote procedure call (RPC) mechanism. + +The newline character at the end of each line allows for some really simple +*framing* (detecting individual records). While each individual line is valid +JSON, the complete file as a whole is technically no longer valid JSON, because +it contains multiple JSON texts. This implies that for example calling PHP's +`json_decode()` on this complete input would fail because it would try to parse +multiple records at once. Likewise, using "pretty printing" JSON +(`JSON_PRETTY_PRINT`) is not allowed because each JSON text is limited to exactly +one line. On the other hand, values containing newline characters (such as the +`comment` property in the above example) do not cause issues because each newline +within a JSON string will be represented by a `\n` instead. + +One common alternative to NDJSON would be Comma-Separated Values (CSV). +If you want to process CSV files, you may want to take a look at the related +project [clue/reactphp-csv](https://github.com/clue/reactphp-csv) instead: + +``` +name,age,comment +Alice,30,"Yes, I like cheese" +Bob,50,"Hello +World!" +``` + +CSV may look slightly simpler, but this simplicity comes at a price. CSV is +limited to untyped, two-dimensional data, so there's no standard way of storing +any nested structures or to differentiate a boolean value from a string or +integer. Field names are sometimes used, sometimes they're not +(application-dependant). Inconsistent handling for fields that contain +separators such as `,` or spaces or line breaks (see the `comment` field above) +introduce additional complexity and its text encoding is usually undefined, +Unicode (or UTF-8) is unlikely to be supported and CSV files often use ISO +8859-1 encoding or some variant (again application-dependant). + +While NDJSON helps avoiding many of CSV's shortcomings, it is still a +(relatively) young format while CSV files have been used in production systems +for decades. This means that if you want to interface with an existing system, +you may have to rely on the format that's already supported. If you're building +a new system, using NDJSON is an excellent choice as it provides a flexible way +to process individual records using a common text-based format that can include +any kind of structured data. + +## Usage + +### Decoder + +The `Decoder` (parser) class can be used to make sure you only get back +complete, valid JSON elements when reading from a stream. +It wraps a given +[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) +and exposes its data through the same interface, but emits the JSON elements +as parsed values instead of just chunks of strings: + +``` +{"name":"test","active":true} +{"name":"hello w\u00f6rld","active":true} +``` + +```php +$stdin = new React\Stream\ReadableResourceStream(STDIN); + +$ndjson = new Clue\React\NDJson\Decoder($stdin); + +$ndjson->on('data', function ($data) { + // $data is a parsed element from the JSON stream + // line 1: $data = (object)array('name' => 'test', 'active' => true); + // line 2: $data = (object)array('name' => 'hello wörld', 'active' => true); + var_dump($data); +}); +``` + +ReactPHP's streams emit chunks of data strings and make no assumption about their lengths. +These chunks do not necessarily represent complete JSON elements, as an +element may be broken up into multiple chunks. +This class reassembles these elements by buffering incomplete ones. + +The `Decoder` supports the same optional parameters as the underlying +[`json_decode()`](https://www.php.net/manual/en/function.json-decode.php) function. +This means that, by default, JSON objects will be emitted as a `stdClass`. +This behavior can be controlled through the optional constructor parameters: + +```php +$ndjson = new Clue\React\NDJson\Decoder($stdin, true); + +$ndjson->on('data', function ($data) { + // JSON objects will be emitted as assoc arrays now +}); +``` + +Additionally, the `Decoder` limits the maximum buffer size (maximum line +length) to avoid buffer overflows due to malformed user input. Usually, there +should be no need to change this value, unless you know you're dealing with some +unreasonably long lines. It accepts an additional argument if you want to change +this from the default of 64 KiB: + +```php +$ndjson = new Clue\React\NDJson\Decoder($stdin, false, 512, 0, 64 * 1024); +``` + +If the underlying stream emits an `error` event or the plain stream contains +any data that does not represent a valid NDJson stream, +it will emit an `error` event and then `close` the input stream: + +```php +$ndjson->on('error', function (Exception $error) { + // an error occured, stream will close next +}); +``` + +If the underlying stream emits an `end` event, it will flush any incomplete +data from the buffer, thus either possibly emitting a final `data` event +followed by an `end` event on success or an `error` event for +incomplete/invalid JSON data as above: + +```php +$ndjson->on('end', function () { + // stream successfully ended, stream will close next +}); +``` + +If either the underlying stream or the `Decoder` is closed, it will forward +the `close` event: + +```php +$ndjson->on('close', function () { + // stream closed + // possibly after an "end" event or due to an "error" event +}); +``` + +The `close(): void` method can be used to explicitly close the `Decoder` and +its underlying stream: + +```php +$ndjson->close(); +``` + +The `pipe(WritableStreamInterface $dest, array $options = array(): WritableStreamInterface` +method can be used to forward all data to the given destination stream. +Please note that the `Decoder` emits decoded/parsed data events, while many +(most?) writable streams expect only data chunks: + +```php +$ndjson->pipe($logger); +``` + +For more details, see ReactPHP's +[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface). + +### Encoder + +The `Encoder` (serializer) class can be used to make sure anything you write to +a stream ends up as valid JSON elements in the resulting NDJSON stream. +It wraps a given +[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface) +and accepts its data through the same interface, but handles any data as complete +JSON elements instead of just chunks of strings: + +```php +$stdout = new React\Stream\WritableResourceStream(STDOUT); + +$ndjson = new Clue\React\NDJson\Encoder($stdout); + +$ndjson->write(array('name' => 'test', 'active' => true)); +$ndjson->write(array('name' => 'hello wörld', 'active' => true)); +``` +``` +{"name":"test","active":true} +{"name":"hello w\u00f6rld","active":true} +``` + +The `Encoder` supports the same parameters as the underlying +[`json_encode()`](https://www.php.net/manual/en/function.json-encode.php) function. +This means that, by default, Unicode characters will be escaped in the output. +This behavior can be controlled through the optional constructor parameters: + +```php +$ndjson = new Clue\React\NDJson\Encoder($stdout, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + +$ndjson->write('hello wörld'); +``` +``` +"hello wörld" +``` + +Note that trying to pass the `JSON_PRETTY_PRINT` option will yield an +`InvalidArgumentException` because it is not compatible with NDJSON. + +If the underlying stream emits an `error` event or the given data contains +any data that can not be represented as a valid NDJSON stream, +it will emit an `error` event and then `close` the input stream: + +```php +$ndjson->on('error', function (Exception $error) { + // an error occured, stream will close next +}); +``` + +If either the underlying stream or the `Encoder` is closed, it will forward +the `close` event: + +```php +$ndjson->on('close', function () { + // stream closed + // possibly after an "end" event or due to an "error" event +}); +``` + +The `end(mixed $data = null): void` method can be used to optionally emit +any final data and then soft-close the `Encoder` and its underlying stream: + +```php +$ndjson->end(); +``` + +The `close(): void` method can be used to explicitly close the `Encoder` and +its underlying stream: + +```php +$ndjson->close(); +``` + +For more details, see ReactPHP's +[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface). + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require clue/ndjson-react:^1.3 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and +HHVM. +It's *highly recommended to use the latest supported PHP version* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +## License + +This project is released under the permissive [MIT license](LICENSE). + +> Did you know that I offer custom development services and issuing invoices for + sponsorships of releases and for contributions? Contact me (@clue) for details. + +## More + +* If you want to learn more about processing streams of data, refer to the documentation of + the underlying [react/stream](https://github.com/reactphp/stream) component. + +* If you want to process compressed NDJSON files (`.ndjson.gz` file extension), + you may want to use [clue/reactphp-zlib](https://github.com/clue/reactphp-zlib) + on the compressed input stream before passing the decompressed stream to the NDJSON decoder. + +* If you want to create compressed NDJSON files (`.ndjson.gz` file extension), + you may want to use [clue/reactphp-zlib](https://github.com/clue/reactphp-zlib) + on the resulting NDJSON encoder output stream before passing the compressed + stream to the file output stream. + +* If you want to concurrently process the records from your NDJSON stream, + you may want to use [clue/reactphp-flux](https://github.com/clue/reactphp-flux) + to concurrently process many (but not too many) records at once. + +* If you want to process structured data in the more common text-based format, + you may want to use [clue/reactphp-csv](https://github.com/clue/reactphp-csv) + to process Comma-Separated-Values (CSV) files (`.csv` file extension). diff --git a/vendor/clue/ndjson-react/composer.json b/vendor/clue/ndjson-react/composer.json new file mode 100644 index 0000000000..01a4b8594f --- /dev/null +++ b/vendor/clue/ndjson-react/composer.json @@ -0,0 +1,31 @@ +{ + "name": "clue/ndjson-react", + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "keywords": ["NDJSON", "newline", "JSON", "jsonlines", "streaming", "ReactPHP"], + "homepage": "https://github.com/clue/reactphp-ndjson", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Clue\\Tests\\React\\NDJson\\": "tests/" + } + } +} diff --git a/vendor/clue/ndjson-react/src/Decoder.php b/vendor/clue/ndjson-react/src/Decoder.php new file mode 100644 index 0000000000..876ff2b11e --- /dev/null +++ b/vendor/clue/ndjson-react/src/Decoder.php @@ -0,0 +1,166 @@ +input = $input; + + if (!$input->isReadable()) { + $this->close(); + return; + } + + $this->assoc = $assoc; + $this->depth = $depth; + $this->options = $options; + $this->maxlength = $maxlength; + + $this->input->on('data', array($this, 'handleData')); + $this->input->on('end', array($this, 'handleEnd')); + $this->input->on('error', array($this, 'handleError')); + $this->input->on('close', array($this, 'close')); + } + + public function isReadable() + { + return !$this->closed; + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->closed = true; + $this->buffer = ''; + + $this->input->close(); + + $this->emit('close'); + $this->removeAllListeners(); + } + + public function pause() + { + $this->input->pause(); + } + + public function resume() + { + $this->input->resume(); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + Util::pipe($this, $dest, $options); + + return $dest; + } + + /** @internal */ + public function handleData($data) + { + if (!\is_string($data)) { + $this->handleError(new \UnexpectedValueException('Expected stream to emit string, but got ' . \gettype($data))); + return; + } + + $this->buffer .= $data; + + // keep parsing while a newline has been found + while (($newline = \strpos($this->buffer, "\n")) !== false && $newline <= $this->maxlength) { + // read data up until newline and remove from buffer + $data = (string)\substr($this->buffer, 0, $newline); + $this->buffer = (string)\substr($this->buffer, $newline + 1); + + // decode data with options given in ctor + // @codeCoverageIgnoreStart + if ($this->options === 0) { + $data = \json_decode($data, $this->assoc, $this->depth); + } else { + assert(\PHP_VERSION_ID >= 50400); + $data = \json_decode($data, $this->assoc, $this->depth, $this->options); + } + // @codeCoverageIgnoreEnd + + // abort stream if decoding failed + if ($data === null && \json_last_error() !== \JSON_ERROR_NONE) { + // @codeCoverageIgnoreStart + if (\PHP_VERSION_ID > 50500) { + $errstr = \json_last_error_msg(); + } elseif (\json_last_error() === \JSON_ERROR_SYNTAX) { + $errstr = 'Syntax error'; + } else { + $errstr = 'Unknown error'; + } + // @codeCoverageIgnoreEnd + return $this->handleError(new \RuntimeException('Unable to decode JSON: ' . $errstr, \json_last_error())); + } + + $this->emit('data', array($data)); + } + + if (isset($this->buffer[$this->maxlength])) { + $this->handleError(new \OverflowException('Buffer size exceeded')); + } + } + + /** @internal */ + public function handleEnd() + { + if ($this->buffer !== '') { + $this->handleData("\n"); + } + + if (!$this->closed) { + $this->emit('end'); + $this->close(); + } + } + + /** @internal */ + public function handleError(\Exception $error) + { + $this->emit('error', array($error)); + $this->close(); + } +} diff --git a/vendor/clue/ndjson-react/src/Encoder.php b/vendor/clue/ndjson-react/src/Encoder.php new file mode 100644 index 0000000000..30d5c311dd --- /dev/null +++ b/vendor/clue/ndjson-react/src/Encoder.php @@ -0,0 +1,144 @@ +output = $output; + + if (!$output->isWritable()) { + $this->close(); + return; + } + + $this->options = $options; + $this->depth = $depth; + + $this->output->on('drain', array($this, 'handleDrain')); + $this->output->on('error', array($this, 'handleError')); + $this->output->on('close', array($this, 'close')); + } + + public function write($data) + { + if ($this->closed) { + return false; + } + + // we have to handle PHP warnings for legacy PHP < 5.5 + // certain values (such as INF etc.) emit a warning, but still encode successfully + // @codeCoverageIgnoreStart + if (\PHP_VERSION_ID < 50500) { + $errstr = null; + \set_error_handler(function ($_, $error) use (&$errstr) { + $errstr = $error; + }); + + // encode data with options given in ctor (depth not supported) + $data = \json_encode($data, $this->options); + + // always check error code and match missing error messages + \restore_error_handler(); + $errno = \json_last_error(); + if (\defined('JSON_ERROR_UTF8') && $errno === \JSON_ERROR_UTF8) { + // const JSON_ERROR_UTF8 added in PHP 5.3.3, but no error message assigned in legacy PHP < 5.5 + // this overrides PHP 5.3.14 only: https://3v4l.org/IGP8Z#v5314 + $errstr = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + } elseif ($errno !== \JSON_ERROR_NONE && $errstr === null) { + // error number present, but no error message applicable + $errstr = 'Unknown error'; + } + + // abort stream if encoding fails + if ($errno !== \JSON_ERROR_NONE || $errstr !== null) { + $this->handleError(new \RuntimeException('Unable to encode JSON: ' . $errstr, $errno)); + return false; + } + } else { + // encode data with options given in ctor + $data = \json_encode($data, $this->options, $this->depth); + + // abort stream if encoding fails + if ($data === false && \json_last_error() !== \JSON_ERROR_NONE) { + $this->handleError(new \RuntimeException('Unable to encode JSON: ' . \json_last_error_msg(), \json_last_error())); + return false; + } + } + // @codeCoverageIgnoreEnd + + return $this->output->write($data . "\n"); + } + + public function end($data = null) + { + if ($data !== null) { + $this->write($data); + } + + $this->output->end(); + } + + public function isWritable() + { + return !$this->closed; + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->closed = true; + $this->output->close(); + + $this->emit('close'); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleDrain() + { + $this->emit('drain'); + } + + /** @internal */ + public function handleError(\Exception $error) + { + $this->emit('error', array($error)); + $this->close(); + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index afef3fa2ad..7824d8f7ea 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -42,35 +42,37 @@ */ class ClassLoader { - /** @var ?string */ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ private $vendorDir; // PSR-4 /** - * @var array[] - * @psalm-var array> + * @var array> */ private $prefixLengthsPsr4 = array(); /** - * @var array[] - * @psalm-var array> + * @var array> */ private $prefixDirsPsr4 = array(); /** - * @var array[] - * @psalm-var array + * @var list */ private $fallbackDirsPsr4 = array(); // PSR-0 /** - * @var array[] - * @psalm-var array> + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> */ private $prefixesPsr0 = array(); /** - * @var array[] - * @psalm-var array + * @var list */ private $fallbackDirsPsr0 = array(); @@ -78,8 +80,7 @@ class ClassLoader private $useIncludePath = false; /** - * @var string[] - * @psalm-var array + * @var array */ private $classMap = array(); @@ -87,29 +88,29 @@ class ClassLoader private $classMapAuthoritative = false; /** - * @var bool[] - * @psalm-var array + * @var array */ private $missingClasses = array(); - /** @var ?string */ + /** @var string|null */ private $apcuPrefix; /** - * @var self[] + * @var array */ private static $registeredLoaders = array(); /** - * @param ?string $vendorDir + * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); } /** - * @return string[] + * @return array> */ public function getPrefixes() { @@ -121,8 +122,7 @@ public function getPrefixes() } /** - * @return array[] - * @psalm-return array> + * @return array> */ public function getPrefixesPsr4() { @@ -130,8 +130,7 @@ public function getPrefixesPsr4() } /** - * @return array[] - * @psalm-return array + * @return list */ public function getFallbackDirs() { @@ -139,8 +138,7 @@ public function getFallbackDirs() } /** - * @return array[] - * @psalm-return array + * @return list */ public function getFallbackDirsPsr4() { @@ -148,8 +146,7 @@ public function getFallbackDirsPsr4() } /** - * @return string[] Array of classname => path - * @psalm-return array + * @return array Array of classname => path */ public function getClassMap() { @@ -157,8 +154,7 @@ public function getClassMap() } /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap + * @param array $classMap Class to filename map * * @return void */ @@ -175,24 +171,25 @@ public function addClassMap(array $classMap) * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, - (array) $paths + $paths ); } @@ -201,19 +198,19 @@ public function add($prefix, $paths, $prepend = false) $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = (array) $paths; + $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], - (array) $paths + $paths ); } } @@ -222,9 +219,9 @@ public function add($prefix, $paths, $prepend = false) * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * @@ -232,17 +229,18 @@ public function add($prefix, $paths, $prepend = false) */ public function addPsr4($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, - (array) $paths + $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { @@ -252,18 +250,18 @@ public function addPsr4($prefix, $paths, $prepend = false) throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; + $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], - (array) $paths + $paths ); } } @@ -272,8 +270,8 @@ public function addPsr4($prefix, $paths, $prepend = false) * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories * * @return void */ @@ -290,8 +288,8 @@ public function set($prefix, $paths) * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * @@ -425,7 +423,8 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } @@ -476,9 +475,9 @@ public function findFile($class) } /** - * Returns the currently registered loaders indexed by their corresponding vendor directories. + * Returns the currently registered loaders keyed by their corresponding vendor directories. * - * @return self[] + * @return array */ public static function getRegisteredLoaders() { @@ -555,18 +554,26 @@ private function findFileWithExtension($class, $ext) return false; } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private - */ -function includeFile($file) -{ - include $file; + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 41bc143c11..51e734a774 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -28,7 +28,7 @@ class InstalledVersions { /** * @var mixed[]|null - * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; @@ -39,7 +39,7 @@ class InstalledVersions /** * @var array[] - * @psalm-var array}> + * @psalm-var array}> */ private static $installedByVendor = array(); @@ -98,7 +98,7 @@ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } @@ -119,7 +119,7 @@ public static function isInstalled($packageName, $includeDevRequirements = true) */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { - $constraint = $parser->parseConstraints($constraint); + $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); @@ -243,7 +243,7 @@ public static function getInstallPath($packageName) /** * @return array - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { @@ -257,7 +257,7 @@ public static function getRootPackage() * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @@ -280,7 +280,7 @@ public static function getRawData() * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ public static function getAllRawData() { @@ -303,7 +303,7 @@ public static function getAllRawData() * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { @@ -313,7 +313,7 @@ public static function reload($data) /** * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ private static function getInstalled() { @@ -328,7 +328,9 @@ private static function getInstalled() if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $installed[count($installed) - 1]; } @@ -340,12 +342,17 @@ private static function getInstalled() // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = require __DIR__ . '/installed.php'; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; } else { self::$installed = array(); } } - $installed[] = self::$installed; + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } return $installed; } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index eab5cf60a4..6f97ca1317 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -7,6 +7,7 @@ return array( 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', @@ -14,24 +15,24 @@ 'OpenChecker\\OpenCheckerSetup' => $baseDir . '/modules/openchecker/openchecker_classes.php', 'OpenChecker\\Pagination' => $baseDir . '/modules/openchecker/openchecker_classes.php', 'OpenChecker\\convertLongLat' => $baseDir . '/modules/openchecker/openchecker_classes.php', - 'PhpCsFixer\\Diff\\Chunk' => $vendorDir . '/php-cs-fixer/diff/src/Chunk.php', - 'PhpCsFixer\\Diff\\ConfigurationException' => $vendorDir . '/php-cs-fixer/diff/src/Exception/ConfigurationException.php', - 'PhpCsFixer\\Diff\\Diff' => $vendorDir . '/php-cs-fixer/diff/src/Diff.php', - 'PhpCsFixer\\Diff\\Differ' => $vendorDir . '/php-cs-fixer/diff/src/Differ.php', - 'PhpCsFixer\\Diff\\Exception' => $vendorDir . '/php-cs-fixer/diff/src/Exception/Exception.php', - 'PhpCsFixer\\Diff\\InvalidArgumentException' => $vendorDir . '/php-cs-fixer/diff/src/Exception/InvalidArgumentException.php', - 'PhpCsFixer\\Diff\\Line' => $vendorDir . '/php-cs-fixer/diff/src/Line.php', - 'PhpCsFixer\\Diff\\LongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/LongestCommonSubsequenceCalculator.php', - 'PhpCsFixer\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', - 'PhpCsFixer\\Diff\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/Output/AbstractChunkOutputBuilder.php', - 'PhpCsFixer\\Diff\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/Output/DiffOnlyOutputBuilder.php', - 'PhpCsFixer\\Diff\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/php-cs-fixer/diff/src/Output/DiffOutputBuilderInterface.php', - 'PhpCsFixer\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', - 'PhpCsFixer\\Diff\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/php-cs-fixer/diff/src/Output/UnifiedDiffOutputBuilder.php', - 'PhpCsFixer\\Diff\\Parser' => $vendorDir . '/php-cs-fixer/diff/src/Parser.php', - 'PhpCsFixer\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/php-cs-fixer/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php', + 'SebastianBergmann\\Diff\\ConfigurationException' => $vendorDir . '/sebastian/diff/src/Exception/ConfigurationException.php', + 'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php', + 'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php', + 'SebastianBergmann\\Diff\\Exception' => $vendorDir . '/sebastian/diff/src/Exception/Exception.php', + 'SebastianBergmann\\Diff\\InvalidArgumentException' => $vendorDir . '/sebastian/diff/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php', + 'SebastianBergmann\\Diff\\LongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/LongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\Output\\AbstractChunkOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOutputBuilderInterface' => $vendorDir . '/sebastian/diff/src/Output/DiffOutputBuilderInterface.php', + 'SebastianBergmann\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\UnifiedDiffOutputBuilder' => $vendorDir . '/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php', + 'SebastianBergmann\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => $vendorDir . '/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php index 90e070f69e..b6d120b292 100644 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -8,6 +8,7 @@ return array( 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 30fabefed8..3f412ae2be 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -25,15 +25,22 @@ 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'React\\Stream\\' => array($vendorDir . '/react/stream/src'), + 'React\\Socket\\' => array($vendorDir . '/react/socket/src'), + 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), + 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'), + 'React\\Dns\\' => array($vendorDir . '/react/dns/src'), + 'React\\ChildProcess\\' => array($vendorDir . '/react/child-process/src'), + 'React\\Cache\\' => array($vendorDir . '/react/cache/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), - 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpCsFixer\\' => array($vendorDir . '/friendsofphp/php-cs-fixer/src'), 'Kint\\' => array($vendorDir . '/kint-php/kint/src'), - 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), - 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), + 'Fidry\\CpuCoreCounter\\' => array($vendorDir . '/fidry/cpu-core-counter/src'), + 'Evenement\\' => array($vendorDir . '/evenement/evenement/src'), 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), + 'Clue\\React\\NDJson\\' => array($vendorDir . '/clue/ndjson-react/src'), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 523abcad7e..3a6aa88044 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -29,29 +29,22 @@ public static function getLoader() spl_autoload_unregister(array('ComposerAutoloaderInit2532e47646cbd0ac2fbb391e55b03321', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; - \Composer\Autoload\ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321::getInitializer($loader)(); + call_user_func(\Composer\Autoload\ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321::getInitializer($loader)); $loader->register(true); - $includeFiles = \Composer\Autoload\ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321::$files; - foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire2532e47646cbd0ac2fbb391e55b03321($fileIdentifier, $file); + $filesToLoad = \Composer\Autoload\ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); } return $loader; } } - -/** - * @param string $fileIdentifier - * @param string $file - * @return void - */ -function composerRequire2532e47646cbd0ac2fbb391e55b03321($fileIdentifier, $file) -{ - if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - - require $file; - } -} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 9d486c5e9b..73346cd38a 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -9,6 +9,7 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 public static $files = array ( 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', @@ -49,28 +50,41 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 'Symfony\\Component\\EventDispatcher\\' => 34, 'Symfony\\Component\\Console\\' => 26, ), + 'R' => + array ( + 'React\\Stream\\' => 13, + 'React\\Socket\\' => 13, + 'React\\Promise\\' => 14, + 'React\\EventLoop\\' => 16, + 'React\\Dns\\' => 10, + 'React\\ChildProcess\\' => 19, + 'React\\Cache\\' => 12, + ), 'P' => array ( 'Psr\\Log\\' => 8, 'Psr\\EventDispatcher\\' => 20, 'Psr\\Container\\' => 14, - 'Psr\\Cache\\' => 10, 'PhpCsFixer\\' => 11, ), 'K' => array ( 'Kint\\' => 5, ), - 'D' => + 'F' => + array ( + 'Fidry\\CpuCoreCounter\\' => 21, + ), + 'E' => array ( - 'Doctrine\\Common\\Lexer\\' => 22, - 'Doctrine\\Common\\Annotations\\' => 28, + 'Evenement\\' => 10, ), 'C' => array ( 'Composer\\XdebugHandler\\' => 23, 'Composer\\Semver\\' => 16, 'Composer\\Pcre\\' => 14, + 'Clue\\React\\NDJson\\' => 18, ), ); @@ -151,6 +165,34 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 array ( 0 => __DIR__ . '/..' . '/symfony/console', ), + 'React\\Stream\\' => + array ( + 0 => __DIR__ . '/..' . '/react/stream/src', + ), + 'React\\Socket\\' => + array ( + 0 => __DIR__ . '/..' . '/react/socket/src', + ), + 'React\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise/src', + ), + 'React\\EventLoop\\' => + array ( + 0 => __DIR__ . '/..' . '/react/event-loop/src', + ), + 'React\\Dns\\' => + array ( + 0 => __DIR__ . '/..' . '/react/dns/src', + ), + 'React\\ChildProcess\\' => + array ( + 0 => __DIR__ . '/..' . '/react/child-process/src', + ), + 'React\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/react/cache/src', + ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', @@ -163,10 +205,6 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), - 'Psr\\Cache\\' => - array ( - 0 => __DIR__ . '/..' . '/psr/cache/src', - ), 'PhpCsFixer\\' => array ( 0 => __DIR__ . '/..' . '/friendsofphp/php-cs-fixer/src', @@ -175,13 +213,13 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 array ( 0 => __DIR__ . '/..' . '/kint-php/kint/src', ), - 'Doctrine\\Common\\Lexer\\' => + 'Fidry\\CpuCoreCounter\\' => array ( - 0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', + 0 => __DIR__ . '/..' . '/fidry/cpu-core-counter/src', ), - 'Doctrine\\Common\\Annotations\\' => + 'Evenement\\' => array ( - 0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations', + 0 => __DIR__ . '/..' . '/evenement/evenement/src', ), 'Composer\\XdebugHandler\\' => array ( @@ -195,6 +233,10 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 array ( 0 => __DIR__ . '/..' . '/composer/pcre/src', ), + 'Clue\\React\\NDJson\\' => + array ( + 0 => __DIR__ . '/..' . '/clue/ndjson-react/src', + ), ); public static $prefixesPsr0 = array ( @@ -216,6 +258,7 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 public static $classMap = array ( 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', @@ -223,24 +266,24 @@ class ComposerStaticInit2532e47646cbd0ac2fbb391e55b03321 'OpenChecker\\OpenCheckerSetup' => __DIR__ . '/../..' . '/modules/openchecker/openchecker_classes.php', 'OpenChecker\\Pagination' => __DIR__ . '/../..' . '/modules/openchecker/openchecker_classes.php', 'OpenChecker\\convertLongLat' => __DIR__ . '/../..' . '/modules/openchecker/openchecker_classes.php', - 'PhpCsFixer\\Diff\\Chunk' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Chunk.php', - 'PhpCsFixer\\Diff\\ConfigurationException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Exception/ConfigurationException.php', - 'PhpCsFixer\\Diff\\Diff' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Diff.php', - 'PhpCsFixer\\Diff\\Differ' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Differ.php', - 'PhpCsFixer\\Diff\\Exception' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Exception/Exception.php', - 'PhpCsFixer\\Diff\\InvalidArgumentException' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Exception/InvalidArgumentException.php', - 'PhpCsFixer\\Diff\\Line' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Line.php', - 'PhpCsFixer\\Diff\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/LongestCommonSubsequenceCalculator.php', - 'PhpCsFixer\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', - 'PhpCsFixer\\Diff\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Output/AbstractChunkOutputBuilder.php', - 'PhpCsFixer\\Diff\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Output/DiffOnlyOutputBuilder.php', - 'PhpCsFixer\\Diff\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Output/DiffOutputBuilderInterface.php', - 'PhpCsFixer\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', - 'PhpCsFixer\\Diff\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Output/UnifiedDiffOutputBuilder.php', - 'PhpCsFixer\\Diff\\Parser' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/Parser.php', - 'PhpCsFixer\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/php-cs-fixer/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php', + 'SebastianBergmann\\Diff\\ConfigurationException' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/ConfigurationException.php', + 'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php', + 'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php', + 'SebastianBergmann\\Diff\\Exception' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/Exception.php', + 'SebastianBergmann\\Diff\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/diff/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php', + 'SebastianBergmann\\Diff\\LongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/LongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\MemoryEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php', + 'SebastianBergmann\\Diff\\Output\\AbstractChunkOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\DiffOutputBuilderInterface' => __DIR__ . '/..' . '/sebastian/diff/src/Output/DiffOutputBuilderInterface.php', + 'SebastianBergmann\\Diff\\Output\\StrictUnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Output\\UnifiedDiffOutputBuilder' => __DIR__ . '/..' . '/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php', + 'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php', + 'SebastianBergmann\\Diff\\TimeEfficientLongestCommonSubsequenceCalculator' => __DIR__ . '/..' . '/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 44c73e5a66..621dc95d21 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -48,6 +48,73 @@ "abandoned": "endroid/qr-code", "install-path": "../aferrandini/phpqrcode" }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "time": "2022-12-23T10:58:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "install-path": "../clue/ndjson-react" + }, { "name": "composer/pcre", "version": "3.0.0", @@ -124,17 +191,17 @@ }, { "name": "composer/semver", - "version": "3.3.1", - "version_normalized": "3.3.1.0", + "version": "3.4.2", + "version_normalized": "3.4.2.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/5d8e574bb0e69188786b8ef77d43341222a41a71", - "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -144,7 +211,7 @@ "phpstan/phpstan": "^1.4", "symfony/phpunit-bridge": "^4.2 || ^5" }, - "time": "2022-03-16T11:22:07+00:00", + "time": "2024-07-12T11:35:52+00:00", "type": "library", "extra": { "branch-alias": { @@ -186,9 +253,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.1" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -276,39 +343,32 @@ "install-path": "./xdebug-handler" }, { - "name": "doctrine/annotations", - "version": "1.13.2", - "version_normalized": "1.13.2.0", + "name": "evenement/evenement", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", - "reference": "5b668aef16090008790395c02c893b1ba13f7e08", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" + "php": ">=7.0" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" + "phpunit/phpunit": "^9 || ^6" }, - "time": "2021-08-05T19:00:23+00:00", + "time": "2023-08-08T05:53:35+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -317,69 +377,289 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "install-path": "../evenement/evenement" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.14.0", + "version_normalized": "4.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "time": "2021-12-25T01:21:49+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + }, + "install-path": "../ezyang/htmlpurifier" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "time": "2024-02-07T09:43:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "install-path": "../fidry/cpu-core-counter" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.61.1", + "version_normalized": "3.61.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "94a87189f55814e6cabca2d9a33b06de384a2ab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/94a87189f55814e6cabca2d9a33b06de384a2ab8", + "reference": "94a87189f55814e6cabca2d9a33b06de384a2ab8", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.0", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "time": "2024-07-31T14:33:15+00:00", + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" } ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "description": "A tool to automatically fix PHP code style", "keywords": [ - "annotations", - "docblock", - "parser" + "Static code analysis", + "fixer", + "standards", + "static analysis" ], "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.2" + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.61.1" }, - "install-path": "../doctrine/annotations" + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "install-path": "../friendsofphp/php-cs-fixer" }, { - "name": "doctrine/lexer", - "version": "1.2.3", - "version_normalized": "1.2.3.0", + "name": "kint-php/kint", + "version": "3.3", + "version_normalized": "3.3.0.0", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "url": "https://github.com/kint-php/kint.git", + "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/kint-php/kint/zipball/335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", + "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": ">=5.3.6" }, "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "friendsofphp/php-cs-fixer": "^2.0", + "phpunit/phpunit": "^4.0", + "seld/phar-utils": "^1.0", + "symfony/finder": "^2.0 || ^3.0 || ^4.0", + "vimeo/psalm": "^3.0" }, - "time": "2022-02-28T11:07:21+00:00", + "suggest": { + "ext-ctype": "Simple data type tests", + "ext-iconv": "Provides fallback detection for ambiguous legacy string encodings such as the Windows and ISO 8859 code pages", + "ext-mbstring": "Provides string encoding detection", + "kint-php/kint-js": "Provides a simplified dump to console.log()", + "kint-php/kint-twig": "Provides d() and s() functions in twig templates", + "symfony/polyfill-ctype": "Replacement for ext-ctype if missing", + "symfony/polyfill-iconv": "Replacement for ext-iconv if missing", + "symfony/polyfill-mbstring": "Replacement for ext-mbstring if missing" + }, + "time": "2019-10-17T18:05:24+00:00", "type": "library", "installation-source": "dist", "autoload": { + "files": [ + "init.php" + ], "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Kint\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -388,164 +668,310 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Jonathan Vollebregt", + "homepage": "https://github.com/jnvsor" }, { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Rokas Šleinius", + "homepage": "https://github.com/raveren" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Contributors", + "homepage": "https://github.com/kint-php/kint/graphs/contributors" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "Kint - debugging tool for PHP developers", + "homepage": "https://kint-php.github.io/kint/", "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", + "debug", + "kint", "php" ], "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" + "issues": "https://github.com/kint-php/kint/issues", + "source": "https://github.com/kint-php/kint/tree/master" }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, + "install-path": "../kint-php/kint" + }, + { + "name": "npm-asset/js-cookie", + "version": "2.1.3", + "version_normalized": "2.1.3.0", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.1.3.tgz" + }, + "type": "npm-asset", + "installation-source": "dist", + "license": [ + "MIT" + ], + "install-path": "../npm-asset/js-cookie" + }, + { + "name": "psr/container", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:50:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "time": "2019-01-08T18:20:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "install-path": "../doctrine/lexer" + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "install-path": "../psr/event-dispatcher" }, { - "name": "ezyang/htmlpurifier", - "version": "v4.14.0", - "version_normalized": "4.14.0.0", + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", "source": { "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { - "php": ">=5.2" + "php": ">=5.3.0" }, - "time": "2021-12-25T01:21:49+00:00", + "time": "2021-05-03T11:20:27+00:00", "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, "installation-source": "dist", "autoload": { - "files": [ - "library/HTMLPurifier.composer.php" - ], - "psr-0": { - "HTMLPurifier": "library/" - }, - "exclude-from-classmap": [ - "/library/HTMLPurifier/Language/" - ] + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1-or-later" + "MIT" ], "authors": [ { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "html" + "log", + "psr", + "psr-3" ], "support": { - "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "install-path": "../ezyang/htmlpurifier" + "install-path": "../psr/log" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.8.0", - "version_normalized": "3.8.0.0", + "name": "react/cache", + "version": "v1.2.0", + "version_normalized": "1.2.0.0", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", - "reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", "shasum": "" }, "require": { - "composer/semver": "^3.2", - "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^1.13", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0", - "php-cs-fixer/diff": "^2.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.25", - "symfony/polyfill-php81": "^1.25", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.10", - "php-coveralls/php-coveralls": "^2.5.2", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^6.0", - "symfony/yaml": "^5.4 || ^6.0" + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." + "time": "2022-11-30T15:59:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/cache" + }, + { + "name": "react/child-process", + "version": "v0.6.5", + "version_normalized": "0.6.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, - "time": "2022-03-18T17:20:59+00:00", - "bin": [ - "php-cs-fixer" - ], - "type": "application", + "time": "2022-09-16T13:41:56+00:00", + "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "PhpCsFixer\\": "src/" + "React\\ChildProcess\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -554,71 +980,80 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "A tool to automatically fix PHP code style", + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.5" }, "funding": [ { - "url": "https://github.com/keradus", + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", "type": "github" } ], - "install-path": "../friendsofphp/php-cs-fixer" + "install-path": "../react/child-process" }, { - "name": "kint-php/kint", - "version": "3.3", - "version_normalized": "3.3.0.0", + "name": "react/dns", + "version": "v1.13.0", + "version_normalized": "1.13.0.0", "source": { "type": "git", - "url": "https://github.com/kint-php/kint.git", - "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b" + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kint-php/kint/zipball/335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", - "reference": "335ac1bcaf04d87df70d8aa51e8887ba2c6d203b", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", "shasum": "" }, "require": { - "php": ">=5.3.6" + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "phpunit/phpunit": "^4.0", - "seld/phar-utils": "^1.0", - "symfony/finder": "^2.0 || ^3.0 || ^4.0", - "vimeo/psalm": "^3.0" - }, - "suggest": { - "ext-ctype": "Simple data type tests", - "ext-iconv": "Provides fallback detection for ambiguous legacy string encodings such as the Windows and ISO 8859 code pages", - "ext-mbstring": "Provides string encoding detection", - "kint-php/kint-js": "Provides a simplified dump to console.log()", - "kint-php/kint-twig": "Provides d() and s() functions in twig templates", - "symfony/polyfill-ctype": "Replacement for ext-ctype if missing", - "symfony/polyfill-iconv": "Replacement for ext-iconv if missing", - "symfony/polyfill-mbstring": "Replacement for ext-mbstring if missing" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" }, - "time": "2019-10-17T18:05:24+00:00", + "time": "2024-06-13T14:18:03+00:00", "type": "library", "installation-source": "dist", "autoload": { - "files": [ - "init.php" - ], "psr-4": { - "Kint\\": "src/" + "React\\Dns\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -627,130 +1062,151 @@ ], "authors": [ { - "name": "Jonathan Vollebregt", - "homepage": "https://github.com/jnvsor" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Rokas Šleinius", - "homepage": "https://github.com/raveren" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" }, { - "name": "Contributors", - "homepage": "https://github.com/kint-php/kint/graphs/contributors" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Kint - debugging tool for PHP developers", - "homepage": "https://kint-php.github.io/kint/", + "description": "Async DNS resolver for ReactPHP", "keywords": [ - "debug", - "kint", - "php" + "async", + "dns", + "dns-resolver", + "reactphp" ], "support": { - "issues": "https://github.com/kint-php/kint/issues", - "source": "https://github.com/kint-php/kint/tree/master" - }, - "install-path": "../kint-php/kint" - }, - { - "name": "npm-asset/js-cookie", - "version": "2.1.3", - "version_normalized": "2.1.3.0", - "dist": { - "type": "tar", - "url": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.1.3.tgz" + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" }, - "type": "npm-asset", - "installation-source": "dist", - "license": [ - "MIT" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } ], - "install-path": "../npm-asset/js-cookie" + "install-path": "../react/dns" }, { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", - "version_normalized": "2.0.2.0", + "name": "react/event-loop", + "version": "v1.5.0", + "version_normalized": "1.5.0.0", "source": { "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0 || ^8.0" + "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, - "time": "2020-10-14T08:32:19+00:00", + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "time": "2023-11-13T13:48:05+00:00", "type": "library", "installation-source": "dist", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "React\\EventLoop\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ - "diff" + "asynchronous", + "event-loop" ], "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, - "install-path": "../php-cs-fixer/diff" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/event-loop" }, { - "name": "psr/cache", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "name": "react/promise", + "version": "v3.2.0", + "version_normalized": "3.2.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1.0" }, - "time": "2016-08-06T20:24:11+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, + "time": "2024-05-24T10:39:05+00:00", + "type": "library", "installation-source": "dist", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Psr\\Cache\\": "src/" + "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -759,45 +1215,78 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common interface for caching libraries", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "cache", - "psr", - "psr-6" + "promise", + "promises" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" }, - "install-path": "../psr/cache" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/promise" }, { - "name": "psr/container", - "version": "1.1.2", - "version_normalized": "1.1.2.0", + "name": "react/socket", + "version": "v1.16.0", + "version_normalized": "1.16.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { - "php": ">=7.4.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, - "time": "2021-11-05T16:50:12+00:00", + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "time": "2024-07-26T10:38:09+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "React\\Socket\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -806,54 +1295,76 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "Connection", + "Socket", + "async", + "reactphp", + "stream" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, - "install-path": "../psr/container" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/socket" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "version_normalized": "1.0.0.0", + "name": "react/stream", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { - "php": ">=7.2.0" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" }, - "time": "2019-01-08T18:20:26+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, + "time": "2024-06-11T12:45:25+00:00", + "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "Psr\\EventDispatcher\\": "src/" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -862,74 +1373,117 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Standard interfaces for event handling.", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ - "events", - "psr", - "psr-14" + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" ], "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" }, - "install-path": "../psr/event-dispatcher" + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "install-path": "../react/stream" }, { - "name": "psr/log", - "version": "1.1.4", - "version_normalized": "1.1.4.0", + "name": "sebastian/diff", + "version": "4.0.6", + "version_normalized": "4.0.6.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.3" }, - "time": "2021-05-03T11:20:27+00:00", + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "time": "2024-03-02T06:30:58+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "4.0-dev" } }, "installation-source": "dist", "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "log", - "psr", - "psr-3" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, - "install-path": "../psr/log" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "install-path": "../sebastian/diff" }, { "name": "symfony/console", @@ -1736,17 +2290,17 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.25.0", - "version_normalized": "1.25.0.0", + "version": "v1.30.0", + "version_normalized": "1.30.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -1758,12 +2312,9 @@ "suggest": { "ext-mbstring": "For best performance" }, - "time": "2021-11-30T18:21:41+00:00", + "time": "2024-06-19T12:30:46+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1802,7 +2353,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -1904,28 +2455,25 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.25.0", - "version_normalized": "1.25.0.0", + "version": "v1.30.0", + "version_normalized": "1.30.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", - "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { "php": ">=7.1" }, - "time": "2022-03-04T08:16:47+00:00", + "time": "2024-05-31T15:07:36+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1970,7 +2518,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -1990,28 +2538,25 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.25.0", - "version_normalized": "1.25.0.0", + "version": "v1.30.0", + "version_normalized": "1.30.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", "shasum": "" }, "require": { "php": ">=7.1" }, - "time": "2021-09-13T13:58:11+00:00", + "time": "2024-06-19T12:30:46+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2052,7 +2597,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" }, "funding": [ { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index ad984f6840..44abb162b2 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,148 +1,139 @@ array( + 'name' => 'opencaching/opencaching.pl', 'pretty_version' => 'dev-master', 'version' => 'dev-master', + 'reference' => '2bd9164a70851e8dad1397be99d4aa56c9c95fc4', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'a220537ea4a6443509efa79db721feb191a83cd2', - 'name' => 'opencaching/opencaching.pl', 'dev' => true, ), 'versions' => array( 'aferrandini/phpqrcode' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', + 'reference' => '3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46', 'type' => 'library', 'install_path' => __DIR__ . '/../aferrandini/phpqrcode', 'aliases' => array(), - 'reference' => '3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46', + 'dev_requirement' => false, + ), + 'clue/ndjson-react' => array( + 'pretty_version' => 'v1.3.0', + 'version' => '1.3.0.0', + 'reference' => '392dc165fce93b5bb5c637b67e59619223c931b0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../clue/ndjson-react', + 'aliases' => array(), 'dev_requirement' => false, ), 'composer/pcre' => array( 'pretty_version' => '3.0.0', 'version' => '3.0.0.0', + 'reference' => 'e300eb6c535192decd27a85bc72a9290f0d6b3bd', 'type' => 'library', 'install_path' => __DIR__ . '/./pcre', 'aliases' => array(), - 'reference' => 'e300eb6c535192decd27a85bc72a9290f0d6b3bd', 'dev_requirement' => false, ), 'composer/semver' => array( - 'pretty_version' => '3.3.1', - 'version' => '3.3.1.0', + 'pretty_version' => '3.4.2', + 'version' => '3.4.2.0', + 'reference' => 'c51258e759afdb17f1fd1fe83bc12baaef6309d6', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), - 'reference' => '5d8e574bb0e69188786b8ef77d43341222a41a71', 'dev_requirement' => false, ), 'composer/xdebug-handler' => array( 'pretty_version' => '3.0.3', 'version' => '3.0.3.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/./xdebug-handler', - 'aliases' => array(), 'reference' => 'ced299686f41dce890debac69273b47ffe98a40c', - 'dev_requirement' => false, - ), - 'doctrine/annotations' => array( - 'pretty_version' => '1.13.2', - 'version' => '1.13.2.0', 'type' => 'library', - 'install_path' => __DIR__ . '/../doctrine/annotations', + 'install_path' => __DIR__ . '/./xdebug-handler', 'aliases' => array(), - 'reference' => '5b668aef16090008790395c02c893b1ba13f7e08', 'dev_requirement' => false, ), - 'doctrine/lexer' => array( - 'pretty_version' => '1.2.3', - 'version' => '1.2.3.0', + 'evenement/evenement' => array( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'reference' => '0a16b0d71ab13284339abb99d9d2bd813640efbc', 'type' => 'library', - 'install_path' => __DIR__ . '/../doctrine/lexer', + 'install_path' => __DIR__ . '/../evenement/evenement', 'aliases' => array(), - 'reference' => 'c268e882d4dbdd85e36e4ad69e02dc284f89d229', 'dev_requirement' => false, ), 'ezyang/htmlpurifier' => array( 'pretty_version' => 'v4.14.0', 'version' => '4.14.0.0', + 'reference' => '12ab42bd6e742c70c0a52f7b82477fcd44e64b75', 'type' => 'library', 'install_path' => __DIR__ . '/../ezyang/htmlpurifier', 'aliases' => array(), - 'reference' => '12ab42bd6e742c70c0a52f7b82477fcd44e64b75', + 'dev_requirement' => false, + ), + 'fidry/cpu-core-counter' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => 'f92996c4d5c1a696a6a970e20f7c4216200fcc42', + 'type' => 'library', + 'install_path' => __DIR__ . '/../fidry/cpu-core-counter', + 'aliases' => array(), 'dev_requirement' => false, ), 'friendsofphp/php-cs-fixer' => array( - 'pretty_version' => 'v3.8.0', - 'version' => '3.8.0.0', + 'pretty_version' => 'v3.61.1', + 'version' => '3.61.1.0', + 'reference' => '94a87189f55814e6cabca2d9a33b06de384a2ab8', 'type' => 'application', 'install_path' => __DIR__ . '/../friendsofphp/php-cs-fixer', 'aliases' => array(), - 'reference' => 'cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3', 'dev_requirement' => false, ), 'kint-php/kint' => array( 'pretty_version' => '3.3', 'version' => '3.3.0.0', + 'reference' => '335ac1bcaf04d87df70d8aa51e8887ba2c6d203b', 'type' => 'library', 'install_path' => __DIR__ . '/../kint-php/kint', 'aliases' => array(), - 'reference' => '335ac1bcaf04d87df70d8aa51e8887ba2c6d203b', 'dev_requirement' => false, ), 'npm-asset/js-cookie' => array( 'pretty_version' => '2.1.3', 'version' => '2.1.3.0', + 'reference' => null, 'type' => 'npm-asset', 'install_path' => __DIR__ . '/../npm-asset/js-cookie', 'aliases' => array(), - 'reference' => NULL, 'dev_requirement' => false, ), 'opencaching/opencaching.pl' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', + 'reference' => '2bd9164a70851e8dad1397be99d4aa56c9c95fc4', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'a220537ea4a6443509efa79db721feb191a83cd2', - 'dev_requirement' => false, - ), - 'php-cs-fixer/diff' => array( - 'pretty_version' => 'v2.0.2', - 'version' => '2.0.2.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../php-cs-fixer/diff', - 'aliases' => array(), - 'reference' => '29dc0d507e838c4580d018bd8b5cb412474f7ec3', - 'dev_requirement' => false, - ), - 'psr/cache' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/cache', - 'aliases' => array(), - 'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', 'dev_requirement' => false, ), 'psr/container' => array( 'pretty_version' => '1.1.2', 'version' => '1.1.2.0', + 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/container', 'aliases' => array(), - 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', 'dev_requirement' => false, ), 'psr/event-dispatcher' => array( 'pretty_version' => '1.0.0', 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/event-dispatcher', 'aliases' => array(), - 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', 'dev_requirement' => false, ), 'psr/event-dispatcher-implementation' => array( @@ -154,10 +145,10 @@ 'psr/log' => array( 'pretty_version' => '1.1.4', 'version' => '1.1.4.0', + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), - 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'dev_requirement' => false, ), 'psr/log-implementation' => array( @@ -166,40 +157,112 @@ 0 => '1.0|2.0', ), ), + 'react/cache' => array( + 'pretty_version' => 'v1.2.0', + 'version' => '1.2.0.0', + 'reference' => 'd47c472b64aa5608225f47965a484b75c7817d5b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'react/child-process' => array( + 'pretty_version' => 'v0.6.5', + 'version' => '0.6.5.0', + 'reference' => 'e71eb1aa55f057c7a4a0d08d06b0b0a484bead43', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/child-process', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'react/dns' => array( + 'pretty_version' => 'v1.13.0', + 'version' => '1.13.0.0', + 'reference' => 'eb8ae001b5a455665c89c1df97f6fb682f8fb0f5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/dns', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'react/event-loop' => array( + 'pretty_version' => 'v1.5.0', + 'version' => '1.5.0.0', + 'reference' => 'bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/event-loop', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'react/promise' => array( + 'pretty_version' => 'v3.2.0', + 'version' => '3.2.0.0', + 'reference' => '8a164643313c71354582dc850b42b33fa12a4b63', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/promise', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'react/socket' => array( + 'pretty_version' => 'v1.16.0', + 'version' => '1.16.0.0', + 'reference' => '23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/socket', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'react/stream' => array( + 'pretty_version' => 'v1.4.0', + 'version' => '1.4.0.0', + 'reference' => '1e5b0acb8fe55143b5b426817155190eb6f5b18d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../react/stream', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'sebastian/diff' => array( + 'pretty_version' => '4.0.6', + 'version' => '4.0.6.0', + 'reference' => 'ba01945089c3a293b01ba9badc29ad55b106b0bc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sebastian/diff', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'symfony/console' => array( 'pretty_version' => 'v5.4.5', 'version' => '5.4.5.0', + 'reference' => 'd8111acc99876953f52fe16d4c50eb60940d49ad', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), - 'reference' => 'd8111acc99876953f52fe16d4c50eb60940d49ad', 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( 'pretty_version' => 'v2.5.0', 'version' => '2.5.0.0', + 'reference' => '6f981ee24cf69ee7ce9736146d1c57c2780598a8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), - 'reference' => '6f981ee24cf69ee7ce9736146d1c57c2780598a8', 'dev_requirement' => false, ), 'symfony/event-dispatcher' => array( 'pretty_version' => 'v5.4.3', 'version' => '5.4.3.0', + 'reference' => 'dec8a9f58d20df252b9cd89f1c6c1530f747685d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher', 'aliases' => array(), - 'reference' => 'dec8a9f58d20df252b9cd89f1c6c1530f747685d', 'dev_requirement' => false, ), 'symfony/event-dispatcher-contracts' => array( 'pretty_version' => 'v2.5.0', 'version' => '2.5.0.0', + 'reference' => '66bea3b09be61613cd3b4043a65a8ec48cfa6d2a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', 'aliases' => array(), - 'reference' => '66bea3b09be61613cd3b4043a65a8ec48cfa6d2a', 'dev_requirement' => false, ), 'symfony/event-dispatcher-implementation' => array( @@ -211,127 +274,127 @@ 'symfony/filesystem' => array( 'pretty_version' => 'v5.4.6', 'version' => '5.4.6.0', + 'reference' => 'd53a45039974952af7f7ebc461ccdd4295e29440', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), - 'reference' => 'd53a45039974952af7f7ebc461ccdd4295e29440', 'dev_requirement' => false, ), 'symfony/finder' => array( 'pretty_version' => 'v5.4.3', 'version' => '5.4.3.0', + 'reference' => '231313534dded84c7ecaa79d14bc5da4ccb69b7d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), - 'reference' => '231313534dded84c7ecaa79d14bc5da4ccb69b7d', 'dev_requirement' => false, ), 'symfony/options-resolver' => array( 'pretty_version' => 'v5.4.3', 'version' => '5.4.3.0', + 'reference' => 'cc1147cb11af1b43f503ac18f31aa3bec213aba8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/options-resolver', 'aliases' => array(), - 'reference' => 'cc1147cb11af1b43f503ac18f31aa3bec213aba8', 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( 'pretty_version' => 'v1.25.0', 'version' => '1.25.0.0', + 'reference' => '30885182c981ab175d4d034db0f6f469898070ab', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), - 'reference' => '30885182c981ab175d4d034db0f6f469898070ab', 'dev_requirement' => false, ), 'symfony/polyfill-intl-grapheme' => array( 'pretty_version' => 'v1.25.0', 'version' => '1.25.0.0', + 'reference' => '81b86b50cf841a64252b439e738e97f4a34e2783', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), - 'reference' => '81b86b50cf841a64252b439e738e97f4a34e2783', 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( 'pretty_version' => 'v1.25.0', 'version' => '1.25.0.0', + 'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), - 'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8', 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.25.0', - 'version' => '1.25.0.0', + 'pretty_version' => 'v1.30.0', + 'version' => '1.30.0.0', + 'reference' => 'fd22ab50000ef01661e2a31d850ebaa297f8e03c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), - 'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825', 'dev_requirement' => false, ), 'symfony/polyfill-php73' => array( 'pretty_version' => 'v1.25.0', 'version' => '1.25.0.0', + 'reference' => 'cc5db0e22b3cb4111010e48785a97f670b350ca5', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php73', 'aliases' => array(), - 'reference' => 'cc5db0e22b3cb4111010e48785a97f670b350ca5', 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.25.0', - 'version' => '1.25.0.0', + 'pretty_version' => 'v1.30.0', + 'version' => '1.30.0.0', + 'reference' => '77fa7995ac1b21ab60769b7323d600a991a90433', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), - 'reference' => '4407588e0d3f1f52efb65fbe92babe41f37fe50c', 'dev_requirement' => false, ), 'symfony/polyfill-php81' => array( - 'pretty_version' => 'v1.25.0', - 'version' => '1.25.0.0', + 'pretty_version' => 'v1.30.0', + 'version' => '1.30.0.0', + 'reference' => '3fb075789fb91f9ad9af537c4012d523085bd5af', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php81', 'aliases' => array(), - 'reference' => '5de4ba2d41b15f9bd0e19b2ab9674135813ec98f', 'dev_requirement' => false, ), 'symfony/process' => array( 'pretty_version' => 'v5.4.5', 'version' => '5.4.5.0', + 'reference' => '95440409896f90a5f85db07a32b517ecec17fa4c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/process', 'aliases' => array(), - 'reference' => '95440409896f90a5f85db07a32b517ecec17fa4c', 'dev_requirement' => false, ), 'symfony/service-contracts' => array( 'pretty_version' => 'v2.5.0', 'version' => '2.5.0.0', + 'reference' => '1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/service-contracts', 'aliases' => array(), - 'reference' => '1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc', 'dev_requirement' => false, ), 'symfony/stopwatch' => array( 'pretty_version' => 'v5.4.5', 'version' => '5.4.5.0', + 'reference' => '4d04b5c24f3c9a1a168a131f6cbe297155bc0d30', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/stopwatch', 'aliases' => array(), - 'reference' => '4d04b5c24f3c9a1a168a131f6cbe297155bc0d30', 'dev_requirement' => false, ), 'symfony/string' => array( 'pretty_version' => 'v5.4.3', 'version' => '5.4.3.0', + 'reference' => '92043b7d8383e48104e411bc9434b260dbeb5a10', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/string', 'aliases' => array(), - 'reference' => '92043b7d8383e48104e411bc9434b260dbeb5a10', 'dev_requirement' => false, ), ), diff --git a/vendor/composer/semver/CHANGELOG.md b/vendor/composer/semver/CHANGELOG.md index b61f0dff9d..7e44191440 100644 --- a/vendor/composer/semver/CHANGELOG.md +++ b/vendor/composer/semver/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +### [3.4.2] 2024-07-12 + + * Fixed PHP 5.3 syntax error + +### [3.4.1] 2024-07-12 + + * Fixed normalizeStability's return type to enforce valid stabilities + +### [3.4.0] 2023-08-31 + + * Support larger major version numbers (#149) + +### [3.3.2] 2022-04-01 + + * Fixed handling of non-string values (#134) + ### [3.3.1] 2022-03-16 * Fixed possible cache key clash in the CompilingMatcher memoization (#132) @@ -171,6 +187,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Namespace: `Composer\Test\Package\LinkConstraint` -> `Composer\Test\Semver\Constraint` * Changed: code style using php-cs-fixer. +[3.4.2]: https://github.com/composer/semver/compare/3.4.1...3.4.2 +[3.4.1]: https://github.com/composer/semver/compare/3.4.0...3.4.1 +[3.4.0]: https://github.com/composer/semver/compare/3.3.2...3.4.0 +[3.3.2]: https://github.com/composer/semver/compare/3.3.1...3.3.2 [3.3.1]: https://github.com/composer/semver/compare/3.3.0...3.3.1 [3.3.0]: https://github.com/composer/semver/compare/3.2.9...3.3.0 [3.2.9]: https://github.com/composer/semver/compare/3.2.8...3.2.9 diff --git a/vendor/composer/semver/README.md b/vendor/composer/semver/README.md index 99bc46151f..7677849065 100644 --- a/vendor/composer/semver/README.md +++ b/vendor/composer/semver/README.md @@ -1,13 +1,14 @@ composer/semver =============== -Semver library that offers utilities, version constraint parsing and validation. +Semver (Semantic Versioning) library that offers utilities, version constraint parsing and validation. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. -[![Continuous Integration](https://github.com/composer/semver/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/semver/actions) - +[![Continuous Integration](https://github.com/composer/semver/actions/workflows/continuous-integration.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/continuous-integration.yml) +[![PHP Lint](https://github.com/composer/semver/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/lint.yml) +[![PHPStan](https://github.com/composer/semver/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/composer/semver/actions/workflows/phpstan.yml) Installation ------------ @@ -15,7 +16,7 @@ Installation Install the latest version with: ```bash -$ composer require composer/semver +composer require composer/semver ``` diff --git a/vendor/composer/semver/composer.json b/vendor/composer/semver/composer.json index ba78676ddc..f3a6f4cc68 100644 --- a/vendor/composer/semver/composer.json +++ b/vendor/composer/semver/composer.json @@ -27,7 +27,7 @@ } ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues" }, "require": { diff --git a/vendor/composer/semver/src/VersionParser.php b/vendor/composer/semver/src/VersionParser.php index 1754a76378..305a0faec1 100644 --- a/vendor/composer/semver/src/VersionParser.php +++ b/vendor/composer/semver/src/VersionParser.php @@ -51,7 +51,7 @@ class VersionParser */ public static function parseStability($version) { - $version = (string) preg_replace('{#.+$}', '', $version); + $version = (string) preg_replace('{#.+$}', '', (string) $version); if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) { return 'dev'; @@ -82,10 +82,15 @@ public static function parseStability($version) * @param string $stability * * @return string + * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev' */ public static function normalizeStability($stability) { - $stability = strtolower($stability); + $stability = strtolower((string) $stability); + + if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), true)) { + throw new \InvalidArgumentException('Invalid stability string "'.$stability.'", expected one of stable, RC, beta, alpha or dev'); + } return $stability === 'rc' ? 'RC' : $stability; } @@ -94,7 +99,7 @@ public static function normalizeStability($stability) * Normalizes a version string to be able to perform comparisons on it. * * @param string $version - * @param string $fullVersion optional complete version string to give more context + * @param ?string $fullVersion optional complete version string to give more context * * @throws \UnexpectedValueException * @@ -102,7 +107,7 @@ public static function normalizeStability($stability) */ public function normalize($version, $fullVersion = null) { - $version = trim($version); + $version = trim((string) $version); $origVersion = $version; if (null === $fullVersion) { $fullVersion = $version; @@ -134,15 +139,15 @@ public function normalize($version, $fullVersion = null) } // match classical versioning - if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { + if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0'); $index = 5; // match date(time) based versioning - } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { - $version = preg_replace('{\D}', '.', $matches[1]); + } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) { + $version = (string) preg_replace('{\D}', '.', $matches[1]); $index = 2; } @@ -195,7 +200,7 @@ public function normalize($version, $fullVersion = null) */ public function parseNumericAliasPrefix($branch) { - if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) { + if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) { return $matches['version'] . '.'; } @@ -211,7 +216,7 @@ public function parseNumericAliasPrefix($branch) */ public function normalizeBranch($name) { - $name = trim($name); + $name = trim((string) $name); if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; @@ -231,6 +236,8 @@ public function normalizeBranch($name) * @param string $name * * @return string + * + * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore */ public function normalizeDefaultBranch($name) { @@ -238,7 +245,7 @@ public function normalizeDefaultBranch($name) return '9999999-dev'; } - return $name; + return (string) $name; } /** @@ -250,24 +257,24 @@ public function normalizeDefaultBranch($name) */ public function parseConstraints($constraints) { - $prettyConstraint = $constraints; + $prettyConstraint = (string) $constraints; - $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); + $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints)); if (false === $orConstraints) { throw new \RuntimeException('Failed to preg_split string: '.$constraints); } $orGroups = array(); - foreach ($orConstraints as $constraints) { - $andConstraints = preg_split('{(?< ,]) *(?< ,]) *(? 1) { $constraintObjects = array(); - foreach ($andConstraints as $constraint) { - foreach ($this->parseConstraint($constraint) as $parsedConstraint) { - $constraintObjects[] = $parsedConstraint; + foreach ($andConstraints as $andConstraint) { + foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) { + $constraintObjects[] = $parsedAndConstraint; } } } else { @@ -283,11 +290,11 @@ public function parseConstraints($constraints) $orGroups[] = $constraint; } - $constraint = MultiConstraint::create($orGroups, false); + $parsedConstraint = MultiConstraint::create($orGroups, false); - $constraint->setPrettyString($prettyConstraint); + $parsedConstraint->setPrettyString($prettyConstraint); - return $constraint; + return $parsedConstraint; } /** diff --git a/vendor/doctrine/annotations/README.md b/vendor/doctrine/annotations/README.md deleted file mode 100644 index c2c7eb7bad..0000000000 --- a/vendor/doctrine/annotations/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Doctrine Annotations - -[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions) -[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations) -[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references) -[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations) -[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations) - -Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). - -## Documentation - -See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). - -## Contributing - -When making a pull request, make sure your changes follow the -[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction). diff --git a/vendor/doctrine/annotations/composer.json b/vendor/doctrine/annotations/composer.json deleted file mode 100644 index 00d0231075..0000000000 --- a/vendor/doctrine/annotations/composer.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "doctrine/annotations", - "type": "library", - "description": "Docblock Annotations Parser", - "keywords": ["annotations", "docblock", "parser"], - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "license": "MIT", - "authors": [ - {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, - {"name": "Roman Borschel", "email": "roman@code-factory.org"}, - {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, - {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, - {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} - ], - "require": { - "php": "^7.1 || ^8.0", - "ext-tokenizer": "*", - "doctrine/lexer": "1.*", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2" - }, - "config": { - "sort-packages": true - }, - "autoload": { - "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } - }, - "autoload-dev": { - "psr-4": { - "Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations", - "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" - }, - "files": [ - "tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php", - "tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php" - ] - } -} diff --git a/vendor/doctrine/annotations/docs/en/annotations.rst b/vendor/doctrine/annotations/docs/en/annotations.rst deleted file mode 100644 index 2c3c428654..0000000000 --- a/vendor/doctrine/annotations/docs/en/annotations.rst +++ /dev/null @@ -1,252 +0,0 @@ -Handling Annotations -==================== - -There are several different approaches to handling annotations in PHP. -Doctrine Annotations maps docblock annotations to PHP classes. Because -not all docblock annotations are used for metadata purposes a filter is -applied to ignore or skip classes that are not Doctrine annotations. - -Take a look at the following code snippet: - -.. code-block:: php - - namespace MyProject\Entities; - - use Doctrine\ORM\Mapping AS ORM; - use Symfony\Component\Validator\Constraints AS Assert; - - /** - * @author Benjamin Eberlei - * @ORM\Entity - * @MyProject\Annotations\Foobarable - */ - class User - { - /** - * @ORM\Id @ORM\Column @ORM\GeneratedValue - * @dummy - * @var int - */ - private $id; - - /** - * @ORM\Column(type="string") - * @Assert\NotEmpty - * @Assert\Email - * @var string - */ - private $email; - } - -In this snippet you can see a variety of different docblock annotations: - -- Documentation annotations such as ``@var`` and ``@author``. These - annotations are ignored and never considered for throwing an - exception due to wrongly used annotations. -- Annotations imported through use statements. The statement ``use - Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace - available as ``@ORM\ClassName``. Same goes for the import of - ``@Assert``. -- The ``@dummy`` annotation. It is not a documentation annotation and - not ignored. For Doctrine Annotations it is not entirely clear how - to handle this annotation. Depending on the configuration an exception - (unknown annotation) will be thrown when parsing this annotation. -- The fully qualified annotation ``@MyProject\Annotations\Foobarable``. - This is transformed directly into the given class name. - -How are these annotations loaded? From looking at the code you could -guess that the ORM Mapping, Assert Validation and the fully qualified -annotation can just be loaded using -the defined PHP autoloaders. This is not the case however: For error -handling reasons every check for class existence inside the -``AnnotationReader`` sets the second parameter $autoload -of ``class_exists($name, $autoload)`` to false. To work flawlessly the -``AnnotationReader`` requires silent autoloaders which many autoloaders are -not. Silent autoloading is NOT part of the `PSR-0 specification -`_ -for autoloading. - -This is why Doctrine Annotations uses its own autoloading mechanism -through a global registry. If you are wondering about the annotation -registry being global, there is no other way to solve the architectural -problems of autoloading annotation classes in a straightforward fashion. -Additionally if you think about PHP autoloading then you recognize it is -a global as well. - -To anticipate the configuration section, making the above PHP class work -with Doctrine Annotations requires this setup: - -.. code-block:: php - - use Doctrine\Common\Annotations\AnnotationReader; - use Doctrine\Common\Annotations\AnnotationRegistry; - - AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); - AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); - AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); - - $reader = new AnnotationReader(); - AnnotationReader::addGlobalIgnoredName('dummy'); - -The second block with the annotation registry calls registers all the -three different annotation namespaces that are used. -Doctrine Annotations saves all its annotations in a single file, that is -why ``AnnotationRegistry#registerFile`` is used in contrast to -``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 -compatible loading mechanism for class to file names. - -In the third block, we create the actual ``AnnotationReader`` instance. -Note that we also add ``dummy`` to the global list of ignored -annotations for which we do not throw exceptions. Setting this is -necessary in our example case, otherwise ``@dummy`` would trigger an -exception to be thrown during the parsing of the docblock of -``MyProject\Entities\User#id``. - -Setup and Configuration ------------------------ - -To use the annotations library is simple, you just need to create a new -``AnnotationReader`` instance: - -.. code-block:: php - - $reader = new \Doctrine\Common\Annotations\AnnotationReader(); - -This creates a simple annotation reader with no caching other than in -memory (in php arrays). Since parsing docblocks can be expensive you -should cache this process by using a caching reader. - -To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``. -This reader decorates the original reader and stores all annotations in a PSR-6 -cache: - -.. code-block:: php - - use Doctrine\Common\Annotations\AnnotationReader; - use Doctrine\Common\Annotations\PsrCachedReader; - - $cache = ... // instantiate a PSR-6 Cache pool - - $reader = new PsrCachedReader( - new AnnotationReader(), - $cache, - $debug = true - ); - -The ``debug`` flag is used here as well to invalidate the cache files -when the PHP class with annotations changed and should be used during -development. - -.. warning :: - - The ``AnnotationReader`` works and caches under the - assumption that all annotations of a doc-block are processed at - once. That means that annotation classes that do not exist and - aren't loaded and cannot be autoloaded (using the - AnnotationRegistry) would never be visible and not accessible if a - cache is used unless the cache is cleared and the annotations - requested again, this time with all annotations defined. - -By default the annotation reader returns a list of annotations with -numeric indexes. If you want your annotations to be indexed by their -class name you can wrap the reader in an ``IndexedReader``: - -.. code-block:: php - - use Doctrine\Common\Annotations\AnnotationReader; - use Doctrine\Common\Annotations\IndexedReader; - - $reader = new IndexedReader(new AnnotationReader()); - -.. warning:: - - You should never wrap the indexed reader inside a cached reader, - only the other way around. This way you can re-use the cache with - indexed or numeric keys, otherwise your code may experience failures - due to caching in a numerical or indexed format. - -Registering Annotations -~~~~~~~~~~~~~~~~~~~~~~~ - -As explained in the introduction, Doctrine Annotations uses its own -autoloading mechanism to determine if a given annotation has a -corresponding PHP class that can be autoloaded. For annotation -autoloading you have to configure the -``Doctrine\Common\Annotations\AnnotationRegistry``. There are three -different mechanisms to configure annotation autoloading: - -- Calling ``AnnotationRegistry#registerFile($file)`` to register a file - that contains one or more annotation classes. -- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = - null)`` to register that the given namespace contains annotations and - that their base directory is located at the given $dirs or in the - include path if ``NULL`` is passed. The given directories should *NOT* - be the directory where classes of the namespace are in, but the base - directory of the root namespace. The AnnotationRegistry uses a - namespace to directory separator approach to resolve the correct path. -- Calling ``AnnotationRegistry#registerLoader($callable)`` to register - an autoloader callback. The callback accepts the class as first and - only parameter and has to return ``true`` if the corresponding file - was found and included. - -.. note:: - - Loaders have to fail silently, if a class is not found even if it - matches for example the namespace prefix of that loader. Never is a - loader to throw a warning or exception if the loading failed - otherwise parsing doc block annotations will become a huge pain. - -A sample loader callback could look like: - -.. code-block:: php - - use Doctrine\Common\Annotations\AnnotationRegistry; - use Symfony\Component\ClassLoader\UniversalClassLoader; - - AnnotationRegistry::registerLoader(function($class) { - $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; - - if (file_exists("/my/base/path/" . $file)) { - // file_exists() makes sure that the loader fails silently - require "/my/base/path/" . $file; - } - }); - - $loader = new UniversalClassLoader(); - AnnotationRegistry::registerLoader(array($loader, "loadClass")); - - -Ignoring missing exceptions -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default an exception is thrown from the ``AnnotationReader`` if an -annotation was found that: - -- is not part of the list of ignored "documentation annotations"; -- was not imported through a use statement; -- is not a fully qualified class that exists. - -You can disable this behavior for specific names if your docblocks do -not follow strict requirements: - -.. code-block:: php - - $reader = new \Doctrine\Common\Annotations\AnnotationReader(); - AnnotationReader::addGlobalIgnoredName('foo'); - -PHP Imports -~~~~~~~~~~~ - -By default the annotation reader parses the use-statement of a php file -to gain access to the import rules and register them for the annotation -processing. Only if you are using PHP Imports can you validate the -correct usage of annotations and throw exceptions if you misspelled an -annotation. This mechanism is enabled by default. - -To ease the upgrade path, we still allow you to disable this mechanism. -Note however that we will remove this in future versions: - -.. code-block:: php - - $reader = new \Doctrine\Common\Annotations\AnnotationReader(); - $reader->setEnabledPhpImports(false); diff --git a/vendor/doctrine/annotations/docs/en/custom.rst b/vendor/doctrine/annotations/docs/en/custom.rst deleted file mode 100644 index 11fbe1a31e..0000000000 --- a/vendor/doctrine/annotations/docs/en/custom.rst +++ /dev/null @@ -1,443 +0,0 @@ -Custom Annotation Classes -========================= - -If you want to define your own annotations, you just have to group them -in a namespace and register this namespace in the ``AnnotationRegistry``. -Annotation classes have to contain a class-level docblock with the text -``@Annotation``: - -.. code-block:: php - - namespace MyCompany\Annotations; - - /** @Annotation */ - class Bar - { - // some code - } - -Inject annotation values ------------------------- - -The annotation parser checks if the annotation constructor has arguments, -if so then it will pass the value array, otherwise it will try to inject -values into public properties directly: - - -.. code-block:: php - - namespace MyCompany\Annotations; - - /** - * @Annotation - * - * Some Annotation using a constructor - */ - class Bar - { - private $foo; - - public function __construct(array $values) - { - $this->foo = $values['foo']; - } - } - - /** - * @Annotation - * - * Some Annotation without a constructor - */ - class Foo - { - public $bar; - } - -Optional: Constructors with Named Parameters --------------------------------------------- - -Starting with Annotations v1.11 a new annotation instantiation strategy -is available that aims at compatibility of Annotation classes with the PHP 8 -attribute feature. You need to declare a constructor with regular parameter -names that match the named arguments in the annotation syntax. - -To enable this feature, you can tag your annotation class with -``@NamedArgumentConstructor`` (available from v1.12) or implement the -``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface -(available from v1.11 and deprecated as of v1.12). -When using the ``@NamedArgumentConstructor`` tag, the first argument of the -constructor is considered as the default one. - - -Usage with the ``@NamedArgumentContrustor`` tag - -.. code-block:: php - - namespace MyCompany\Annotations; - - /** - * @Annotation - * @NamedArgumentConstructor - */ - class Bar implements NamedArgumentConstructorAnnotation - { - private $foo; - - public function __construct(string $foo) - { - $this->foo = $foo; - } - } - - /** Usable with @Bar(foo="baz") */ - /** Usable with @Bar("baz") */ - -In combination with PHP 8's constructor property promotion feature -you can simplify this to: - -.. code-block:: php - - namespace MyCompany\Annotations; - - /** - * @Annotation - * @NamedArgumentConstructor - */ - class Bar implements NamedArgumentConstructorAnnotation - { - public function __construct(private string $foo) {} - } - - -Usage with the -``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` -interface (v1.11, deprecated as of v1.12): -.. code-block:: php - - namespace MyCompany\Annotations; - - use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation; - - /** @Annotation */ - class Bar implements NamedArgumentConstructorAnnotation - { - private $foo; - - public function __construct(private string $foo) {} - } - - /** Usable with @Bar(foo="baz") */ - -Annotation Target ------------------ - -``@Target`` indicates the kinds of class elements to which an annotation -type is applicable. Then you could define one or more targets: - -- ``CLASS`` Allowed in class docblocks -- ``PROPERTY`` Allowed in property docblocks -- ``METHOD`` Allowed in the method docblocks -- ``FUNCTION`` Allowed in function dockblocks -- ``ALL`` Allowed in class, property, method and function docblocks -- ``ANNOTATION`` Allowed inside other annotations - -If the annotations is not allowed in the current context, an -``AnnotationException`` is thrown. - -.. code-block:: php - - namespace MyCompany\Annotations; - - /** - * @Annotation - * @Target({"METHOD","PROPERTY"}) - */ - class Bar - { - // some code - } - - /** - * @Annotation - * @Target("CLASS") - */ - class Foo - { - // some code - } - -Attribute types ---------------- - -The annotation parser checks the given parameters using the phpdoc -annotation ``@var``, The data type could be validated using the ``@var`` -annotation on the annotation properties or using the ``@Attributes`` and -``@Attribute`` annotations. - -If the data type does not match you get an ``AnnotationException`` - -.. code-block:: php - - namespace MyCompany\Annotations; - - /** - * @Annotation - * @Target({"METHOD","PROPERTY"}) - */ - class Bar - { - /** @var mixed */ - public $mixed; - - /** @var boolean */ - public $boolean; - - /** @var bool */ - public $bool; - - /** @var float */ - public $float; - - /** @var string */ - public $string; - - /** @var integer */ - public $integer; - - /** @var array */ - public $array; - - /** @var SomeAnnotationClass */ - public $annotation; - - /** @var array */ - public $arrayOfIntegers; - - /** @var array */ - public $arrayOfAnnotations; - } - - /** - * @Annotation - * @Target({"METHOD","PROPERTY"}) - * @Attributes({ - * @Attribute("stringProperty", type = "string"), - * @Attribute("annotProperty", type = "SomeAnnotationClass"), - * }) - */ - class Foo - { - public function __construct(array $values) - { - $this->stringProperty = $values['stringProperty']; - $this->annotProperty = $values['annotProperty']; - } - - // some code - } - -Annotation Required -------------------- - -``@Required`` indicates that the field must be specified when the -annotation is used. If it is not used you get an ``AnnotationException`` -stating that this value can not be null. - -Declaring a required field: - -.. code-block:: php - - /** - * @Annotation - * @Target("ALL") - */ - class Foo - { - /** @Required */ - public $requiredField; - } - -Usage: - -.. code-block:: php - - /** @Foo(requiredField="value") */ - public $direction; // Valid - - /** @Foo */ - public $direction; // Required field missing, throws an AnnotationException - - -Enumerated values ------------------ - -- An annotation property marked with ``@Enum`` is a field that accepts a - fixed set of scalar values. -- You should use ``@Enum`` fields any time you need to represent fixed - values. -- The annotation parser checks the given value and throws an - ``AnnotationException`` if the value does not match. - - -Declaring an enumerated property: - -.. code-block:: php - - /** - * @Annotation - * @Target("ALL") - */ - class Direction - { - /** - * @Enum({"NORTH", "SOUTH", "EAST", "WEST"}) - */ - public $value; - } - -Annotation usage: - -.. code-block:: php - - /** @Direction("NORTH") */ - public $direction; // Valid value - - /** @Direction("NORTHEAST") */ - public $direction; // Invalid value, throws an AnnotationException - - -Constants ---------- - -The use of constants and class constants is available on the annotations -parser. - -The following usages are allowed: - -.. code-block:: php - - namespace MyCompany\Entity; - - use MyCompany\Annotations\Foo; - use MyCompany\Annotations\Bar; - use MyCompany\Entity\SomeClass; - - /** - * @Foo(PHP_EOL) - * @Bar(Bar::FOO) - * @Foo({SomeClass::FOO, SomeClass::BAR}) - * @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE}) - */ - class User - { - } - - -Be careful with constants and the cache ! - -.. note:: - - The cached reader will not re-evaluate each time an annotation is - loaded from cache. When a constant is changed the cache must be - cleaned. - - -Usage ------ - -Using the library API is simple. Using the annotations described in the -previous section, you can now annotate other classes with your -annotations: - -.. code-block:: php - - namespace MyCompany\Entity; - - use MyCompany\Annotations\Foo; - use MyCompany\Annotations\Bar; - - /** - * @Foo(bar="foo") - * @Bar(foo="bar") - */ - class User - { - } - -Now we can write a script to get the annotations above: - -.. code-block:: php - - $reflClass = new ReflectionClass('MyCompany\Entity\User'); - $classAnnotations = $reader->getClassAnnotations($reflClass); - - foreach ($classAnnotations AS $annot) { - if ($annot instanceof \MyCompany\Annotations\Foo) { - echo $annot->bar; // prints "foo"; - } else if ($annot instanceof \MyCompany\Annotations\Bar) { - echo $annot->foo; // prints "bar"; - } - } - -You have a complete API for retrieving annotation class instances from a -class, property or method docblock: - - -Reader API -~~~~~~~~~~ - -Access all annotations of a class -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getClassAnnotations(\ReflectionClass $class); - -Access one annotation of a class -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getClassAnnotation(\ReflectionClass $class, $annotationName); - -Access all annotations of a method -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getMethodAnnotations(\ReflectionMethod $method); - -Access one annotation of a method -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getMethodAnnotation(\ReflectionMethod $method, $annotationName); - -Access all annotations of a property -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getPropertyAnnotations(\ReflectionProperty $property); - -Access one annotation of a property -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); - -Access all annotations of a function -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getFunctionAnnotations(\ReflectionFunction $property); - -Access one annotation of a function -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName); diff --git a/vendor/doctrine/annotations/docs/en/index.rst b/vendor/doctrine/annotations/docs/en/index.rst deleted file mode 100644 index 95476c3139..0000000000 --- a/vendor/doctrine/annotations/docs/en/index.rst +++ /dev/null @@ -1,101 +0,0 @@ -Introduction -============ - -Doctrine Annotations allows to implement custom annotation -functionality for PHP classes and functions. - -.. code-block:: php - - class Foo - { - /** - * @MyAnnotation(myProperty="value") - */ - private $bar; - } - -Annotations aren't implemented in PHP itself which is why this component -offers a way to use the PHP doc-blocks as a place for the well known -annotation syntax using the ``@`` char. - -Annotations in Doctrine are used for the ORM configuration to build the -class mapping, but it can be used in other projects for other purposes -too. - -Installation -============ - -You can install the Annotation component with composer: - -.. code-block:: - -   $ composer require doctrine/annotations - -Create an annotation class -========================== - -An annotation class is a representation of the later used annotation -configuration in classes. The annotation class of the previous example -looks like this: - -.. code-block:: php - - /** - * @Annotation - */ - final class MyAnnotation - { - public $myProperty; - } - -The annotation class is declared as an annotation by ``@Annotation``. - -:ref:`Read more about custom annotations. ` - -Reading annotations -=================== - -The access to the annotations happens by reflection of the class or function -containing them. There are multiple reader-classes implementing the -``Doctrine\Common\Annotations\Reader`` interface, that can access the -annotations of a class. A common one is -``Doctrine\Common\Annotations\AnnotationReader``: - -.. code-block:: php - - use Doctrine\Common\Annotations\AnnotationReader; - use Doctrine\Common\Annotations\AnnotationRegistry; - - // Deprecated and will be removed in 2.0 but currently needed - AnnotationRegistry::registerLoader('class_exists'); - - $reflectionClass = new ReflectionClass(Foo::class); - $property = $reflectionClass->getProperty('bar'); - - $reader = new AnnotationReader(); - $myAnnotation = $reader->getPropertyAnnotation( - $property, - MyAnnotation::class - ); - - echo $myAnnotation->myProperty; // result: "value" - -Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works -if you already have an autoloader configured (i.e. composer autoloader). -Otherwise, :ref:`please take a look to the other annotation autoload mechanisms `. - -A reader has multiple methods to access the annotations of a class or -function. - -:ref:`Read more about handling annotations. ` - -IDE Support ------------ - -Some IDEs already provide support for annotations: - -- Eclipse via the `Symfony2 Plugin `_ -- PhpStorm via the `PHP Annotations Plugin `_ or the `Symfony Plugin `_ - -.. _Read more about handling annotations.: annotations -.. _Read more about custom annotations.: custom diff --git a/vendor/doctrine/annotations/docs/en/sidebar.rst b/vendor/doctrine/annotations/docs/en/sidebar.rst deleted file mode 100644 index 6f5d13c46a..0000000000 --- a/vendor/doctrine/annotations/docs/en/sidebar.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. toctree:: - :depth: 3 - - index - annotations - custom diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php deleted file mode 100644 index 750270e425..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php +++ /dev/null @@ -1,59 +0,0 @@ - $data Key-value for properties to be defined in this class. - */ - final public function __construct(array $data) - { - foreach ($data as $key => $value) { - $this->$key = $value; - } - } - - /** - * Error handler for unknown property accessor in Annotation class. - * - * @param string $name Unknown property name. - * - * @throws BadMethodCallException - */ - public function __get($name) - { - throw new BadMethodCallException( - sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) - ); - } - - /** - * Error handler for unknown property mutator in Annotation class. - * - * @param string $name Unknown property name. - * @param mixed $value Property value. - * - * @throws BadMethodCallException - */ - public function __set($name, $value) - { - throw new BadMethodCallException( - sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) - ); - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php deleted file mode 100644 index b1f8514005..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php +++ /dev/null @@ -1,21 +0,0 @@ - */ - public $value; -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php deleted file mode 100644 index 35d6410b10..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php +++ /dev/null @@ -1,69 +0,0 @@ - */ - public $value; - - /** - * Literal target declaration. - * - * @var mixed[] - */ - public $literal; - - /** - * @throws InvalidArgumentException - * - * @phpstan-param array{literal?: mixed[], value: list} $values - */ - public function __construct(array $values) - { - if (! isset($values['literal'])) { - $values['literal'] = []; - } - - foreach ($values['value'] as $var) { - if (! is_scalar($var)) { - throw new InvalidArgumentException(sprintf( - '@Enum supports only scalar values "%s" given.', - is_object($var) ? get_class($var) : gettype($var) - )); - } - } - - foreach ($values['literal'] as $key => $var) { - if (! in_array($key, $values['value'])) { - throw new InvalidArgumentException(sprintf( - 'Undefined enumerator value "%s" for literal "%s".', - $key, - $var - )); - } - } - - $this->value = $values['value']; - $this->literal = $values['literal']; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php deleted file mode 100644 index ae60f7d5b0..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php +++ /dev/null @@ -1,43 +0,0 @@ - */ - public $names; - - /** - * @throws RuntimeException - * - * @phpstan-param array{value: string|list} $values - */ - public function __construct(array $values) - { - if (is_string($values['value'])) { - $values['value'] = [$values['value']]; - } - - if (! is_array($values['value'])) { - throw new RuntimeException(sprintf( - '@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', - json_encode($values['value']) - )); - } - - $this->names = $values['value']; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php deleted file mode 100644 index 1690601037..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php +++ /dev/null @@ -1,13 +0,0 @@ - */ - private static $map = [ - 'ALL' => self::TARGET_ALL, - 'CLASS' => self::TARGET_CLASS, - 'METHOD' => self::TARGET_METHOD, - 'PROPERTY' => self::TARGET_PROPERTY, - 'FUNCTION' => self::TARGET_FUNCTION, - 'ANNOTATION' => self::TARGET_ANNOTATION, - ]; - - /** @phpstan-var list */ - public $value; - - /** - * Targets as bitmask. - * - * @var int - */ - public $targets; - - /** - * Literal target declaration. - * - * @var string - */ - public $literal; - - /** - * @throws InvalidArgumentException - * - * @phpstan-param array{value?: string|list} $values - */ - public function __construct(array $values) - { - if (! isset($values['value'])) { - $values['value'] = null; - } - - if (is_string($values['value'])) { - $values['value'] = [$values['value']]; - } - - if (! is_array($values['value'])) { - throw new InvalidArgumentException( - sprintf( - '@Target expects either a string value, or an array of strings, "%s" given.', - is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) - ) - ); - } - - $bitmask = 0; - foreach ($values['value'] as $literal) { - if (! isset(self::$map[$literal])) { - throw new InvalidArgumentException( - sprintf( - 'Invalid Target "%s". Available targets: [%s]', - $literal, - implode(', ', array_keys(self::$map)) - ) - ); - } - - $bitmask |= self::$map[$literal]; - } - - $this->targets = $bitmask; - $this->value = $values['value']; - $this->literal = implode(', ', $this->value); - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php deleted file mode 100644 index b1ea64e6f5..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php +++ /dev/null @@ -1,171 +0,0 @@ - $available - */ - public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) - { - return new self(sprintf( - '[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.', - $attributeName, - $annotationName, - $context, - implode(', ', $available), - is_object($given) ? get_class($given) : $given - )); - } - - /** - * @return AnnotationException - */ - public static function optimizerPlusSaveComments() - { - return new self( - 'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.' - ); - } - - /** - * @return AnnotationException - */ - public static function optimizerPlusLoadComments() - { - return new self( - 'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.' - ); - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php deleted file mode 100644 index 1f538ee534..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ /dev/null @@ -1,389 +0,0 @@ - - */ - private static $globalImports = [ - 'ignoreannotation' => Annotation\IgnoreAnnotation::class, - ]; - - /** - * A list with annotations that are not causing exceptions when not resolved to an annotation class. - * - * The names are case sensitive. - * - * @var array - */ - private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST; - - /** - * A list with annotations that are not causing exceptions when not resolved to an annotation class. - * - * The names are case sensitive. - * - * @var array - */ - private static $globalIgnoredNamespaces = []; - - /** - * Add a new annotation to the globally ignored annotation names with regard to exception handling. - * - * @param string $name - */ - public static function addGlobalIgnoredName($name) - { - self::$globalIgnoredNames[$name] = true; - } - - /** - * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. - * - * @param string $namespace - */ - public static function addGlobalIgnoredNamespace($namespace) - { - self::$globalIgnoredNamespaces[$namespace] = true; - } - - /** - * Annotations parser. - * - * @var DocParser - */ - private $parser; - - /** - * Annotations parser used to collect parsing metadata. - * - * @var DocParser - */ - private $preParser; - - /** - * PHP parser used to collect imports. - * - * @var PhpParser - */ - private $phpParser; - - /** - * In-memory cache mechanism to store imported annotations per class. - * - * @psalm-var array<'class'|'function', array>> - */ - private $imports = []; - - /** - * In-memory cache mechanism to store ignored annotations per class. - * - * @psalm-var array<'class'|'function', array>> - */ - private $ignoredAnnotationNames = []; - - /** - * Initializes a new AnnotationReader. - * - * @throws AnnotationException - */ - public function __construct(?DocParser $parser = null) - { - if ( - extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || - ini_get('opcache.save_comments') === '0') - ) { - throw AnnotationException::optimizerPlusSaveComments(); - } - - if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) { - throw AnnotationException::optimizerPlusSaveComments(); - } - - // Make sure that the IgnoreAnnotation annotation is loaded - class_exists(IgnoreAnnotation::class); - - $this->parser = $parser ?: new DocParser(); - - $this->preParser = new DocParser(); - - $this->preParser->setImports(self::$globalImports); - $this->preParser->setIgnoreNotImportedAnnotations(true); - $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); - - $this->phpParser = new PhpParser(); - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - $this->parser->setTarget(Target::TARGET_CLASS); - $this->parser->setImports($this->getImports($class)); - $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); - $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); - - return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - $annotations = $this->getClassAnnotations($class); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - $context = 'property ' . $class->getName() . '::$' . $property->getName(); - - $this->parser->setTarget(Target::TARGET_PROPERTY); - $this->parser->setImports($this->getPropertyImports($property)); - $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); - $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); - - return $this->parser->parse($property->getDocComment(), $context); - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - $annotations = $this->getPropertyAnnotations($property); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; - - $this->parser->setTarget(Target::TARGET_METHOD); - $this->parser->setImports($this->getMethodImports($method)); - $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); - $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); - - return $this->parser->parse($method->getDocComment(), $context); - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - $annotations = $this->getMethodAnnotations($method); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * Gets the annotations applied to a function. - * - * @phpstan-return list An array of Annotations. - */ - public function getFunctionAnnotations(ReflectionFunction $function): array - { - $context = 'function ' . $function->getName(); - - $this->parser->setTarget(Target::TARGET_FUNCTION); - $this->parser->setImports($this->getImports($function)); - $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function)); - $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); - - return $this->parser->parse($function->getDocComment(), $context); - } - - /** - * Gets a function annotation. - * - * @return object|null The Annotation or NULL, if the requested annotation does not exist. - */ - public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName) - { - $annotations = $this->getFunctionAnnotations($function); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * Returns the ignored annotations for the given class or function. - * - * @param ReflectionClass|ReflectionFunction $reflection - * - * @return array - */ - private function getIgnoredAnnotationNames($reflection): array - { - $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; - $name = $reflection->getName(); - - if (isset($this->ignoredAnnotationNames[$type][$name])) { - return $this->ignoredAnnotationNames[$type][$name]; - } - - $this->collectParsingMetadata($reflection); - - return $this->ignoredAnnotationNames[$type][$name]; - } - - /** - * Retrieves imports for a class or a function. - * - * @param ReflectionClass|ReflectionFunction $reflection - * - * @return array - */ - private function getImports($reflection): array - { - $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; - $name = $reflection->getName(); - - if (isset($this->imports[$type][$name])) { - return $this->imports[$type][$name]; - } - - $this->collectParsingMetadata($reflection); - - return $this->imports[$type][$name]; - } - - /** - * Retrieves imports for methods. - * - * @return array - */ - private function getMethodImports(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - $classImports = $this->getImports($class); - - $traitImports = []; - - foreach ($class->getTraits() as $trait) { - if ( - ! $trait->hasMethod($method->getName()) - || $trait->getFileName() !== $method->getFileName() - ) { - continue; - } - - $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); - } - - return array_merge($classImports, $traitImports); - } - - /** - * Retrieves imports for properties. - * - * @return array - */ - private function getPropertyImports(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - $classImports = $this->getImports($class); - - $traitImports = []; - - foreach ($class->getTraits() as $trait) { - if (! $trait->hasProperty($property->getName())) { - continue; - } - - $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); - } - - return array_merge($classImports, $traitImports); - } - - /** - * Collects parsing metadata for a given class or function. - * - * @param ReflectionClass|ReflectionFunction $reflection - */ - private function collectParsingMetadata($reflection): void - { - $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; - $name = $reflection->getName(); - - $ignoredAnnotationNames = self::$globalIgnoredNames; - $annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name); - - foreach ($annotations as $annotation) { - if (! ($annotation instanceof IgnoreAnnotation)) { - continue; - } - - foreach ($annotation->names as $annot) { - $ignoredAnnotationNames[$annot] = true; - } - } - - $this->imports[$type][$name] = array_merge( - self::$globalImports, - $this->phpParser->parseUseStatements($reflection), - [ - '__NAMESPACE__' => $reflection->getNamespaceName(), - 'self' => $name, - ] - ); - - $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php deleted file mode 100644 index 259d497ddd..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php +++ /dev/null @@ -1,190 +0,0 @@ -|null $dirs - */ - public static function registerAutoloadNamespace(string $namespace, $dirs = null): void - { - self::$autoloadNamespaces[$namespace] = $dirs; - } - - /** - * Registers multiple namespaces. - * - * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - * - * @param string[][]|string[]|null[] $namespaces indexed by namespace name - */ - public static function registerAutoloadNamespaces(array $namespaces): void - { - self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); - } - - /** - * Registers an autoloading callable for annotations, much like spl_autoload_register(). - * - * NOTE: These class loaders HAVE to be silent when a class was not found! - * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - */ - public static function registerLoader(callable $callable): void - { - // Reset our static cache now that we have a new loader to work with - self::$failedToAutoload = []; - self::$loaders[] = $callable; - } - - /** - * Registers an autoloading callable for annotations, if it is not already registered - * - * @deprecated This method is deprecated and will be removed in - * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. - */ - public static function registerUniqueLoader(callable $callable): void - { - if (in_array($callable, self::$loaders, true)) { - return; - } - - self::registerLoader($callable); - } - - /** - * Autoloads an annotation class silently. - */ - public static function loadAnnotationClass(string $class): bool - { - if (class_exists($class, false)) { - return true; - } - - if (array_key_exists($class, self::$failedToAutoload)) { - return false; - } - - foreach (self::$autoloadNamespaces as $namespace => $dirs) { - if (strpos($class, $namespace) !== 0) { - continue; - } - - $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; - - if ($dirs === null) { - $path = stream_resolve_include_path($file); - if ($path) { - require $path; - - return true; - } - } else { - foreach ((array) $dirs as $dir) { - if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { - require $dir . DIRECTORY_SEPARATOR . $file; - - return true; - } - } - } - } - - foreach (self::$loaders as $loader) { - if ($loader($class) === true) { - return true; - } - } - - if ( - self::$loaders === [] && - self::$autoloadNamespaces === [] && - self::$registerFileUsed === false && - class_exists($class) - ) { - return true; - } - - self::$failedToAutoload[$class] = null; - - return false; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php deleted file mode 100644 index c036b2dab5..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php +++ /dev/null @@ -1,268 +0,0 @@ -> */ - private $loadedAnnotations = []; - - /** @var int[] */ - private $loadedFilemtimes = []; - - /** - * @param bool $debug - */ - public function __construct(Reader $reader, Cache $cache, $debug = false) - { - $this->delegate = $reader; - $this->cache = $cache; - $this->debug = (bool) $debug; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - $cacheKey = $class->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class); - if ($annots === false) { - $annots = $this->delegate->getClassAnnotations($class); - $this->saveToCache($cacheKey, $annots); - } - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - foreach ($this->getClassAnnotations($class) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - $cacheKey = $class->getName() . '$' . $property->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class); - if ($annots === false) { - $annots = $this->delegate->getPropertyAnnotations($property); - $this->saveToCache($cacheKey, $annots); - } - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - foreach ($this->getPropertyAnnotations($property) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - $cacheKey = $class->getName() . '#' . $method->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class); - if ($annots === false) { - $annots = $this->delegate->getMethodAnnotations($method); - $this->saveToCache($cacheKey, $annots); - } - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - foreach ($this->getMethodAnnotations($method) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * Clears loaded annotations. - * - * @return void - */ - public function clearLoadedAnnotations() - { - $this->loadedAnnotations = []; - $this->loadedFilemtimes = []; - } - - /** - * Fetches a value from the cache. - * - * @param string $cacheKey The cache key. - * - * @return mixed The cached value or false when the value is not in cache. - */ - private function fetchFromCache($cacheKey, ReflectionClass $class) - { - $data = $this->cache->fetch($cacheKey); - if ($data !== false) { - if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { - return $data; - } - } - - return false; - } - - /** - * Saves a value to the cache. - * - * @param string $cacheKey The cache key. - * @param mixed $value The value. - * - * @return void - */ - private function saveToCache($cacheKey, $value) - { - $this->cache->save($cacheKey, $value); - if (! $this->debug) { - return; - } - - $this->cache->save('[C]' . $cacheKey, time()); - } - - /** - * Checks if the cache is fresh. - * - * @param string $cacheKey - * - * @return bool - */ - private function isCacheFresh($cacheKey, ReflectionClass $class) - { - $lastModification = $this->getLastModification($class); - if ($lastModification === 0) { - return true; - } - - return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; - } - - /** - * Returns the time the class was last modified, testing traits and parents - */ - private function getLastModification(ReflectionClass $class): int - { - $filename = $class->getFileName(); - - if (isset($this->loadedFilemtimes[$filename])) { - return $this->loadedFilemtimes[$filename]; - } - - $parent = $class->getParentClass(); - - $lastModification = max(array_merge( - [$filename ? filemtime($filename) : 0], - array_map(function (ReflectionClass $reflectionTrait): int { - return $this->getTraitLastModificationTime($reflectionTrait); - }, $class->getTraits()), - array_map(function (ReflectionClass $class): int { - return $this->getLastModification($class); - }, $class->getInterfaces()), - $parent ? [$this->getLastModification($parent)] : [] - )); - - assert($lastModification !== false); - - return $this->loadedFilemtimes[$filename] = $lastModification; - } - - private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int - { - $fileName = $reflectionTrait->getFileName(); - - if (isset($this->loadedFilemtimes[$fileName])) { - return $this->loadedFilemtimes[$fileName]; - } - - $lastModificationTime = max(array_merge( - [$fileName ? filemtime($fileName) : 0], - array_map(function (ReflectionClass $reflectionTrait): int { - return $this->getTraitLastModificationTime($reflectionTrait); - }, $reflectionTrait->getTraits()) - )); - - assert($lastModificationTime !== false); - - return $this->loadedFilemtimes[$fileName] = $lastModificationTime; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php deleted file mode 100644 index f6567c512d..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php +++ /dev/null @@ -1,129 +0,0 @@ -= 100 - public const T_IDENTIFIER = 100; - public const T_AT = 101; - public const T_CLOSE_CURLY_BRACES = 102; - public const T_CLOSE_PARENTHESIS = 103; - public const T_COMMA = 104; - public const T_EQUALS = 105; - public const T_FALSE = 106; - public const T_NAMESPACE_SEPARATOR = 107; - public const T_OPEN_CURLY_BRACES = 108; - public const T_OPEN_PARENTHESIS = 109; - public const T_TRUE = 110; - public const T_NULL = 111; - public const T_COLON = 112; - public const T_MINUS = 113; - - /** @var array */ - protected $noCase = [ - '@' => self::T_AT, - ',' => self::T_COMMA, - '(' => self::T_OPEN_PARENTHESIS, - ')' => self::T_CLOSE_PARENTHESIS, - '{' => self::T_OPEN_CURLY_BRACES, - '}' => self::T_CLOSE_CURLY_BRACES, - '=' => self::T_EQUALS, - ':' => self::T_COLON, - '-' => self::T_MINUS, - '\\' => self::T_NAMESPACE_SEPARATOR, - ]; - - /** @var array */ - protected $withCase = [ - 'true' => self::T_TRUE, - 'false' => self::T_FALSE, - 'null' => self::T_NULL, - ]; - - /** - * Whether the next token starts immediately, or if there were - * non-captured symbols before that - */ - public function nextTokenIsAdjacent(): bool - { - return $this->token === null - || ($this->lookahead !== null - && ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value'])); - } - - /** - * {@inheritdoc} - */ - protected function getCatchablePatterns() - { - return [ - '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', - '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', - '"(?:""|[^"])*+"', - ]; - } - - /** - * {@inheritdoc} - */ - protected function getNonCatchablePatterns() - { - return ['\s+', '\*+', '(.)']; - } - - /** - * {@inheritdoc} - */ - protected function getType(&$value) - { - $type = self::T_NONE; - - if ($value[0] === '"') { - $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); - - return self::T_STRING; - } - - if (isset($this->noCase[$value])) { - return $this->noCase[$value]; - } - - if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { - return self::T_IDENTIFIER; - } - - $lowerValue = strtolower($value); - - if (isset($this->withCase[$lowerValue])) { - return $this->withCase[$lowerValue]; - } - - // Checking numeric value - if (is_numeric($value)) { - return strpos($value, '.') !== false || stripos($value, 'e') !== false - ? self::T_FLOAT : self::T_INTEGER; - } - - return $type; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php deleted file mode 100644 index ae530c50f5..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php +++ /dev/null @@ -1,1459 +0,0 @@ - - */ - private static $classIdentifiers = [ - DocLexer::T_IDENTIFIER, - DocLexer::T_TRUE, - DocLexer::T_FALSE, - DocLexer::T_NULL, - ]; - - /** - * The lexer. - * - * @var DocLexer - */ - private $lexer; - - /** - * Current target context. - * - * @var int - */ - private $target; - - /** - * Doc parser used to collect annotation target. - * - * @var DocParser - */ - private static $metadataParser; - - /** - * Flag to control if the current annotation is nested or not. - * - * @var bool - */ - private $isNestedAnnotation = false; - - /** - * Hashmap containing all use-statements that are to be used when parsing - * the given doc block. - * - * @var array - */ - private $imports = []; - - /** - * This hashmap is used internally to cache results of class_exists() - * look-ups. - * - * @var array - */ - private $classExists = []; - - /** - * Whether annotations that have not been imported should be ignored. - * - * @var bool - */ - private $ignoreNotImportedAnnotations = false; - - /** - * An array of default namespaces if operating in simple mode. - * - * @var string[] - */ - private $namespaces = []; - - /** - * A list with annotations that are not causing exceptions when not resolved to an annotation class. - * - * The names must be the raw names as used in the class, not the fully qualified - * - * @var bool[] indexed by annotation name - */ - private $ignoredAnnotationNames = []; - - /** - * A list with annotations in namespaced format - * that are not causing exceptions when not resolved to an annotation class. - * - * @var bool[] indexed by namespace name - */ - private $ignoredAnnotationNamespaces = []; - - /** @var string */ - private $context = ''; - - /** - * Hash-map for caching annotation metadata. - * - * @var array - */ - private static $annotationMetadata = [ - Annotation\Target::class => [ - 'is_annotation' => true, - 'has_constructor' => true, - 'has_named_argument_constructor' => false, - 'properties' => [], - 'targets_literal' => 'ANNOTATION_CLASS', - 'targets' => Target::TARGET_CLASS, - 'default_property' => 'value', - 'attribute_types' => [ - 'value' => [ - 'required' => false, - 'type' => 'array', - 'array_type' => 'string', - 'value' => 'array', - ], - ], - ], - Annotation\Attribute::class => [ - 'is_annotation' => true, - 'has_constructor' => false, - 'has_named_argument_constructor' => false, - 'targets_literal' => 'ANNOTATION_ANNOTATION', - 'targets' => Target::TARGET_ANNOTATION, - 'default_property' => 'name', - 'properties' => [ - 'name' => 'name', - 'type' => 'type', - 'required' => 'required', - ], - 'attribute_types' => [ - 'value' => [ - 'required' => true, - 'type' => 'string', - 'value' => 'string', - ], - 'type' => [ - 'required' => true, - 'type' => 'string', - 'value' => 'string', - ], - 'required' => [ - 'required' => false, - 'type' => 'boolean', - 'value' => 'boolean', - ], - ], - ], - Annotation\Attributes::class => [ - 'is_annotation' => true, - 'has_constructor' => false, - 'has_named_argument_constructor' => false, - 'targets_literal' => 'ANNOTATION_CLASS', - 'targets' => Target::TARGET_CLASS, - 'default_property' => 'value', - 'properties' => ['value' => 'value'], - 'attribute_types' => [ - 'value' => [ - 'type' => 'array', - 'required' => true, - 'array_type' => Annotation\Attribute::class, - 'value' => 'array<' . Annotation\Attribute::class . '>', - ], - ], - ], - Annotation\Enum::class => [ - 'is_annotation' => true, - 'has_constructor' => true, - 'has_named_argument_constructor' => false, - 'targets_literal' => 'ANNOTATION_PROPERTY', - 'targets' => Target::TARGET_PROPERTY, - 'default_property' => 'value', - 'properties' => ['value' => 'value'], - 'attribute_types' => [ - 'value' => [ - 'type' => 'array', - 'required' => true, - ], - 'literal' => [ - 'type' => 'array', - 'required' => false, - ], - ], - ], - Annotation\NamedArgumentConstructor::class => [ - 'is_annotation' => true, - 'has_constructor' => false, - 'has_named_argument_constructor' => false, - 'targets_literal' => 'ANNOTATION_CLASS', - 'targets' => Target::TARGET_CLASS, - 'default_property' => null, - 'properties' => [], - 'attribute_types' => [], - ], - ]; - - /** - * Hash-map for handle types declaration. - * - * @var array - */ - private static $typeMap = [ - 'float' => 'double', - 'bool' => 'boolean', - // allow uppercase Boolean in honor of George Boole - 'Boolean' => 'boolean', - 'int' => 'integer', - ]; - - /** - * Constructs a new DocParser. - */ - public function __construct() - { - $this->lexer = new DocLexer(); - } - - /** - * Sets the annotation names that are ignored during the parsing process. - * - * The names are supposed to be the raw names as used in the class, not the - * fully qualified class names. - * - * @param bool[] $names indexed by annotation name - * - * @return void - */ - public function setIgnoredAnnotationNames(array $names) - { - $this->ignoredAnnotationNames = $names; - } - - /** - * Sets the annotation namespaces that are ignored during the parsing process. - * - * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name - * - * @return void - */ - public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) - { - $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; - } - - /** - * Sets ignore on not-imported annotations. - * - * @param bool $bool - * - * @return void - */ - public function setIgnoreNotImportedAnnotations($bool) - { - $this->ignoreNotImportedAnnotations = (bool) $bool; - } - - /** - * Sets the default namespaces. - * - * @param string $namespace - * - * @return void - * - * @throws RuntimeException - */ - public function addNamespace($namespace) - { - if ($this->imports) { - throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); - } - - $this->namespaces[] = $namespace; - } - - /** - * Sets the imports. - * - * @param array $imports - * - * @return void - * - * @throws RuntimeException - */ - public function setImports(array $imports) - { - if ($this->namespaces) { - throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); - } - - $this->imports = $imports; - } - - /** - * Sets current target context as bitmask. - * - * @param int $target - * - * @return void - */ - public function setTarget($target) - { - $this->target = $target; - } - - /** - * Parses the given docblock string for annotations. - * - * @param string $input The docblock string to parse. - * @param string $context The parsing context. - * - * @throws AnnotationException - * @throws ReflectionException - * - * @phpstan-return list Array of annotations. If no annotations are found, an empty array is returned. - */ - public function parse($input, $context = '') - { - $pos = $this->findInitialTokenPosition($input); - if ($pos === null) { - return []; - } - - $this->context = $context; - - $this->lexer->setInput(trim(substr($input, $pos), '* /')); - $this->lexer->moveNext(); - - return $this->Annotations(); - } - - /** - * Finds the first valid annotation - * - * @param string $input The docblock string to parse - */ - private function findInitialTokenPosition($input): ?int - { - $pos = 0; - - // search for first valid annotation - while (($pos = strpos($input, '@', $pos)) !== false) { - $preceding = substr($input, $pos - 1, 1); - - // if the @ is preceded by a space, a tab or * it is valid - if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { - return $pos; - } - - $pos++; - } - - return null; - } - - /** - * Attempts to match the given token with the current lookahead token. - * If they match, updates the lookahead token; otherwise raises a syntax error. - * - * @param int $token Type of token. - * - * @return bool True if tokens match; false otherwise. - * - * @throws AnnotationException - */ - private function match(int $token): bool - { - if (! $this->lexer->isNextToken($token)) { - throw $this->syntaxError($this->lexer->getLiteral($token)); - } - - return $this->lexer->moveNext(); - } - - /** - * Attempts to match the current lookahead token with any of the given tokens. - * - * If any of them matches, this method updates the lookahead token; otherwise - * a syntax error is raised. - * - * @throws AnnotationException - * - * @phpstan-param list $tokens - */ - private function matchAny(array $tokens): bool - { - if (! $this->lexer->isNextTokenAny($tokens)) { - throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens))); - } - - return $this->lexer->moveNext(); - } - - /** - * Generates a new syntax error. - * - * @param string $expected Expected string. - * @param mixed[]|null $token Optional token. - */ - private function syntaxError(string $expected, ?array $token = null): AnnotationException - { - if ($token === null) { - $token = $this->lexer->lookahead; - } - - $message = sprintf('Expected %s, got ', $expected); - $message .= $this->lexer->lookahead === null - ? 'end of string' - : sprintf("'%s' at position %s", $token['value'], $token['position']); - - if (strlen($this->context)) { - $message .= ' in ' . $this->context; - } - - $message .= '.'; - - return AnnotationException::syntaxError($message); - } - - /** - * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism - * but uses the {@link AnnotationRegistry} to load classes. - * - * @param class-string $fqcn - */ - private function classExists(string $fqcn): bool - { - if (isset($this->classExists[$fqcn])) { - return $this->classExists[$fqcn]; - } - - // first check if the class already exists, maybe loaded through another AnnotationReader - if (class_exists($fqcn, false)) { - return $this->classExists[$fqcn] = true; - } - - // final check, does this class exist? - return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); - } - - /** - * Collects parsing metadata for a given annotation class - * - * @param class-string $name The annotation name - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function collectAnnotationMetadata(string $name): void - { - if (self::$metadataParser === null) { - self::$metadataParser = new self(); - - self::$metadataParser->setIgnoreNotImportedAnnotations(true); - self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); - self::$metadataParser->setImports([ - 'enum' => Enum::class, - 'target' => Target::class, - 'attribute' => Attribute::class, - 'attributes' => Attributes::class, - 'namedargumentconstructor' => NamedArgumentConstructor::class, - ]); - - // Make sure that annotations from metadata are loaded - class_exists(Enum::class); - class_exists(Target::class); - class_exists(Attribute::class); - class_exists(Attributes::class); - class_exists(NamedArgumentConstructor::class); - } - - $class = new ReflectionClass($name); - $docComment = $class->getDocComment(); - - // Sets default values for annotation metadata - $constructor = $class->getConstructor(); - $metadata = [ - 'default_property' => null, - 'has_constructor' => $constructor !== null && $constructor->getNumberOfParameters() > 0, - 'constructor_args' => [], - 'properties' => [], - 'property_types' => [], - 'attribute_types' => [], - 'targets_literal' => null, - 'targets' => Target::TARGET_ALL, - 'is_annotation' => strpos($docComment, '@Annotation') !== false, - ]; - - $metadata['has_named_argument_constructor'] = $metadata['has_constructor'] - && $class->implementsInterface(NamedArgumentConstructorAnnotation::class); - - // verify that the class is really meant to be an annotation - if ($metadata['is_annotation']) { - self::$metadataParser->setTarget(Target::TARGET_CLASS); - - foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { - if ($annotation instanceof Target) { - $metadata['targets'] = $annotation->targets; - $metadata['targets_literal'] = $annotation->literal; - - continue; - } - - if ($annotation instanceof NamedArgumentConstructor) { - $metadata['has_named_argument_constructor'] = $metadata['has_constructor']; - if ($metadata['has_named_argument_constructor']) { - // choose the first argument as the default property - $metadata['default_property'] = $constructor->getParameters()[0]->getName(); - } - } - - if (! ($annotation instanceof Attributes)) { - continue; - } - - foreach ($annotation->value as $attribute) { - $this->collectAttributeTypeMetadata($metadata, $attribute); - } - } - - // if not has a constructor will inject values into public properties - if ($metadata['has_constructor'] === false) { - // collect all public properties - foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { - $metadata['properties'][$property->name] = $property->name; - - $propertyComment = $property->getDocComment(); - if ($propertyComment === false) { - continue; - } - - $attribute = new Attribute(); - - $attribute->required = (strpos($propertyComment, '@Required') !== false); - $attribute->name = $property->name; - $attribute->type = (strpos($propertyComment, '@var') !== false && - preg_match('/@var\s+([^\s]+)/', $propertyComment, $matches)) - ? $matches[1] - : 'mixed'; - - $this->collectAttributeTypeMetadata($metadata, $attribute); - - // checks if the property has @Enum - if (strpos($propertyComment, '@Enum') === false) { - continue; - } - - $context = 'property ' . $class->name . '::$' . $property->name; - - self::$metadataParser->setTarget(Target::TARGET_PROPERTY); - - foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { - if (! $annotation instanceof Enum) { - continue; - } - - $metadata['enum'][$property->name]['value'] = $annotation->value; - $metadata['enum'][$property->name]['literal'] = (! empty($annotation->literal)) - ? $annotation->literal - : $annotation->value; - } - } - - // choose the first property as default property - $metadata['default_property'] = reset($metadata['properties']); - } elseif ($metadata['has_named_argument_constructor']) { - foreach ($constructor->getParameters() as $parameter) { - $metadata['constructor_args'][$parameter->getName()] = [ - 'position' => $parameter->getPosition(), - 'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null, - ]; - } - } - } - - self::$annotationMetadata[$name] = $metadata; - } - - /** - * Collects parsing metadata for a given attribute. - * - * @param mixed[] $metadata - */ - private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute): void - { - // handle internal type declaration - $type = self::$typeMap[$attribute->type] ?? $attribute->type; - - // handle the case if the property type is mixed - if ($type === 'mixed') { - return; - } - - // Evaluate type - $pos = strpos($type, '<'); - if ($pos !== false) { - // Checks if the property has array - $arrayType = substr($type, $pos + 1, -1); - $type = 'array'; - - if (isset(self::$typeMap[$arrayType])) { - $arrayType = self::$typeMap[$arrayType]; - } - - $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; - } else { - // Checks if the property has type[] - $pos = strrpos($type, '['); - if ($pos !== false) { - $arrayType = substr($type, 0, $pos); - $type = 'array'; - - if (isset(self::$typeMap[$arrayType])) { - $arrayType = self::$typeMap[$arrayType]; - } - - $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; - } - } - - $metadata['attribute_types'][$attribute->name]['type'] = $type; - $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; - $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; - } - - /** - * Annotations ::= Annotation {[ "*" ]* [Annotation]}* - * - * @throws AnnotationException - * @throws ReflectionException - * - * @phpstan-return list - */ - private function Annotations(): array - { - $annotations = []; - - while ($this->lexer->lookahead !== null) { - if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) { - $this->lexer->moveNext(); - continue; - } - - // make sure the @ is preceded by non-catchable pattern - if ( - $this->lexer->token !== null && - $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen( - $this->lexer->token['value'] - ) - ) { - $this->lexer->moveNext(); - continue; - } - - // make sure the @ is followed by either a namespace separator, or - // an identifier token - $peek = $this->lexer->glimpse(); - if ( - ($peek === null) - || ($peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && ! in_array( - $peek['type'], - self::$classIdentifiers, - true - )) - || $peek['position'] !== $this->lexer->lookahead['position'] + 1 - ) { - $this->lexer->moveNext(); - continue; - } - - $this->isNestedAnnotation = false; - $annot = $this->Annotation(); - if ($annot === false) { - continue; - } - - $annotations[] = $annot; - } - - return $annotations; - } - - /** - * Annotation ::= "@" AnnotationName MethodCall - * AnnotationName ::= QualifiedName | SimpleName - * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName - * NameSpacePart ::= identifier | null | false | true - * SimpleName ::= identifier | null | false | true - * - * @return object|false False if it is not a valid annotation. - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function Annotation() - { - $this->match(DocLexer::T_AT); - - // check if we have an annotation - $name = $this->Identifier(); - - if ( - $this->lexer->isNextToken(DocLexer::T_MINUS) - && $this->lexer->nextTokenIsAdjacent() - ) { - // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded - return false; - } - - // only process names which are not fully qualified, yet - // fully qualified names must start with a \ - $originalName = $name; - - if ($name[0] !== '\\') { - $pos = strpos($name, '\\'); - $alias = ($pos === false) ? $name : substr($name, 0, $pos); - $found = false; - $loweredAlias = strtolower($alias); - - if ($this->namespaces) { - foreach ($this->namespaces as $namespace) { - if ($this->classExists($namespace . '\\' . $name)) { - $name = $namespace . '\\' . $name; - $found = true; - break; - } - } - } elseif (isset($this->imports[$loweredAlias])) { - $namespace = ltrim($this->imports[$loweredAlias], '\\'); - $name = ($pos !== false) - ? $namespace . substr($name, $pos) - : $namespace; - $found = $this->classExists($name); - } elseif ( - ! isset($this->ignoredAnnotationNames[$name]) - && isset($this->imports['__NAMESPACE__']) - && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) - ) { - $name = $this->imports['__NAMESPACE__'] . '\\' . $name; - $found = true; - } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { - $found = true; - } - - if (! $found) { - if ($this->isIgnoredAnnotation($name)) { - return false; - } - - throw AnnotationException::semanticalError(sprintf( - <<<'EXCEPTION' -The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation? -EXCEPTION - , - $name, - $this->context - )); - } - } - - $name = ltrim($name, '\\'); - - if (! $this->classExists($name)) { - throw AnnotationException::semanticalError(sprintf( - 'The annotation "@%s" in %s does not exist, or could not be auto-loaded.', - $name, - $this->context - )); - } - - // at this point, $name contains the fully qualified class name of the - // annotation, and it is also guaranteed that this class exists, and - // that it is loaded - - // collects the metadata annotation only if there is not yet - if (! isset(self::$annotationMetadata[$name])) { - $this->collectAnnotationMetadata($name); - } - - // verify that the class is really meant to be an annotation and not just any ordinary class - if (self::$annotationMetadata[$name]['is_annotation'] === false) { - if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) { - return false; - } - - throw AnnotationException::semanticalError(sprintf( - <<<'EXCEPTION' -The class "%s" is not annotated with @Annotation. -Are you sure this class can be used as annotation? -If so, then you need to add @Annotation to the _class_ doc comment of "%s". -If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s. -EXCEPTION - , - $name, - $name, - $originalName, - $this->context - )); - } - - //if target is nested annotation - $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; - - // Next will be nested - $this->isNestedAnnotation = true; - - //if annotation does not support current target - if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) { - throw AnnotationException::semanticalError( - sprintf( - <<<'EXCEPTION' -Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s. -EXCEPTION - , - $originalName, - $this->context, - self::$annotationMetadata[$name]['targets_literal'] - ) - ); - } - - $arguments = $this->MethodCall(); - $values = $this->resolvePositionalValues($arguments, $name); - - if (isset(self::$annotationMetadata[$name]['enum'])) { - // checks all declared attributes - foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { - // checks if the attribute is a valid enumerator - if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { - throw AnnotationException::enumeratorError( - $property, - $name, - $this->context, - $enum['literal'], - $values[$property] - ); - } - } - } - - // checks all declared attributes - foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { - if ( - $property === self::$annotationMetadata[$name]['default_property'] - && ! isset($values[$property]) && isset($values['value']) - ) { - $property = 'value'; - } - - // handle a not given attribute or null value - if (! isset($values[$property])) { - if ($type['required']) { - throw AnnotationException::requiredError( - $property, - $originalName, - $this->context, - 'a(n) ' . $type['value'] - ); - } - - continue; - } - - if ($type['type'] === 'array') { - // handle the case of a single value - if (! is_array($values[$property])) { - $values[$property] = [$values[$property]]; - } - - // checks if the attribute has array type declaration, such as "array" - if (isset($type['array_type'])) { - foreach ($values[$property] as $item) { - if (gettype($item) !== $type['array_type'] && ! $item instanceof $type['array_type']) { - throw AnnotationException::attributeTypeError( - $property, - $originalName, - $this->context, - 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's', - $item - ); - } - } - } - } elseif (gettype($values[$property]) !== $type['type'] && ! $values[$property] instanceof $type['type']) { - throw AnnotationException::attributeTypeError( - $property, - $originalName, - $this->context, - 'a(n) ' . $type['value'], - $values[$property] - ); - } - } - - if (self::$annotationMetadata[$name]['has_named_argument_constructor']) { - if (PHP_VERSION_ID >= 80000) { - return new $name(...$values); - } - - $positionalValues = []; - foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { - $positionalValues[$parameter['position']] = $parameter['default']; - } - - foreach ($values as $property => $value) { - if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { - throw AnnotationException::creationError(sprintf( - <<<'EXCEPTION' -The annotation @%s declared on %s does not have a property named "%s" -that can be set through its named arguments constructor. -Available named arguments: %s -EXCEPTION - , - $originalName, - $this->context, - $property, - implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])) - )); - } - - $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value; - } - - return new $name(...$positionalValues); - } - - // check if the annotation expects values via the constructor, - // or directly injected into public properties - if (self::$annotationMetadata[$name]['has_constructor'] === true) { - return new $name($values); - } - - $instance = new $name(); - - foreach ($values as $property => $value) { - if (! isset(self::$annotationMetadata[$name]['properties'][$property])) { - if ($property !== 'value') { - throw AnnotationException::creationError(sprintf( - <<<'EXCEPTION' -The annotation @%s declared on %s does not have a property named "%s". -Available properties: %s -EXCEPTION - , - $originalName, - $this->context, - $property, - implode(', ', self::$annotationMetadata[$name]['properties']) - )); - } - - // handle the case if the property has no annotations - $property = self::$annotationMetadata[$name]['default_property']; - if (! $property) { - throw AnnotationException::creationError(sprintf( - 'The annotation @%s declared on %s does not accept any values, but got %s.', - $originalName, - $this->context, - json_encode($values) - )); - } - } - - $instance->{$property} = $value; - } - - return $instance; - } - - /** - * MethodCall ::= ["(" [Values] ")"] - * - * @return mixed[] - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function MethodCall(): array - { - $values = []; - - if (! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { - return $values; - } - - $this->match(DocLexer::T_OPEN_PARENTHESIS); - - if (! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { - $values = $this->Values(); - } - - $this->match(DocLexer::T_CLOSE_PARENTHESIS); - - return $values; - } - - /** - * Values ::= Array | Value {"," Value}* [","] - * - * @return mixed[] - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function Values(): array - { - $values = [$this->Value()]; - - while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { - $this->match(DocLexer::T_COMMA); - - if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { - break; - } - - $token = $this->lexer->lookahead; - $value = $this->Value(); - - $values[] = $value; - } - - $namedArguments = []; - $positionalArguments = []; - foreach ($values as $k => $value) { - if (is_object($value) && $value instanceof stdClass) { - $namedArguments[$value->name] = $value->value; - } else { - $positionalArguments[$k] = $value; - } - } - - return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments]; - } - - /** - * Constant ::= integer | string | float | boolean - * - * @return mixed - * - * @throws AnnotationException - */ - private function Constant() - { - $identifier = $this->Identifier(); - - if (! defined($identifier) && strpos($identifier, '::') !== false && $identifier[0] !== '\\') { - [$className, $const] = explode('::', $identifier); - - $pos = strpos($className, '\\'); - $alias = ($pos === false) ? $className : substr($className, 0, $pos); - $found = false; - $loweredAlias = strtolower($alias); - - switch (true) { - case ! empty($this->namespaces): - foreach ($this->namespaces as $ns) { - if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { - $className = $ns . '\\' . $className; - $found = true; - break; - } - } - - break; - - case isset($this->imports[$loweredAlias]): - $found = true; - $className = ($pos !== false) - ? $this->imports[$loweredAlias] . substr($className, $pos) - : $this->imports[$loweredAlias]; - break; - - default: - if (isset($this->imports['__NAMESPACE__'])) { - $ns = $this->imports['__NAMESPACE__']; - - if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { - $className = $ns . '\\' . $className; - $found = true; - } - } - - break; - } - - if ($found) { - $identifier = $className . '::' . $const; - } - } - - /** - * Checks if identifier ends with ::class and remove the leading backslash if it exists. - */ - if ( - $this->identifierEndsWithClassConstant($identifier) && - ! $this->identifierStartsWithBackslash($identifier) - ) { - return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier)); - } - - if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) { - return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1); - } - - if (! defined($identifier)) { - throw AnnotationException::semanticalErrorConstants($identifier, $this->context); - } - - return constant($identifier); - } - - private function identifierStartsWithBackslash(string $identifier): bool - { - return $identifier[0] === '\\'; - } - - private function identifierEndsWithClassConstant(string $identifier): bool - { - return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class'); - } - - /** - * @return int|false - */ - private function getClassConstantPositionInIdentifier(string $identifier) - { - return stripos($identifier, '::class'); - } - - /** - * Identifier ::= string - * - * @throws AnnotationException - */ - private function Identifier(): string - { - // check if we have an annotation - if (! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { - throw $this->syntaxError('namespace separator or identifier'); - } - - $this->lexer->moveNext(); - - $className = $this->lexer->token['value']; - - while ( - $this->lexer->lookahead !== null && - $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + - strlen($this->lexer->token['value'])) && - $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR) - ) { - $this->match(DocLexer::T_NAMESPACE_SEPARATOR); - $this->matchAny(self::$classIdentifiers); - - $className .= '\\' . $this->lexer->token['value']; - } - - return $className; - } - - /** - * Value ::= PlainValue | FieldAssignment - * - * @return mixed - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function Value() - { - $peek = $this->lexer->glimpse(); - - if ($peek['type'] === DocLexer::T_EQUALS) { - return $this->FieldAssignment(); - } - - return $this->PlainValue(); - } - - /** - * PlainValue ::= integer | string | float | boolean | Array | Annotation - * - * @return mixed - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function PlainValue() - { - if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { - return $this->Arrayx(); - } - - if ($this->lexer->isNextToken(DocLexer::T_AT)) { - return $this->Annotation(); - } - - if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { - return $this->Constant(); - } - - switch ($this->lexer->lookahead['type']) { - case DocLexer::T_STRING: - $this->match(DocLexer::T_STRING); - - return $this->lexer->token['value']; - - case DocLexer::T_INTEGER: - $this->match(DocLexer::T_INTEGER); - - return (int) $this->lexer->token['value']; - - case DocLexer::T_FLOAT: - $this->match(DocLexer::T_FLOAT); - - return (float) $this->lexer->token['value']; - - case DocLexer::T_TRUE: - $this->match(DocLexer::T_TRUE); - - return true; - - case DocLexer::T_FALSE: - $this->match(DocLexer::T_FALSE); - - return false; - - case DocLexer::T_NULL: - $this->match(DocLexer::T_NULL); - - return null; - - default: - throw $this->syntaxError('PlainValue'); - } - } - - /** - * FieldAssignment ::= FieldName "=" PlainValue - * FieldName ::= identifier - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function FieldAssignment(): stdClass - { - $this->match(DocLexer::T_IDENTIFIER); - $fieldName = $this->lexer->token['value']; - - $this->match(DocLexer::T_EQUALS); - - $item = new stdClass(); - $item->name = $fieldName; - $item->value = $this->PlainValue(); - - return $item; - } - - /** - * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" - * - * @return mixed[] - * - * @throws AnnotationException - * @throws ReflectionException - */ - private function Arrayx(): array - { - $array = $values = []; - - $this->match(DocLexer::T_OPEN_CURLY_BRACES); - - // If the array is empty, stop parsing and return. - if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { - $this->match(DocLexer::T_CLOSE_CURLY_BRACES); - - return $array; - } - - $values[] = $this->ArrayEntry(); - - while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { - $this->match(DocLexer::T_COMMA); - - // optional trailing comma - if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { - break; - } - - $values[] = $this->ArrayEntry(); - } - - $this->match(DocLexer::T_CLOSE_CURLY_BRACES); - - foreach ($values as $value) { - [$key, $val] = $value; - - if ($key !== null) { - $array[$key] = $val; - } else { - $array[] = $val; - } - } - - return $array; - } - - /** - * ArrayEntry ::= Value | KeyValuePair - * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant - * Key ::= string | integer | Constant - * - * @throws AnnotationException - * @throws ReflectionException - * - * @phpstan-return array{mixed, mixed} - */ - private function ArrayEntry(): array - { - $peek = $this->lexer->glimpse(); - - if ( - $peek['type'] === DocLexer::T_EQUALS - || $peek['type'] === DocLexer::T_COLON - ) { - if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { - $key = $this->Constant(); - } else { - $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]); - $key = $this->lexer->token['value']; - } - - $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]); - - return [$key, $this->PlainValue()]; - } - - return [null, $this->Value()]; - } - - /** - * Checks whether the given $name matches any ignored annotation name or namespace - */ - private function isIgnoredAnnotation(string $name): bool - { - if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { - return true; - } - - foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { - $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; - - if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) { - return true; - } - } - - return false; - } - - /** - * Resolve positional arguments (without name) to named ones - * - * @param array $arguments - * - * @return array - */ - private function resolvePositionalValues(array $arguments, string $name): array - { - $positionalArguments = $arguments['positional_arguments'] ?? []; - $values = $arguments['named_arguments'] ?? []; - - if ( - self::$annotationMetadata[$name]['has_named_argument_constructor'] - && self::$annotationMetadata[$name]['default_property'] !== null - ) { - // We must ensure that we don't have positional arguments after named ones - $positions = array_keys($positionalArguments); - $lastPosition = null; - foreach ($positions as $position) { - if ( - ($lastPosition === null && $position !== 0) || - ($lastPosition !== null && $position !== $lastPosition + 1) - ) { - throw $this->syntaxError('Positional arguments after named arguments is not allowed'); - } - - $lastPosition = $position; - } - - foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { - $position = $parameter['position']; - if (isset($values[$property]) || ! isset($positionalArguments[$position])) { - continue; - } - - $values[$property] = $positionalArguments[$position]; - } - } else { - if (count($positionalArguments) > 0 && ! isset($values['value'])) { - if (count($positionalArguments) === 1) { - $value = array_pop($positionalArguments); - } else { - $value = array_values($positionalArguments); - } - - $values['value'] = $value; - } - } - - return $values; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php deleted file mode 100644 index 6c6c22c3a0..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php +++ /dev/null @@ -1,315 +0,0 @@ -> */ - private $loadedAnnotations = []; - - /** @var array */ - private $classNameHashes = []; - - /** @var int */ - private $umask; - - /** - * @param string $cacheDir - * @param bool $debug - * @param int $umask - * - * @throws InvalidArgumentException - */ - public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) - { - if (! is_int($umask)) { - throw new InvalidArgumentException(sprintf( - 'The parameter umask must be an integer, was: %s', - gettype($umask) - )); - } - - $this->reader = $reader; - $this->umask = $umask; - - if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) { - throw new InvalidArgumentException(sprintf( - 'The directory "%s" does not exist and could not be created.', - $cacheDir - )); - } - - $this->dir = rtrim($cacheDir, '\\/'); - $this->debug = $debug; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - if (! isset($this->classNameHashes[$class->name])) { - $this->classNameHashes[$class->name] = sha1($class->name); - } - - $key = $this->classNameHashes[$class->name]; - - if (isset($this->loadedAnnotations[$key])) { - return $this->loadedAnnotations[$key]; - } - - $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; - if (! is_file($path)) { - $annot = $this->reader->getClassAnnotations($class); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - $filename = $class->getFilename(); - if ( - $this->debug - && $filename !== false - && filemtime($path) < filemtime($filename) - ) { - @unlink($path); - - $annot = $this->reader->getClassAnnotations($class); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - return $this->loadedAnnotations[$key] = include $path; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - if (! isset($this->classNameHashes[$class->name])) { - $this->classNameHashes[$class->name] = sha1($class->name); - } - - $key = $this->classNameHashes[$class->name] . '$' . $property->getName(); - - if (isset($this->loadedAnnotations[$key])) { - return $this->loadedAnnotations[$key]; - } - - $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; - if (! is_file($path)) { - $annot = $this->reader->getPropertyAnnotations($property); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - $filename = $class->getFilename(); - if ( - $this->debug - && $filename !== false - && filemtime($path) < filemtime($filename) - ) { - @unlink($path); - - $annot = $this->reader->getPropertyAnnotations($property); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - return $this->loadedAnnotations[$key] = include $path; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - if (! isset($this->classNameHashes[$class->name])) { - $this->classNameHashes[$class->name] = sha1($class->name); - } - - $key = $this->classNameHashes[$class->name] . '#' . $method->getName(); - - if (isset($this->loadedAnnotations[$key])) { - return $this->loadedAnnotations[$key]; - } - - $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; - if (! is_file($path)) { - $annot = $this->reader->getMethodAnnotations($method); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - $filename = $class->getFilename(); - if ( - $this->debug - && $filename !== false - && filemtime($path) < filemtime($filename) - ) { - @unlink($path); - - $annot = $this->reader->getMethodAnnotations($method); - $this->saveCacheFile($path, $annot); - - return $this->loadedAnnotations[$key] = $annot; - } - - return $this->loadedAnnotations[$key] = include $path; - } - - /** - * Saves the cache file. - * - * @param string $path - * @param mixed $data - * - * @return void - */ - private function saveCacheFile($path, $data) - { - if (! is_writable($this->dir)) { - throw new InvalidArgumentException(sprintf( - <<<'EXCEPTION' -The directory "%s" is not writable. Both the webserver and the console user need access. -You can manage access rights for multiple users with "chmod +a". -If your system does not support this, check out the acl package., -EXCEPTION - , - $this->dir - )); - } - - $tempfile = tempnam($this->dir, uniqid('', true)); - - if ($tempfile === false) { - throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); - } - - @chmod($tempfile, 0666 & (~$this->umask)); - - $written = file_put_contents( - $tempfile, - 'umask)); - - if (rename($tempfile, $path) === false) { - @unlink($tempfile); - - throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); - } - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - $annotations = $this->getClassAnnotations($class); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - $annotations = $this->getMethodAnnotations($method); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - $annotations = $this->getPropertyAnnotations($property); - - foreach ($annotations as $annotation) { - if ($annotation instanceof $annotationName) { - return $annotation; - } - } - - return null; - } - - /** - * Clears loaded annotations. - * - * @return void - */ - public function clearLoadedAnnotations() - { - $this->loadedAnnotations = []; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php deleted file mode 100644 index 2efeb1d221..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php +++ /dev/null @@ -1,177 +0,0 @@ - true, - 'Attribute' => true, - 'Attributes' => true, - /* Can we enable this? 'Enum' => true, */ - 'Required' => true, - 'Target' => true, - 'NamedArgumentConstructor' => true, - ]; - - private const WidelyUsedNonStandard = [ - 'fix' => true, - 'fixme' => true, - 'override' => true, - ]; - - private const PhpDocumentor1 = [ - 'abstract' => true, - 'access' => true, - 'code' => true, - 'deprec' => true, - 'endcode' => true, - 'exception' => true, - 'final' => true, - 'ingroup' => true, - 'inheritdoc' => true, - 'inheritDoc' => true, - 'magic' => true, - 'name' => true, - 'private' => true, - 'static' => true, - 'staticvar' => true, - 'staticVar' => true, - 'toc' => true, - 'tutorial' => true, - 'throw' => true, - ]; - - private const PhpDocumentor2 = [ - 'api' => true, - 'author' => true, - 'category' => true, - 'copyright' => true, - 'deprecated' => true, - 'example' => true, - 'filesource' => true, - 'global' => true, - 'ignore' => true, - /* Can we enable this? 'index' => true, */ - 'internal' => true, - 'license' => true, - 'link' => true, - 'method' => true, - 'package' => true, - 'param' => true, - 'property' => true, - 'property-read' => true, - 'property-write' => true, - 'return' => true, - 'see' => true, - 'since' => true, - 'source' => true, - 'subpackage' => true, - 'throws' => true, - 'todo' => true, - 'TODO' => true, - 'usedby' => true, - 'uses' => true, - 'var' => true, - 'version' => true, - ]; - - private const PHPUnit = [ - 'author' => true, - 'after' => true, - 'afterClass' => true, - 'backupGlobals' => true, - 'backupStaticAttributes' => true, - 'before' => true, - 'beforeClass' => true, - 'codeCoverageIgnore' => true, - 'codeCoverageIgnoreStart' => true, - 'codeCoverageIgnoreEnd' => true, - 'covers' => true, - 'coversDefaultClass' => true, - 'coversNothing' => true, - 'dataProvider' => true, - 'depends' => true, - 'doesNotPerformAssertions' => true, - 'expectedException' => true, - 'expectedExceptionCode' => true, - 'expectedExceptionMessage' => true, - 'expectedExceptionMessageRegExp' => true, - 'group' => true, - 'large' => true, - 'medium' => true, - 'preserveGlobalState' => true, - 'requires' => true, - 'runTestsInSeparateProcesses' => true, - 'runInSeparateProcess' => true, - 'small' => true, - 'test' => true, - 'testdox' => true, - 'testWith' => true, - 'ticket' => true, - 'uses' => true, - ]; - - private const PhpCheckStyle = ['SuppressWarnings' => true]; - - private const PhpStorm = ['noinspection' => true]; - - private const PEAR = ['package_version' => true]; - - private const PlainUML = [ - 'startuml' => true, - 'enduml' => true, - ]; - - private const Symfony = ['experimental' => true]; - - private const PhpCodeSniffer = [ - 'codingStandardsIgnoreStart' => true, - 'codingStandardsIgnoreEnd' => true, - ]; - - private const SlevomatCodingStandard = ['phpcsSuppress' => true]; - - private const Phan = ['suppress' => true]; - - private const Rector = ['noRector' => true]; - - private const StaticAnalysis = [ - // PHPStan, Psalm - 'extends' => true, - 'implements' => true, - 'template' => true, - 'use' => true, - - // Psalm - 'pure' => true, - 'immutable' => true, - ]; - - public const LIST = self::Reserved - + self::WidelyUsedNonStandard - + self::PhpDocumentor1 - + self::PhpDocumentor2 - + self::PHPUnit - + self::PhpCheckStyle - + self::PhpStorm - + self::PEAR - + self::PlainUML - + self::Symfony - + self::SlevomatCodingStandard - + self::PhpCodeSniffer - + self::Phan - + self::Rector - + self::StaticAnalysis; - - private function __construct() - { - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php deleted file mode 100644 index 42e70765db..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php +++ /dev/null @@ -1,100 +0,0 @@ -delegate = $reader; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - $annotations = []; - foreach ($this->delegate->getClassAnnotations($class) as $annot) { - $annotations[get_class($annot)] = $annot; - } - - return $annotations; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotation) - { - return $this->delegate->getClassAnnotation($class, $annotation); - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $annotations = []; - foreach ($this->delegate->getMethodAnnotations($method) as $annot) { - $annotations[get_class($annot)] = $annot; - } - - return $annotations; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotation) - { - return $this->delegate->getMethodAnnotation($method, $annotation); - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $annotations = []; - foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { - $annotations[get_class($annot)] = $annot; - } - - return $annotations; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotation) - { - return $this->delegate->getPropertyAnnotation($property, $annotation); - } - - /** - * Proxies all methods to the delegate. - * - * @param string $method - * @param mixed[] $args - * - * @return mixed - */ - public function __call($method, $args) - { - return call_user_func_array([$this->delegate, $method], $args); - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php deleted file mode 100644 index 8af224c0bf..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php +++ /dev/null @@ -1,14 +0,0 @@ -ReflectionClass object. - * - * @return array A list with use statements in the form (Alias => FQN). - */ - public function parseClass(ReflectionClass $class) - { - return $this->parseUseStatements($class); - } - - /** - * Parse a class or function for use statements. - * - * @param ReflectionClass|ReflectionFunction $reflection - * - * @psalm-return array a list with use statements in the form (Alias => FQN). - */ - public function parseUseStatements($reflection): array - { - if (method_exists($reflection, 'getUseStatements')) { - return $reflection->getUseStatements(); - } - - $filename = $reflection->getFileName(); - - if ($filename === false) { - return []; - } - - $content = $this->getFileContent($filename, $reflection->getStartLine()); - - if ($content === null) { - return []; - } - - $namespace = preg_quote($reflection->getNamespaceName()); - $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); - $tokenizer = new TokenParser('parseUseStatements($reflection->getNamespaceName()); - } - - /** - * Gets the content of the file right up to the given line number. - * - * @param string $filename The name of the file to load. - * @param int $lineNumber The number of lines to read from file. - * - * @return string|null The content of the file or null if the file does not exist. - */ - private function getFileContent($filename, $lineNumber) - { - if (! is_file($filename)) { - return null; - } - - $content = ''; - $lineCnt = 0; - $file = new SplFileObject($filename); - while (! $file->eof()) { - if ($lineCnt++ === $lineNumber) { - break; - } - - $content .= $file->fgets(); - } - - return $content; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php deleted file mode 100644 index a7099d5790..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php +++ /dev/null @@ -1,232 +0,0 @@ -> */ - private $loadedAnnotations = []; - - /** @var int[] */ - private $loadedFilemtimes = []; - - public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false) - { - $this->delegate = $reader; - $this->cache = $cache; - $this->debug = (bool) $debug; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - $cacheKey = $class->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class); - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - foreach ($this->getClassAnnotations($class) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - $class = $property->getDeclaringClass(); - $cacheKey = $class->getName() . '$' . $property->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property); - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - foreach ($this->getPropertyAnnotations($property) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - $class = $method->getDeclaringClass(); - $cacheKey = $class->getName() . '#' . $method->getName(); - - if (isset($this->loadedAnnotations[$cacheKey])) { - return $this->loadedAnnotations[$cacheKey]; - } - - $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method); - - return $this->loadedAnnotations[$cacheKey] = $annots; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - foreach ($this->getMethodAnnotations($method) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - public function clearLoadedAnnotations(): void - { - $this->loadedAnnotations = []; - $this->loadedFilemtimes = []; - } - - /** @return mixed[] */ - private function fetchFromCache( - string $cacheKey, - ReflectionClass $class, - string $method, - Reflector $reflector - ): array { - $cacheKey = rawurlencode($cacheKey); - - $item = $this->cache->getItem($cacheKey); - if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) { - $this->cache->save($item->set($this->delegate->{$method}($reflector))); - } - - return $item->get(); - } - - /** - * Used in debug mode to check if the cache is fresh. - * - * @return bool Returns true if the cache was fresh, or false if the class - * being read was modified since writing to the cache. - */ - private function refresh(string $cacheKey, ReflectionClass $class): bool - { - $lastModification = $this->getLastModification($class); - if ($lastModification === 0) { - return true; - } - - $item = $this->cache->getItem('[C]' . $cacheKey); - if ($item->isHit() && $item->get() >= $lastModification) { - return true; - } - - $this->cache->save($item->set(time())); - - return false; - } - - /** - * Returns the time the class was last modified, testing traits and parents - */ - private function getLastModification(ReflectionClass $class): int - { - $filename = $class->getFileName(); - - if (isset($this->loadedFilemtimes[$filename])) { - return $this->loadedFilemtimes[$filename]; - } - - $parent = $class->getParentClass(); - - $lastModification = max(array_merge( - [$filename ? filemtime($filename) : 0], - array_map(function (ReflectionClass $reflectionTrait): int { - return $this->getTraitLastModificationTime($reflectionTrait); - }, $class->getTraits()), - array_map(function (ReflectionClass $class): int { - return $this->getLastModification($class); - }, $class->getInterfaces()), - $parent ? [$this->getLastModification($parent)] : [] - )); - - assert($lastModification !== false); - - return $this->loadedFilemtimes[$filename] = $lastModification; - } - - private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int - { - $fileName = $reflectionTrait->getFileName(); - - if (isset($this->loadedFilemtimes[$fileName])) { - return $this->loadedFilemtimes[$fileName]; - } - - $lastModificationTime = max(array_merge( - [$fileName ? filemtime($fileName) : 0], - array_map(function (ReflectionClass $reflectionTrait): int { - return $this->getTraitLastModificationTime($reflectionTrait); - }, $reflectionTrait->getTraits()) - )); - - assert($lastModificationTime !== false); - - return $this->loadedFilemtimes[$fileName] = $lastModificationTime; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php deleted file mode 100644 index 0663ffda06..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php +++ /dev/null @@ -1,80 +0,0 @@ - An array of Annotations. - */ - public function getClassAnnotations(ReflectionClass $class); - - /** - * Gets a class annotation. - * - * @param ReflectionClass $class The ReflectionClass of the class from which - * the class annotations should be read. - * @param class-string $annotationName The name of the annotation. - * - * @return T|null The Annotation or NULL, if the requested annotation does not exist. - * - * @template T - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName); - - /** - * Gets the annotations applied to a method. - * - * @param ReflectionMethod $method The ReflectionMethod of the method from which - * the annotations should be read. - * - * @return array An array of Annotations. - */ - public function getMethodAnnotations(ReflectionMethod $method); - - /** - * Gets a method annotation. - * - * @param ReflectionMethod $method The ReflectionMethod to read the annotations from. - * @param class-string $annotationName The name of the annotation. - * - * @return T|null The Annotation or NULL, if the requested annotation does not exist. - * - * @template T - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName); - - /** - * Gets the annotations applied to a property. - * - * @param ReflectionProperty $property The ReflectionProperty of the property - * from which the annotations should be read. - * - * @return array An array of Annotations. - */ - public function getPropertyAnnotations(ReflectionProperty $property); - - /** - * Gets a property annotation. - * - * @param ReflectionProperty $property The ReflectionProperty to read the annotations from. - * @param class-string $annotationName The name of the annotation. - * - * @return T|null The Annotation or NULL, if the requested annotation does not exist. - * - * @template T - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName); -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php deleted file mode 100644 index 8a78c119d7..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php +++ /dev/null @@ -1,114 +0,0 @@ -parser = new DocParser(); - $this->parser->setIgnoreNotImportedAnnotations(true); - } - - /** - * Adds a namespace in which we will look for annotations. - * - * @param string $namespace - * - * @return void - */ - public function addNamespace($namespace) - { - $this->parser->addNamespace($namespace); - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotations(ReflectionClass $class) - { - return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotations(ReflectionMethod $method) - { - return $this->parser->parse( - $method->getDocComment(), - 'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()' - ); - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotations(ReflectionProperty $property) - { - return $this->parser->parse( - $property->getDocComment(), - 'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName() - ); - } - - /** - * {@inheritDoc} - */ - public function getClassAnnotation(ReflectionClass $class, $annotationName) - { - foreach ($this->getClassAnnotations($class) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getMethodAnnotation(ReflectionMethod $method, $annotationName) - { - foreach ($this->getMethodAnnotations($method) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } - - /** - * {@inheritDoc} - */ - public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) - { - foreach ($this->getPropertyAnnotations($property) as $annot) { - if ($annot instanceof $annotationName) { - return $annot; - } - } - - return null; - } -} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php deleted file mode 100644 index 9605fb8ddf..0000000000 --- a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php +++ /dev/null @@ -1,208 +0,0 @@ - - */ - private $tokens; - - /** - * The number of tokens. - * - * @var int - */ - private $numTokens; - - /** - * The current array pointer. - * - * @var int - */ - private $pointer = 0; - - /** - * @param string $contents - */ - public function __construct($contents) - { - $this->tokens = token_get_all($contents); - - // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it - // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored - // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a - // docblock. If the first thing in the file is a class without a doc block this would cause calls to - // getDocBlock() on said class to return our long lost doc_comment. Argh. - // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least - // it's harmless to us. - token_get_all("numTokens = count($this->tokens); - } - - /** - * Gets the next non whitespace and non comment token. - * - * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. - * If FALSE then only whitespace and normal comments are skipped. - * - * @return mixed[]|string|null The token if exists, null otherwise. - */ - public function next($docCommentIsComment = true) - { - for ($i = $this->pointer; $i < $this->numTokens; $i++) { - $this->pointer++; - if ( - $this->tokens[$i][0] === T_WHITESPACE || - $this->tokens[$i][0] === T_COMMENT || - ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) - ) { - continue; - } - - return $this->tokens[$i]; - } - - return null; - } - - /** - * Parses a single use statement. - * - * @return array A list with all found class names for a use statement. - */ - public function parseUseStatement() - { - $groupRoot = ''; - $class = ''; - $alias = ''; - $statements = []; - $explicitAlias = false; - while (($token = $this->next())) { - if (! $explicitAlias && $token[0] === T_STRING) { - $class .= $token[1]; - $alias = $token[1]; - } elseif ($explicitAlias && $token[0] === T_STRING) { - $alias = $token[1]; - } elseif ( - PHP_VERSION_ID >= 80000 && - ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) - ) { - $class .= $token[1]; - - $classSplit = explode('\\', $token[1]); - $alias = $classSplit[count($classSplit) - 1]; - } elseif ($token[0] === T_NS_SEPARATOR) { - $class .= '\\'; - $alias = ''; - } elseif ($token[0] === T_AS) { - $explicitAlias = true; - $alias = ''; - } elseif ($token === ',') { - $statements[strtolower($alias)] = $groupRoot . $class; - $class = ''; - $alias = ''; - $explicitAlias = false; - } elseif ($token === ';') { - $statements[strtolower($alias)] = $groupRoot . $class; - break; - } elseif ($token === '{') { - $groupRoot = $class; - $class = ''; - } elseif ($token === '}') { - continue; - } else { - break; - } - } - - return $statements; - } - - /** - * Gets all use statements. - * - * @param string $namespaceName The namespace name of the reflected class. - * - * @return array A list with all found use statements. - */ - public function parseUseStatements($namespaceName) - { - $statements = []; - while (($token = $this->next())) { - if ($token[0] === T_USE) { - $statements = array_merge($statements, $this->parseUseStatement()); - continue; - } - - if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) { - continue; - } - - // Get fresh array for new namespace. This is to prevent the parser to collect the use statements - // for a previous namespace with the same name. This is the case if a namespace is defined twice - // or if a namespace with the same name is commented out. - $statements = []; - } - - return $statements; - } - - /** - * Gets the namespace. - * - * @return string The found namespace. - */ - public function parseNamespace() - { - $name = ''; - while ( - ($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || ( - PHP_VERSION_ID >= 80000 && - ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) - )) - ) { - $name .= $token[1]; - } - - return $name; - } - - /** - * Gets the class name. - * - * @return string The found class name. - */ - public function parseClass() - { - // Namespaces and class names are tokenized the same: T_STRINGs - // separated by T_NS_SEPARATOR so we can use one function to provide - // both. - return $this->parseNamespace(); - } -} diff --git a/vendor/doctrine/lexer/README.md b/vendor/doctrine/lexer/README.md deleted file mode 100644 index 784f2a2712..0000000000 --- a/vendor/doctrine/lexer/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Doctrine Lexer - -[![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions) - -Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. - -This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). - -https://www.doctrine-project.org/projects/lexer.html diff --git a/vendor/doctrine/lexer/composer.json b/vendor/doctrine/lexer/composer.json deleted file mode 100644 index c435647728..0000000000 --- a/vendor/doctrine/lexer/composer.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "doctrine/lexer", - "type": "library", - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "keywords": [ - "php", - "parser", - "lexer", - "annotations", - "docblock" - ], - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "license": "MIT", - "authors": [ - {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, - {"name": "Roman Borschel", "email": "roman@code-factory.org"}, - {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} - ], - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "autoload": { - "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } - }, - "autoload-dev": { - "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" } - }, - "config": { - "allow-plugins": { - "composer/package-versions-deprecated": true, - "dealerdirect/phpcodesniffer-composer-installer": true - }, - "sort-packages": true - } -} diff --git a/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php b/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php deleted file mode 100644 index 7e8a11d443..0000000000 --- a/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php +++ /dev/null @@ -1,337 +0,0 @@ - - */ - private $tokens = []; - - /** - * Current lexer position in input string. - * - * @var int - */ - private $position = 0; - - /** - * Current peek of current lexer position. - * - * @var int - */ - private $peek = 0; - - /** - * The next token in the input. - * - * @var mixed[]|null - * @psalm-var Token|null - */ - public $lookahead; - - /** - * The last matched/seen token. - * - * @var mixed[]|null - * @psalm-var Token|null - */ - public $token; - - /** - * Composed regex for input parsing. - * - * @var string|null - */ - private $regex; - - /** - * Sets the input data to be tokenized. - * - * The Lexer is immediately reset and the new input tokenized. - * Any unprocessed tokens from any previous input are lost. - * - * @param string $input The input to be tokenized. - * - * @return void - */ - public function setInput($input) - { - $this->input = $input; - $this->tokens = []; - - $this->reset(); - $this->scan($input); - } - - /** - * Resets the lexer. - * - * @return void - */ - public function reset() - { - $this->lookahead = null; - $this->token = null; - $this->peek = 0; - $this->position = 0; - } - - /** - * Resets the peek pointer to 0. - * - * @return void - */ - public function resetPeek() - { - $this->peek = 0; - } - - /** - * Resets the lexer position on the input to the given position. - * - * @param int $position Position to place the lexical scanner. - * - * @return void - */ - public function resetPosition($position = 0) - { - $this->position = $position; - } - - /** - * Retrieve the original lexer's input until a given position. - * - * @param int $position - * - * @return string - */ - public function getInputUntilPosition($position) - { - return substr($this->input, 0, $position); - } - - /** - * Checks whether a given token matches the current lookahead. - * - * @param int|string $type - * - * @return bool - */ - public function isNextToken($type) - { - return $this->lookahead !== null && $this->lookahead['type'] === $type; - } - - /** - * Checks whether any of the given tokens matches the current lookahead. - * - * @param list $types - * - * @return bool - */ - public function isNextTokenAny(array $types) - { - return $this->lookahead !== null && in_array($this->lookahead['type'], $types, true); - } - - /** - * Moves to the next token in the input string. - * - * @return bool - */ - public function moveNext() - { - $this->peek = 0; - $this->token = $this->lookahead; - $this->lookahead = isset($this->tokens[$this->position]) - ? $this->tokens[$this->position++] : null; - - return $this->lookahead !== null; - } - - /** - * Tells the lexer to skip input tokens until it sees a token with the given value. - * - * @param string $type The token type to skip until. - * - * @return void - */ - public function skipUntil($type) - { - while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { - $this->moveNext(); - } - } - - /** - * Checks if given value is identical to the given token. - * - * @param mixed $value - * @param int|string $token - * - * @return bool - */ - public function isA($value, $token) - { - return $this->getType($value) === $token; - } - - /** - * Moves the lookahead token forward. - * - * @return mixed[]|null The next token or NULL if there are no more tokens ahead. - * @psalm-return Token|null - */ - public function peek() - { - if (isset($this->tokens[$this->position + $this->peek])) { - return $this->tokens[$this->position + $this->peek++]; - } - - return null; - } - - /** - * Peeks at the next token, returns it and immediately resets the peek. - * - * @return mixed[]|null The next token or NULL if there are no more tokens ahead. - * @psalm-return Token|null - */ - public function glimpse() - { - $peek = $this->peek(); - $this->peek = 0; - - return $peek; - } - - /** - * Scans the input string for tokens. - * - * @param string $input A query string. - * - * @return void - */ - protected function scan($input) - { - if (! isset($this->regex)) { - $this->regex = sprintf( - '/(%s)|%s/%s', - implode(')|(', $this->getCatchablePatterns()), - implode('|', $this->getNonCatchablePatterns()), - $this->getModifiers() - ); - } - - $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; - $matches = preg_split($this->regex, $input, -1, $flags); - - if ($matches === false) { - // Work around https://bugs.php.net/78122 - $matches = [[$input, 0]]; - } - - foreach ($matches as $match) { - // Must remain before 'value' assignment since it can change content - $type = $this->getType($match[0]); - - $this->tokens[] = [ - 'value' => $match[0], - 'type' => $type, - 'position' => $match[1], - ]; - } - } - - /** - * Gets the literal for a given token. - * - * @param int|string $token - * - * @return int|string - */ - public function getLiteral($token) - { - $className = static::class; - $reflClass = new ReflectionClass($className); - $constants = $reflClass->getConstants(); - - foreach ($constants as $name => $value) { - if ($value === $token) { - return $className . '::' . $name; - } - } - - return $token; - } - - /** - * Regex modifiers - * - * @return string - */ - protected function getModifiers() - { - return 'iu'; - } - - /** - * Lexical catchable patterns. - * - * @return string[] - */ - abstract protected function getCatchablePatterns(); - - /** - * Lexical non-catchable patterns. - * - * @return string[] - */ - abstract protected function getNonCatchablePatterns(); - - /** - * Retrieve token type. Also processes the token value if necessary. - * - * @param string $value - * - * @return int|string|null - */ - abstract protected function getType(&$value); -} diff --git a/vendor/doctrine/lexer/psalm.xml b/vendor/doctrine/lexer/psalm.xml deleted file mode 100644 index f331e50c11..0000000000 --- a/vendor/doctrine/lexer/psalm.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/vendor/evenement/evenement/.gitattributes b/vendor/evenement/evenement/.gitattributes new file mode 100644 index 0000000000..8e493b8ce9 --- /dev/null +++ b/vendor/evenement/evenement/.gitattributes @@ -0,0 +1,7 @@ +/.github export-ignore +/doc export-ignore +/examples export-ignore +/tests export-ignore +/.gitignore export-ignore +/CHANGELOG.md export-ignore +/phpunit.xml.dist export-ignore diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/evenement/evenement/LICENSE similarity index 83% rename from vendor/psr/cache/LICENSE.txt rename to vendor/evenement/evenement/LICENSE index b1c2c97b9d..d9a37d0a04 100644 --- a/vendor/psr/cache/LICENSE.txt +++ b/vendor/evenement/evenement/LICENSE @@ -1,14 +1,14 @@ -Copyright (c) 2015 PHP Framework Interoperability Group +Copyright (c) 2011 Igor Wiedler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, diff --git a/vendor/evenement/evenement/README.md b/vendor/evenement/evenement/README.md new file mode 100644 index 0000000000..455dd22c2b --- /dev/null +++ b/vendor/evenement/evenement/README.md @@ -0,0 +1,64 @@ +# Événement + +Événement is a very simple event dispatching library for PHP. + +It has the same design goals as [Silex](https://silex.symfony.com/) and +[Pimple](https://github.com/silexphp/Pimple), to empower the user while staying concise +and simple. + +It is very strongly inspired by the [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) API found in +[node.js](http://nodejs.org). + +![Continuous Integration](https://github.com/igorw/evenement/workflows/CI/badge.svg) +[![Latest Stable Version](https://poser.pugx.org/evenement/evenement/v/stable.png)](https://packagist.org/packages/evenement/evenement) +[![Total Downloads](https://poser.pugx.org/evenement/evenement/downloads.png)](https://packagist.org/packages/evenement/evenement/stats) +[![License](https://poser.pugx.org/evenement/evenement/license.png)](https://packagist.org/packages/evenement/evenement) + +## Fetch + +The recommended way to install Événement is [through composer](http://getcomposer.org). By running the following command: + + $ composer require evenement/evenement + +## Usage + +### Creating an Emitter + +```php +on('user.created', function (User $user) use ($logger) { + $logger->log(sprintf("User '%s' was created.", $user->getLogin())); +}); +``` + +### Removing Listeners + +```php +removeListener('user.created', function (User $user) use ($logger) { + $logger->log(sprintf("User '%s' was created.", $user->getLogin())); +}); +``` + +### Emitting Events + +```php +emit('user.created', [$user]); +``` + +Tests +----- + + $ ./vendor/bin/phpunit + +License +------- +MIT, see LICENSE. diff --git a/vendor/evenement/evenement/composer.json b/vendor/evenement/evenement/composer.json new file mode 100644 index 0000000000..5444d93e25 --- /dev/null +++ b/vendor/evenement/evenement/composer.json @@ -0,0 +1,29 @@ +{ + "name": "evenement/evenement", + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": ["event-dispatcher", "event-emitter"], + "license": "MIT", + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Evenement\\Tests\\": "tests/" + }, + "files": ["tests/functions.php"] + } +} diff --git a/vendor/evenement/evenement/src/EventEmitter.php b/vendor/evenement/evenement/src/EventEmitter.php new file mode 100644 index 0000000000..db189b972b --- /dev/null +++ b/vendor/evenement/evenement/src/EventEmitter.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +class EventEmitter implements EventEmitterInterface +{ + use EventEmitterTrait; +} diff --git a/vendor/evenement/evenement/src/EventEmitterInterface.php b/vendor/evenement/evenement/src/EventEmitterInterface.php new file mode 100644 index 0000000000..310631a104 --- /dev/null +++ b/vendor/evenement/evenement/src/EventEmitterInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +interface EventEmitterInterface +{ + public function on($event, callable $listener); + public function once($event, callable $listener); + public function removeListener($event, callable $listener); + public function removeAllListeners($event = null); + public function listeners($event = null); + public function emit($event, array $arguments = []); +} diff --git a/vendor/evenement/evenement/src/EventEmitterTrait.php b/vendor/evenement/evenement/src/EventEmitterTrait.php new file mode 100644 index 0000000000..1503429608 --- /dev/null +++ b/vendor/evenement/evenement/src/EventEmitterTrait.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Evenement; + +use InvalidArgumentException; + +use function count; +use function array_keys; +use function array_merge; +use function array_search; +use function array_unique; +use function array_values; + +trait EventEmitterTrait +{ + protected $listeners = []; + protected $onceListeners = []; + + public function on($event, callable $listener) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (!isset($this->listeners[$event])) { + $this->listeners[$event] = []; + } + + $this->listeners[$event][] = $listener; + + return $this; + } + + public function once($event, callable $listener) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (!isset($this->onceListeners[$event])) { + $this->onceListeners[$event] = []; + } + + $this->onceListeners[$event][] = $listener; + + return $this; + } + + public function removeListener($event, callable $listener) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + if (isset($this->listeners[$event])) { + $index = array_search($listener, $this->listeners[$event], true); + if (false !== $index) { + unset($this->listeners[$event][$index]); + if (count($this->listeners[$event]) === 0) { + unset($this->listeners[$event]); + } + } + } + + if (isset($this->onceListeners[$event])) { + $index = array_search($listener, $this->onceListeners[$event], true); + if (false !== $index) { + unset($this->onceListeners[$event][$index]); + if (count($this->onceListeners[$event]) === 0) { + unset($this->onceListeners[$event]); + } + } + } + } + + public function removeAllListeners($event = null) + { + if ($event !== null) { + unset($this->listeners[$event]); + } else { + $this->listeners = []; + } + + if ($event !== null) { + unset($this->onceListeners[$event]); + } else { + $this->onceListeners = []; + } + } + + public function listeners($event = null): array + { + if ($event === null) { + $events = []; + $eventNames = array_unique( + array_merge( + array_keys($this->listeners), + array_keys($this->onceListeners) + ) + ); + foreach ($eventNames as $eventName) { + $events[$eventName] = array_merge( + isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [], + isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : [] + ); + } + return $events; + } + + return array_merge( + isset($this->listeners[$event]) ? $this->listeners[$event] : [], + isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : [] + ); + } + + public function emit($event, array $arguments = []) + { + if ($event === null) { + throw new InvalidArgumentException('event name must not be null'); + } + + $listeners = []; + if (isset($this->listeners[$event])) { + $listeners = array_values($this->listeners[$event]); + } + + $onceListeners = []; + if (isset($this->onceListeners[$event])) { + $onceListeners = array_values($this->onceListeners[$event]); + } + + if(empty($listeners) === false) { + foreach ($listeners as $listener) { + $listener(...$arguments); + } + } + + if(empty($onceListeners) === false) { + unset($this->onceListeners[$event]); + foreach ($onceListeners as $listener) { + $listener(...$arguments); + } + } + } +} diff --git a/vendor/fidry/cpu-core-counter/LICENSE.md b/vendor/fidry/cpu-core-counter/LICENSE.md new file mode 100644 index 0000000000..02442130a8 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/LICENSE.md @@ -0,0 +1,16 @@ +# The MIT License (MIT) + +Copyright (c) 2022 Théo FIDRY + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the _Software_), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED **AS IS**, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/fidry/cpu-core-counter/README.md b/vendor/fidry/cpu-core-counter/README.md new file mode 100644 index 0000000000..4816935091 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/README.md @@ -0,0 +1,114 @@ +# CPU Core Counter + +This package is a tiny utility to get the number of CPU cores. + +```sh +composer require fidry/cpu-core-counter +``` + + +## Usage + +```php +use Fidry\CpuCoreCounter\CpuCoreCounter; +use Fidry\CpuCoreCounter\NumberOfCpuCoreNotFound; +use Fidry\CpuCoreCounter\Finder\DummyCpuCoreFinder; + +$counter = new CpuCoreCounter(); + +try { + $counter->getCount(); // e.g. 8 +} catch (NumberOfCpuCoreNotFound) { + return 1; // Fallback value +} + +// An alternative form where we not want to catch the exception: + +$counter = new CpuCoreCounter([ + ...CpuCoreCounter::getDefaultFinders(), + new DummyCpuCoreFinder(1), // Fallback value +]); + +$counter->getCount(); // e.g. 8 + +``` + + +## Advanced usage + +### Changing the finders + +When creating `CpuCoreCounter`, you may want to change the order of the finders +used or disable a specific finder. You can easily do so by passing the finders +you want + +```php +// Remove WindowsWmicFinder +$finders = array_filter( + CpuCoreCounter::getDefaultFinders(), + static fn (CpuCoreFinder $finder) => !($finder instanceof WindowsWmicFinder) +); + +$cores = (new CpuCoreCounter($finders))->getCount(); +``` + +```php +// Use CPUInfo first & don't use Nproc +$finders = [ + new CpuInfoFinder(), + new WindowsWmicFinder(), + new HwLogicalFinder(), +]; + +$cores = (new CpuCoreCounter($finders))->getCount(); +``` + +### Choosing only logical or physical finders + +`FinderRegistry` provides two helpful entries: + +- `::getDefaultLogicalFinders()`: gives an ordered list of finders that will + look for the _logical_ CPU cores count +- `::getDefaultPhysicalFinders()`: gives an ordered list of finders that will + look for the _physical_ CPU cores count + +By default when using `CpuCoreCounter`, it will use the logical finders since +it is more likely what you are looking for and is what is used by PHP source to +build the PHP binary. + + +### Checks what finders find what on your system + +You have two commands available that provides insight about what the finders +can find: + +``` +$ make diagnose # From this repository +$ ./vendor/fidry/cpu-core-counter/bin/diagnose.php # From the library +``` + +And: +``` +$ make execute # From this repository +$ ./vendor/fidry/cpu-core-counter/bin/execute.php # From the library +``` + + +## Backward Compatibility Promise (BCP) + +The policy is for the major part following the same as [Symfony's one][symfony-bc-policy]. +Note that the code marked as `@private` or `@internal` are excluded from the BCP. + +The following elements are also excluded: + +- The `diagnose` and `execute` commands: those are for debugging/inspection purposes only +- `FinderRegistry::get*Finders()`: new finders may be added or the order of finders changed at any time + + +## License + +This package is licensed using the MIT License. + +Please have a look at [`LICENSE.md`](LICENSE.md). + +[symfony-bc-policy]: https://symfony.com/doc/current/contributing/code/bc.html diff --git a/vendor/fidry/cpu-core-counter/bin/diagnose.php b/vendor/fidry/cpu-core-counter/bin/diagnose.php new file mode 100755 index 0000000000..7dd894a115 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/bin/diagnose.php @@ -0,0 +1,27 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use Fidry\CpuCoreCounter\Diagnoser; +use Fidry\CpuCoreCounter\Finder\FinderRegistry; + +require_once __DIR__.'/../vendor/autoload.php'; + +echo 'Running diagnosis...'.PHP_EOL.PHP_EOL; +echo Diagnoser::diagnose(FinderRegistry::getAllVariants()).PHP_EOL; + +echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL; +echo Diagnoser::diagnose(FinderRegistry::getDefaultLogicalFinders()).PHP_EOL; + +echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL; +echo Diagnoser::diagnose(FinderRegistry::getDefaultPhysicalFinders()).PHP_EOL; diff --git a/vendor/fidry/cpu-core-counter/bin/execute.php b/vendor/fidry/cpu-core-counter/bin/execute.php new file mode 100755 index 0000000000..edadebb1e4 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/bin/execute.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use Fidry\CpuCoreCounter\Diagnoser; +use Fidry\CpuCoreCounter\Finder\FinderRegistry; + +require_once __DIR__.'/../vendor/autoload.php'; + +echo 'Executing finders...'.PHP_EOL.PHP_EOL; +echo Diagnoser::execute(FinderRegistry::getAllVariants()).PHP_EOL; diff --git a/vendor/fidry/cpu-core-counter/composer.json b/vendor/fidry/cpu-core-counter/composer.json new file mode 100644 index 0000000000..3f324d19ff --- /dev/null +++ b/vendor/fidry/cpu-core-counter/composer.json @@ -0,0 +1,48 @@ +{ + "name": "fidry/cpu-core-counter", + "description": "Tiny utility to get the number of CPU cores.", + "license": "MIT", + "type": "library", + "keywords": [ + "cpu", + "core" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Fidry\\CpuCoreCounter\\Test\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "infection/extension-installer": true, + "phpstan/extension-installer": true + }, + "sort-packages": true + } +} diff --git a/vendor/fidry/cpu-core-counter/src/CpuCoreCounter.php b/vendor/fidry/cpu-core-counter/src/CpuCoreCounter.php new file mode 100644 index 0000000000..098693b544 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/CpuCoreCounter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter; + +use Fidry\CpuCoreCounter\Finder\CpuCoreFinder; +use Fidry\CpuCoreCounter\Finder\FinderRegistry; + +final class CpuCoreCounter +{ + /** + * @var list + */ + private $finders; + + /** + * @var positive-int|null + */ + private $count; + + /** + * @param list|null $finders + */ + public function __construct(?array $finders = null) + { + $this->finders = $finders ?? FinderRegistry::getDefaultLogicalFinders(); + } + + /** + * @throws NumberOfCpuCoreNotFound + * + * @return positive-int + */ + public function getCount(): int + { + // Memoize result + if (null === $this->count) { + $this->count = $this->findCount(); + } + + return $this->count; + } + + /** + * @throws NumberOfCpuCoreNotFound + * + * @return positive-int + */ + private function findCount(): int + { + foreach ($this->finders as $finder) { + $cores = $finder->find(); + + if (null !== $cores) { + return $cores; + } + } + + throw NumberOfCpuCoreNotFound::create(); + } + + /** + * @throws NumberOfCpuCoreNotFound + * + * @return array{CpuCoreFinder, positive-int} + */ + public function getFinderAndCores(): array + { + foreach ($this->finders as $finder) { + $cores = $finder->find(); + + if (null !== $cores) { + return [$finder, $cores]; + } + } + + throw NumberOfCpuCoreNotFound::create(); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Diagnoser.php b/vendor/fidry/cpu-core-counter/src/Diagnoser.php new file mode 100644 index 0000000000..872b55f807 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Diagnoser.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter; + +use Fidry\CpuCoreCounter\Finder\CpuCoreFinder; +use function array_map; +use function explode; +use function implode; +use function max; +use function str_repeat; +use const PHP_EOL; + +/** + * Utility to debug. + * + * @private + */ +final class Diagnoser +{ + /** + * Provides an aggregated diagnosis based on each finders diagnosis. + * + * @param list $finders + */ + public static function diagnose(array $finders): string + { + $diagnoses = array_map( + static function (CpuCoreFinder $finder): string { + return self::diagnoseFinder($finder); + }, + $finders + ); + + return implode(PHP_EOL, $diagnoses); + } + + /** + * Executes each finders. + * + * @param list $finders + */ + public static function execute(array $finders): string + { + $diagnoses = array_map( + static function (CpuCoreFinder $finder): string { + $coresCount = $finder->find(); + + return implode( + '', + [ + $finder->toString(), + ': ', + null === $coresCount ? 'NULL' : $coresCount, + ] + ); + }, + $finders + ); + + return implode(PHP_EOL, $diagnoses); + } + + private static function diagnoseFinder(CpuCoreFinder $finder): string + { + $diagnosis = $finder->diagnose(); + + $maxLineLength = max( + array_map( + 'strlen', + explode(PHP_EOL, $diagnosis) + ) + ); + + $separator = str_repeat('-', $maxLineLength); + + return implode( + '', + [ + $finder->toString().':'.PHP_EOL, + $separator.PHP_EOL, + $diagnosis.PHP_EOL, + $separator.PHP_EOL, + ] + ); + } + + private function __construct() + { + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php b/vendor/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php new file mode 100644 index 0000000000..751a311d24 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Executor; + +use function fclose; +use function function_exists; +use function is_resource; +use function proc_close; +use function proc_open; +use function stream_get_contents; + +final class ProcOpenExecutor implements ProcessExecutor +{ + public function execute(string $command): ?array + { + if (!function_exists('proc_open')) { + return null; + } + + $pipes = []; + + $process = @proc_open( + $command, + [ + ['pipe', 'rb'], + ['pipe', 'wb'], // stdout + ['pipe', 'wb'], // stderr + ], + $pipes + ); + + if (!is_resource($process)) { + return null; + } + + fclose($pipes[0]); + + $stdout = (string) stream_get_contents($pipes[1]); + $stderr = (string) stream_get_contents($pipes[2]); + + proc_close($process); + + return [$stdout, $stderr]; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php b/vendor/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php new file mode 100644 index 0000000000..287c01e154 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Executor; + +interface ProcessExecutor +{ + /** + * @return array{string, string}|null STDOUT & STDERR tuple + */ + public function execute(string $command): ?array; +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php new file mode 100644 index 0000000000..ee5c7d3dcd --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function preg_match; + +/** + * Find the number of logical CPU cores for Windows leveraging the Get-CimInstance + * cmdlet, which is a newer version that is recommended over Get-WmiObject. + */ +final class CmiCmdletLogicalFinder extends ProcOpenBasedFinder +{ + private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]-+[\s\n]+(?\d+)/'; + + protected function getCommand(): string + { + return 'Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property NumberOfLogicalProcessors'; + } + + public function toString(): string + { + return 'CmiCmdletLogicalFinder'; + } + + protected function countCpuCores(string $process): ?int + { + if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { + return parent::countCpuCores($process); + } + + $count = $matches['count']; + + return parent::countCpuCores($count); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php new file mode 100644 index 0000000000..8530123641 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function preg_match; + +/** + * Find the number of physical CPU cores for Windows. + * + * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916 + */ +final class CmiCmdletPhysicalFinder extends ProcOpenBasedFinder +{ + private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]-+[\s\n]+(?\d+)/'; + + protected function getCommand(): string + { + return 'Get-CimInstance -ClassName Win32_Processor | Select-Object -Property NumberOfCores'; + } + + public function toString(): string + { + return 'CmiCmdletPhysicalFinder'; + } + + protected function countCpuCores(string $process): ?int + { + if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { + return parent::countCpuCores($process); + } + + $count = $matches['count']; + + return parent::countCpuCores($count); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php new file mode 100644 index 0000000000..edb40e864c --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +interface CpuCoreFinder +{ + /** + * Provides an explanation which may offer some insight as to what the finder + * will be able to find. + * + * This is practical to have an idea of what each finder will find collect + * information for the unit tests, since integration tests are quite complicated + * as dependent on complex infrastructures. + */ + public function diagnose(): string; + + /** + * Find the number of CPU cores. If it could not find it, returns null. The + * means used to find the cores are at the implementation discretion. + * + * @return positive-int|null + */ + public function find(): ?int; + + public function toString(): string; +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php new file mode 100644 index 0000000000..dea4c41294 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function file_get_contents; +use function is_file; +use function sprintf; +use function substr_count; +use const PHP_EOL; + +/** + * Find the number of CPU cores looking up at the cpuinfo file which is available + * on Linux systems and Windows systems with a Linux sub-system. + * + * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909 + * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo + */ +final class CpuInfoFinder implements CpuCoreFinder +{ + private const CPU_INFO_PATH = '/proc/cpuinfo'; + + public function diagnose(): string + { + if (!is_file(self::CPU_INFO_PATH)) { + return sprintf( + 'The file "%s" could not be found.', + self::CPU_INFO_PATH + ); + } + + $cpuInfo = file_get_contents(self::CPU_INFO_PATH); + + if (false === $cpuInfo) { + return sprintf( + 'Could not get the content of the file "%s".', + self::CPU_INFO_PATH + ); + } + + return sprintf( + 'Found the file "%s" with the content:%s%s', + self::CPU_INFO_PATH, + PHP_EOL, + $cpuInfo + ); + } + + /** + * @return positive-int|null + */ + public function find(): ?int + { + $cpuInfo = self::getCpuInfo(); + + return null === $cpuInfo ? null : self::countCpuCores($cpuInfo); + } + + public function toString(): string + { + return 'CpuInfoFinder'; + } + + private static function getCpuInfo(): ?string + { + if (!@is_file(self::CPU_INFO_PATH)) { + return null; + } + + $cpuInfo = @file_get_contents(self::CPU_INFO_PATH); + + return false === $cpuInfo + ? null + : $cpuInfo; + } + + /** + * @internal + * + * @return positive-int|null + */ + public static function countCpuCores(string $cpuInfo): ?int + { + $processorCount = substr_count($cpuInfo, 'processor'); + + return $processorCount > 0 ? $processorCount : null; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php new file mode 100644 index 0000000000..1efa4da694 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function sprintf; + +/** + * This finder returns whatever value you gave to it. This is useful for testing + * or as a fallback to avoid to catch the NumberOfCpuCoreNotFound exception. + */ +final class DummyCpuCoreFinder implements CpuCoreFinder +{ + /** + * @var positive-int + */ + private $count; + + public function diagnose(): string + { + return sprintf( + 'Will return "%d".', + $this->count + ); + } + + /** + * @param positive-int $count + */ + public function __construct(int $count) + { + $this->count = $count; + } + + public function find(): ?int + { + return $this->count; + } + + public function toString(): string + { + return sprintf( + 'DummyCpuCoreFinder(value=%d)', + $this->count + ); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/FinderRegistry.php b/vendor/fidry/cpu-core-counter/src/Finder/FinderRegistry.php new file mode 100644 index 0000000000..ca9b8602f1 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/FinderRegistry.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +final class FinderRegistry +{ + /** + * @return list List of all the known finders with all their variants. + */ + public static function getAllVariants(): array + { + return [ + new CpuInfoFinder(), + new DummyCpuCoreFinder(1), + new HwLogicalFinder(), + new HwPhysicalFinder(), + new LscpuLogicalFinder(), + new LscpuPhysicalFinder(), + new _NProcessorFinder(), + new NProcessorFinder(), + new NProcFinder(true), + new NProcFinder(false), + new NullCpuCoreFinder(), + SkipOnOSFamilyFinder::forWindows( + new DummyCpuCoreFinder(1) + ), + OnlyOnOSFamilyFinder::forWindows( + new DummyCpuCoreFinder(1) + ), + new OnlyInPowerShellFinder(new CmiCmdletLogicalFinder()), + new OnlyInPowerShellFinder(new CmiCmdletPhysicalFinder()), + new WindowsRegistryLogicalFinder(), + new WmicPhysicalFinder(), + new WmicLogicalFinder(), + ]; + } + + /** + * @return list + */ + public static function getDefaultLogicalFinders(): array + { + return [ + OnlyOnOSFamilyFinder::forWindows( + new OnlyInPowerShellFinder( + new CmiCmdletLogicalFinder() + ) + ), + OnlyOnOSFamilyFinder::forWindows(new WindowsRegistryLogicalFinder()), + OnlyOnOSFamilyFinder::forWindows(new WmicLogicalFinder()), + new NProcFinder(), + new HwLogicalFinder(), + new _NProcessorFinder(), + new NProcessorFinder(), + new LscpuLogicalFinder(), + new CpuInfoFinder(), + ]; + } + + /** + * @return list + */ + public static function getDefaultPhysicalFinders(): array + { + return [ + OnlyOnOSFamilyFinder::forWindows( + new OnlyInPowerShellFinder( + new CmiCmdletPhysicalFinder() + ) + ), + OnlyOnOSFamilyFinder::forWindows(new WmicPhysicalFinder()), + new HwPhysicalFinder(), + new LscpuPhysicalFinder(), + ]; + } + + private function __construct() + { + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php new file mode 100644 index 0000000000..d1129038a6 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +/** + * Find the number of logical CPU cores for Linux, BSD and OSX. + * + * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909 + * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html + */ +final class HwLogicalFinder extends ProcOpenBasedFinder +{ + protected function getCommand(): string + { + return 'sysctl -n hw.logicalcpu'; + } + + public function toString(): string + { + return 'HwLogicalFinder'; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php new file mode 100644 index 0000000000..65ca1cfd6d --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +/** + * Find the number of physical CPU cores for Linux, BSD and OSX. + * + * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909 + * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html + */ +final class HwPhysicalFinder extends ProcOpenBasedFinder +{ + protected function getCommand(): string + { + return 'sysctl -n hw.physicalcpu'; + } + + public function toString(): string + { + return 'HwPhysicalFinder'; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php new file mode 100644 index 0000000000..bce09ebf54 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function count; +use function explode; +use function is_array; +use function preg_grep; +use const PHP_EOL; + +/** + * The number of logical cores. + * + * @see https://stackoverflow.com/a/23378780/5846754 + */ +final class LscpuLogicalFinder extends ProcOpenBasedFinder +{ + public function getCommand(): string + { + return 'lscpu -p'; + } + + protected function countCpuCores(string $process): ?int + { + $lines = explode(PHP_EOL, $process); + $actualLines = preg_grep('/^\d+,/', $lines); + + if (!is_array($actualLines)) { + return null; + } + + $count = count($actualLines); + + return 0 === $count ? null : $count; + } + + public function toString(): string + { + return 'LscpuLogicalFinder'; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php new file mode 100644 index 0000000000..58523cee03 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function count; +use function explode; +use function is_array; +use function preg_grep; +use function strtok; +use const PHP_EOL; + +/** + * The number of physical processors. + * + * @see https://stackoverflow.com/a/23378780/5846754 + */ +final class LscpuPhysicalFinder extends ProcOpenBasedFinder +{ + public function toString(): string + { + return 'LscpuPhysicalFinder'; + } + + public function getCommand(): string + { + return 'lscpu -p'; + } + + protected function countCpuCores(string $process): ?int + { + $lines = explode(PHP_EOL, $process); + $actualLines = preg_grep('/^\d+/', $lines); + + if (!is_array($actualLines)) { + return null; + } + + $cores = []; + foreach ($actualLines as $line) { + strtok($line, ','); + $core = strtok(','); + + if (false === $core) { + continue; + } + + $cores[$core] = true; + } + unset($cores['-']); + + $count = count($cores); + + return 0 === $count ? null : $count; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/NProcFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/NProcFinder.php new file mode 100644 index 0000000000..60a8ab787d --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/NProcFinder.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use Fidry\CpuCoreCounter\Executor\ProcessExecutor; +use function sprintf; + +/** + * The number of (logical) cores. + * + * @see https://github.com/infection/infection/blob/fbd8c44/src/Resource/Processor/CpuCoresCountProvider.php#L69-L82 + * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo + */ +final class NProcFinder extends ProcOpenBasedFinder +{ + /** + * @var bool + */ + private $all; + + /** + * @param bool $all If disabled will give the number of cores available for the current process only. + */ + public function __construct( + bool $all = true, + ?ProcessExecutor $executor = null + ) { + parent::__construct($executor); + + $this->all = $all; + } + + public function toString(): string + { + return sprintf( + 'NProcFinder(all=%s)', + $this->all ? 'true' : 'false' + ); + } + + protected function getCommand(): string + { + return 'nproc'.($this->all ? ' --all' : ''); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php new file mode 100644 index 0000000000..9143e31cd3 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +/** + * Find the number of logical CPU cores for FreeSBD, Solaris and the likes. + * + * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw + */ +final class NProcessorFinder extends ProcOpenBasedFinder +{ + protected function getCommand(): string + { + return 'getconf NPROCESSORS_ONLN'; + } + + public function toString(): string + { + return 'NProcessorFinder'; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php new file mode 100644 index 0000000000..50af2d4d86 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +/** + * This finder returns whatever value you gave to it. This is useful for testing. + */ +final class NullCpuCoreFinder implements CpuCoreFinder +{ + public function diagnose(): string + { + return 'Will return "null".'; + } + + public function find(): ?int + { + return null; + } + + public function toString(): string + { + return 'NullCpuCoreFinder'; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php new file mode 100644 index 0000000000..d36d030518 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function getenv; +use function sprintf; + +final class OnlyInPowerShellFinder implements CpuCoreFinder +{ + /** + * @var CpuCoreFinder + */ + private $decoratedFinder; + + public function __construct(CpuCoreFinder $decoratedFinder) + { + $this->decoratedFinder = $decoratedFinder; + } + + public function diagnose(): string + { + $powerShellModulePath = getenv('PSModulePath'); + + return $this->skip() + ? sprintf( + 'Skipped; no power shell module path detected ("%s").', + $powerShellModulePath + ) + : $this->decoratedFinder->diagnose(); + } + + public function find(): ?int + { + return $this->skip() + ? null + : $this->decoratedFinder->find(); + } + + public function toString(): string + { + return sprintf( + 'OnlyInPowerShellFinder(%s)', + $this->decoratedFinder->toString() + ); + } + + private function skip(): bool + { + return false === getenv('PSModulePath'); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php new file mode 100644 index 0000000000..3147808392 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function implode; +use function sprintf; +use const PHP_OS_FAMILY; + +final class OnlyOnOSFamilyFinder implements CpuCoreFinder +{ + /** + * @var list + */ + private $skippedOSFamilies; + + /** + * @var CpuCoreFinder + */ + private $decoratedFinder; + + /** + * @param string|list $skippedOSFamilyOrFamilies + */ + public function __construct( + $skippedOSFamilyOrFamilies, + CpuCoreFinder $decoratedFinder + ) { + $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies; + $this->decoratedFinder = $decoratedFinder; + } + + public static function forWindows(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Windows', + $decoratedFinder + ); + } + + public static function forBSD(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'BSD', + $decoratedFinder + ); + } + + public static function forDarwin(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Darwin', + $decoratedFinder + ); + } + + public static function forSolaris(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Solaris', + $decoratedFinder + ); + } + + public static function forLinux(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Linux', + $decoratedFinder + ); + } + + public function diagnose(): string + { + return $this->skip() + ? sprintf( + 'Skipped platform detected ("%s").', + PHP_OS_FAMILY + ) + : $this->decoratedFinder->diagnose(); + } + + public function find(): ?int + { + return $this->skip() + ? null + : $this->decoratedFinder->find(); + } + + public function toString(): string + { + return sprintf( + 'OnlyOnOSFamilyFinder(only=(%s),%s)', + implode(',', $this->skippedOSFamilies), + $this->decoratedFinder->toString() + ); + } + + private function skip(): bool + { + return !in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php new file mode 100644 index 0000000000..793ec647e7 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use Fidry\CpuCoreCounter\Executor\ProcessExecutor; +use Fidry\CpuCoreCounter\Executor\ProcOpenExecutor; +use function filter_var; +use function function_exists; +use function is_int; +use function sprintf; +use function trim; +use const FILTER_VALIDATE_INT; +use const PHP_EOL; + +abstract class ProcOpenBasedFinder implements CpuCoreFinder +{ + /** + * @var ProcessExecutor + */ + private $executor; + + public function __construct(?ProcessExecutor $executor = null) + { + $this->executor = $executor ?? new ProcOpenExecutor(); + } + + public function diagnose(): string + { + if (!function_exists('proc_open')) { + return 'The function "proc_open" is not available.'; + } + + $command = $this->getCommand(); + $output = $this->executor->execute($command); + + if (null === $output) { + return sprintf( + 'Failed to execute the command "%s".', + $command + ); + } + + [$stdout, $stderr] = $output; + $failed = '' !== trim($stderr); + + return $failed + ? sprintf( + 'Executed the command "%s" which wrote the following output to the STDERR:%s%s', + $command, + PHP_EOL, + $stderr + ) + : sprintf( + 'Executed the command "%s" and got the following (STDOUT) output:%s%s', + $command, + PHP_EOL, + $stdout + ); + } + + /** + * @return positive-int|null + */ + public function find(): ?int + { + $output = $this->executor->execute($this->getCommand()); + + if (null === $output) { + return null; + } + + [$stdout, $stderr] = $output; + $failed = '' !== trim($stderr); + + return $failed + ? null + : $this->countCpuCores($stdout); + } + + /** + * @internal + * + * @return positive-int|null + */ + protected function countCpuCores(string $process): ?int + { + $cpuCount = filter_var($process, FILTER_VALIDATE_INT); + + return is_int($cpuCount) && $cpuCount > 0 ? $cpuCount : null; + } + + abstract protected function getCommand(): string; +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php new file mode 100644 index 0000000000..66a50164ff --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function implode; +use function in_array; +use function sprintf; + +final class SkipOnOSFamilyFinder implements CpuCoreFinder +{ + /** + * @var list + */ + private $skippedOSFamilies; + + /** + * @var CpuCoreFinder + */ + private $decoratedFinder; + + /** + * @param string|list $skippedOSFamilyOrFamilies + */ + public function __construct( + $skippedOSFamilyOrFamilies, + CpuCoreFinder $decoratedFinder + ) { + $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies; + $this->decoratedFinder = $decoratedFinder; + } + + public static function forWindows(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Windows', + $decoratedFinder + ); + } + + public static function forBSD(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'BSD', + $decoratedFinder + ); + } + + public static function forDarwin(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Darwin', + $decoratedFinder + ); + } + + public static function forSolaris(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Solaris', + $decoratedFinder + ); + } + + public static function forLinux(CpuCoreFinder $decoratedFinder): self + { + return new self( + 'Linux', + $decoratedFinder + ); + } + + public function diagnose(): string + { + return $this->skip() + ? sprintf( + 'Skipped platform detected ("%s").', + PHP_OS_FAMILY + ) + : $this->decoratedFinder->diagnose(); + } + + public function find(): ?int + { + return $this->skip() + ? null + : $this->decoratedFinder->find(); + } + + public function toString(): string + { + return sprintf( + 'SkipOnOSFamilyFinder(skip=(%s),%s)', + implode(',', $this->skippedOSFamilies), + $this->decoratedFinder->toString() + ); + } + + private function skip(): bool + { + return in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php new file mode 100644 index 0000000000..b223652b10 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function array_filter; +use function count; +use function explode; +use const PHP_EOL; + +/** + * Find the number of logical CPU cores for Windows. + * + * @see https://knowledge.informatica.com/s/article/151521 + */ +final class WindowsRegistryLogicalFinder extends ProcOpenBasedFinder +{ + protected function getCommand(): string + { + return 'reg query HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor'; + } + + public function toString(): string + { + return 'WindowsRegistryLogicalFinder'; + } + + protected function countCpuCores(string $process): ?int + { + $count = count( + array_filter( + explode(PHP_EOL, $process), + static function (string $line): bool { + return '' !== trim($line); + } + ) + ); + + return $count > 0 ? $count : null; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php new file mode 100644 index 0000000000..db576a6407 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function preg_match; + +/** + * Find the number of logical CPU cores for Windows. + * + * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916 + */ +final class WmicLogicalFinder extends ProcOpenBasedFinder +{ + private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]+(?\d+)/'; + + protected function getCommand(): string + { + return 'wmic cpu get NumberOfLogicalProcessors'; + } + + public function toString(): string + { + return 'WmicLogicalFinder'; + } + + protected function countCpuCores(string $process): ?int + { + if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { + return parent::countCpuCores($process); + } + + $count = $matches['count']; + + return parent::countCpuCores($count); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php new file mode 100644 index 0000000000..140b9fdb5f --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +use function preg_match; + +/** + * Find the number of physical CPU cores for Windows. + * + * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916 + */ +final class WmicPhysicalFinder extends ProcOpenBasedFinder +{ + private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]+(?\d+)/'; + + protected function getCommand(): string + { + return 'wmic cpu get NumberOfCores'; + } + + public function toString(): string + { + return 'WmicPhysicalFinder'; + } + + protected function countCpuCores(string $process): ?int + { + if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { + return parent::countCpuCores($process); + } + + $count = $matches['count']; + + return parent::countCpuCores($count); + } +} diff --git a/vendor/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php b/vendor/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php new file mode 100644 index 0000000000..23f452e44b --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter\Finder; + +/** + * Find the number of logical CPU cores for Linux and the likes. + * + * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw + */ +final class _NProcessorFinder extends ProcOpenBasedFinder +{ + protected function getCommand(): string + { + return 'getconf _NPROCESSORS_ONLN'; + } + + public function toString(): string + { + return '_NProcessorFinder'; + } +} diff --git a/vendor/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php b/vendor/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php new file mode 100644 index 0000000000..e54f893155 --- /dev/null +++ b/vendor/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Fidry\CpuCoreCounter; + +use RuntimeException; + +final class NumberOfCpuCoreNotFound extends RuntimeException +{ + public static function create(): self + { + return new self( + 'Could not find the number of CPU cores available.' + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md b/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md index a12475707b..b35dd0cfab 100644 --- a/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md +++ b/vendor/friendsofphp/php-cs-fixer/CHANGELOG.md @@ -3,6 +3,1334 @@ CHANGELOG for PHP CS Fixer This file contains changelogs for stable releases only. +Changelog for v3.61.1 +--------------------- + +* fix: `NoSuperfluousPhpdocTagsFixer` - fix "Undefined array key 0" error (#8150) + +Changelog for v3.61.0 +--------------------- + +* feat: no_superfluous_phpdoc_tags - also cover ?type (#8125) +* feat: support PHPUnit v9.1 naming for some asserts (#7997) +* fix: Do not mangle non-whitespace token in `PhpdocIndentFixer` (#8147) +* DX: add more typehints for `class-string` (#8139) +* DX: refactor `ProjectCodeTest::provideDataProviderMethodCases` (#8138) + +Changelog for v3.60.0 +--------------------- + +* feat: Add sprintf in the list of compiler optimized functions (#8092) +* feat: `PhpUnitAttributesFixer` - add option to keep annotations (#8090) +* chore: cleanup tests that had `@requires PHP 7.4` ages ago (#8122) +* chore: cleanup `TokensAnalyzerTest` (#8123) +* chore: fix example issue reported by reportPossiblyNonexistentGeneralArrayOffset from PHPStan (#8089) +* chore: NoSuperfluousPhpdocTagsFixer - no need to call heavy toComparableNames method to add null type (#8132) +* chore: PHPStan 11 array rules (#8011) +* chore: PhpUnitSizeClassFixerTest - solve PHP 8.4 issues (#8105) +* chore: reduce PHPStan errors in PhpUnitAttributesFixer (#8091) +* chore: reuse test methods (#8119) +* CI: check autoload (#8121) +* CI: Update PHPStan to 1.11.8 (#8133) +* deps: upgrade dev-tools (#8102) +* DX: check for duplicated test data (#8131) +* DX: check for duplicated test methods (#8124) +* DX: check for duplicated test methods (as AutoReview test) (#8134) +* DX: do not exclude duplicates that are clearly mistakes (#8135) +* DX: Dump `offsetAccess.notFound` errors to baseline (#8107) +* fix: Better way of walking types in `TypeExpression` (#8076) +* fix: CI for PHP 8.4 (#8114) +* fix: update `TokensTest` to shrink PHPStan's baseline (#8112) +* fix: `no_useless_concat_operator` - do not break variable (2) (#7927) +* fix: `NullableTypeDeclarationFixer` - don't convert standalone `null` into nullable union type (#8098) +* fix: `NullableTypeDeclarationFixer` - don't convert standalone `NULL` into nullable union type (#8111) +* fix: `NullableTypeDeclarationFixer` - insert correct token (#8118) +* fix: `PhpUnitAttributesFixer` - handle multiple annotations of the same name (#8075) + +Changelog for v3.59.3 +--------------------- + +* refactor: refactor to templated trait+interface (#7988) + +Changelog for v3.59.2 +--------------------- + +* fix: "list" is reserved type (#8087) +* chore: add missing type in method prototype (#8088) +* CI: bump Ubuntu version (#8086) +* deps: bump infection to unblock PHPUnit 11, and few more as chore (#8083) + +Changelog for v3.59.1 +--------------------- + +* fix: Bump React's JSON decoder buffer size (#8068) +* docs: options - handle enums in dicts (#8082) + +Changelog for v3.59.0 +--------------------- + +* feat(Docker): Multi-arch build (support for `arm64`) (#8079) +* feat: `@PhpCsFixer` ruleset - normalise implicit backslashes in single quoted strings (#7965) +* feat: `SimpleToComplexStringVariableFixer` - support variable being an array (#8064) +* fix: Look up for PHPDoc's variable name by only chars allowed in the variables (#8062) +* fix: Update `PhpUnitTestCaseStaticMethodCallsFixer::STATIC_METHODS` (#8073) +* fix: `native_constant_invocation` - array constants with native constant names (#8008) +* chore: update PHPStan (#8060) +* CI: Update PHPStan to 1.11.4 (#8074) +* docs: don't expose list as config type for dicts (#8081) +* docs: Make wording in `final_class` docs less dismissive (#8065) +* docs: Update 1-bug_report.yml (#8067) +* DX: Remove version from Docker Compose files (#8061) + +Changelog for v3.58.1 +--------------------- + +* fix: `ConstantCaseFixer` - do not change class constant usages (#8055) +* fix: `PhpUnitTestClassRequiresCoversFixer` - do not add annotation when attribute with leading slash present (#8054) + +Changelog for v3.58.0 +--------------------- + +* chore(doc): Use FQCN for parallel config in documentation (#8029) +* chore: fix typo in `PhpUnitTestClassRequiresCoversFixerTest` (#8047) +* chore: RandomApiMigrationFixer - do not modify configuration property (#8033) +* chore: Tokens::setCode - further improvements to cache (#8053) +* chore: update PHPStan (#8045) +* docs: Add missing imports in a cookbook about creating custom rules (#8031) +* docs: fix deprecated string interpolation style (#8036) +* docs: global_namespace_import - simplify allowed config types (#8023) +* feat(GroupImportFixer): Ability to configure which type of imports should be grouped (#8046) +* fix: clear `Tokens::$blockStartCache` and `Tokens::$blockEndCache` when calling `Tokens::setCode` (#8051) +* fix: correctly handle PHP closing tag with `simplified_null_return` (#8049) +* fix: `ConstantCaseFixer` - do not change namespace (#8004) +* fix: `PhpUnitAttributesFixer` - do not add attribute if already present (#8043) +* fix: `PhpUnitSizeClassFixer` - do not add annotation when there are attributes (#8044) +* fix: `PhpUnitTestClassRequiresCoversFixer` - attribute detection when class is `readonly` (#8042) + +Changelog for v3.57.2 +--------------------- + +* docs: better ConfigurableFixer allowed types (#8024) +* docs: Improve Docker usage example (#8021) +* feat: Report used memory to 2 decimal digits only (#8017) +* fix: Support named args in `ParallelConfigFactory::detect()` (#8026) +* fix: `php_unit_test_class_requires_covers` Attribute detection when class is final (#8016) + +Changelog for v3.57.1 +--------------------- + +* chore: update PHPDoc in `Preg::matchAll` (#8012) +* fix: Runner - handle no files while in parallel runner (#8015) + +Changelog for v3.57.0 +--------------------- + +* feat: Ability to run Fixer with parallel runner 🎉 (#7777) + +Changelog for v3.56.2 +--------------------- + +* chore: update PHPStan (#8010) +* DX: Fix Mess Detector violations (#8007) +* DX: Install PCov extension for local Docker (#8006) + +Changelog for v3.56.1 +--------------------- + +* chore: improve PHPDoc typehints (#7994) +* CI: Allow any integer in PHPStan error for Token's constructor (#8000) +* fix: Better array shape in `PhpUnitDedicateAssertFixer` (#7999) +* fix: `ConstantCaseFixer` - do not touch typed constants (#7998) + +Changelog for v3.56.0 +--------------------- + +* feat: `TrailingCommaInMultilineFixer` - handle trailing comma in language constructs (#7989) +* fix: `TrailingCommaInMultilineFixer` - language constructs should be covered by arguments, not parameters (#7990) +* chore: remove invalid comment (#7987) +* DX: Cache optimisation (#7985) + +Changelog for v3.55.0 +--------------------- + +* feat: Introduce `OrderedAttributesFixer` (#7395) +* chore: few SCA fixes and dev-tools update (#7969) +* chore: fix phpdoc types (#7977) +* chore: narrow PHPDoc types (#7979) +* chore: Normalize implicit backslahes in single quoted strings internally (#7786) +* chore: phpdoc - rely on strict list/tuple/assoc instead of array (#7978) +* chore: PhpUnitDataProviderNameFixer - follow config creation pattern (#7980) +* chore: Preg - drop half-support for array-pattern (#7976) +* chore: re-use CodeHasher (#7984) +* chore: RuleSetsTest - assert that Fixer is configurable (#7961) +* chore: sugar syntax (#7986) +* chore: Tokens should be always a list (#7698) +* CI: Ad-hoc fix for MacOS jobs (#7970) +* CI: Fix calculating diff between branches in PRs (#7973) +* DX: allow to enforce cache mechanism by env var (#7983) +* DX: do not typehint fixed-length arrays as lists (#7974) +* DX: Prevent having deprecated fixers listed as successors of other deprecated fixers (#7967) +* DX: Resolve/Ignore PHPStan issues on level 6 + bump to level 7 with new baseline (#7971) +* DX: use `list` type in PHPDoc (#7975) +* fix: `PhpUnitAttributesFixer` - fix for `#[RequiresPhp]` exceeding its constructor parameters (#7966) +* test: don't count comment after class as another classy element (#7982) + +Changelog for v3.54.0 +--------------------- + +* feat: introduce `PhpUnitAttributesFixer` (#7831) +* chore: Properly determine self-approval trigger commit (#7936) +* chore: Revert ref for self-approval Git checkout (#7944) +* CI: check if proper array key is declared (#7912) +* DX: cleanup `FullyQualifiedStrictTypesFixerTest` (#7954) +* DX: cleanup `PhpdocNoAccessFixerTest` (#7933) +* DX: cleanup `PhpUnitMethodCasingFixerTest` (#7948) +* DX: cleanup `PhpUnitStrictFixerTest` (#7938) +* DX: Improve internal dist config for Fixer (#7952) +* DX: Improve issue templates (#7942) +* DX: there is no namespace if there is no PHP code (#7953) +* DX: update .gitattributes (#7931) +* fix: Remove Infection during Docker release (#7937) +* fix: `FullyQualifiedStrictTypesFixer` - do not add imports before PHP opening tag (#7955) +* fix: `PhpUnitMethodCasingFixer` - do not double underscore (#7949) +* fix: `PhpUnitTestClassRequiresCoversFixer` - do not add annotation when there are attributes (#7880) +* test: Ignore PHP version related mutations (#7935) + +Changelog for v3.53.0 +--------------------- + +* chore: Use `list` over `array` in more places (#7905) +* CI: allow for self-approvals for maintainers (#7921) +* CI: Improve Infection setup (#7913) +* CI: no need to trigger enable auto-merge when self-approve (#7929) +* DX: reduce `array_filter` function usages (#7923) +* DX: remove duplicated character from `trim` call (#7930) +* DX: update actions producing warnings (#7925) +* DX: update actions producing warnings (#7928) +* DX: update `phpstan/phpstan-strict-rules` (#7924) +* feat: Add trailing comma in multiline to PER-CS 2.0 (#7916) +* feat: Introduce `AttributeAnalysis` (#7909) +* feat: `@PHP84Migration` introduction (#7774) +* fix: Constant invocation detected in typed constants (#7892) +* fix: `PhpdocArrayTypeFixer` - JIT stack limit exhausted (#7895) +* test: Introduce Infection for mutation tests (#7874) + +Changelog for v3.52.1 +--------------------- + +* fix: StatementIndentationFixer - do not crash on ternary operator in class property (#7899) +* fix: `PhpCsFixer\Tokenizer\Tokens::setSize` return type (#7900) + +Changelog for v3.52.0 +--------------------- + +* chore: fix PHP 8.4 deprecations (#7894) +* chore: fix PHPStan 1.10.60 issues (#7873) +* chore: list over array in more places (#7876) +* chore: replace template with variable in Preg class (#7882) +* chore: update PHPStan (#7871) +* depr: `nullable_type_declaration_for_default_null_value` - deprecate option that is against `@PHP84Migration` (#7872) +* docs: Fix typo (#7889) +* feat: Add support for callable template in PHPDoc parser (#7084) +* feat: Add `array_indentation` to `PER-CS2.0` ruleset (#7881) +* feat: `@Symfony:risky` - add `no_unreachable_default_argument_value` (#7863) +* feat: `PhpCsFixer` ruleset - enable `nullable_type_declaration_for_default_null_value` (#7870) +* fix: Constant invocation detected in DNF types (#7869) +* fix: Correctly indent multiline constants and properties (#7875) +* fix: `no_useless_concat_operator` - do not break variable (#7827) +* fix: `TokensAnalyzer` - handle unary operator in arrow functions (#7862) +* fix: `TypeExpression` - fix "JIT stack limit exhausted" error (#7843) + +Changelog for v3.51.0 +--------------------- + +* chore: add missing tests for non-documentation classes (#7848) +* chore: do not perform type analysis in tests (#7852) +* chore: list over array in more places (#7857) +* chore: tests documentation classes (#7855) +* feat: `@Symfony` - add nullable_type_declaration (#7856) +* test: fix wrong type in param annotation (#7858) + +Changelog for v3.50.0 +--------------------- + +* chore: add missing types (#7842) +* chore: BlocksAnalyzer - raise exception on invalid index (#7819) +* chore: DataProviderAnalysis - expect list over array (#7800) +* chore: do not use `@large` on method level (#7832) +* chore: do not use `@medium` on method level (#7833) +* chore: Fix typos (#7835) +* chore: rename variables (#7847) +* chore: some improvements around array typehints (#7799) +* CI: fix PHP 8.4 job (#7829) +* DX: Include `symfony/var-dumper` in dev tools (#7795) +* feat: Ability to remove unused imports from multi-use statements (#7815) +* feat: allow PHPUnit 11 (#7824) +* feat: Allow shortening symbols from multi-use statements (only classes for now) (#7816) +* feat: introduce `PhpdocArrayTypeFixer` (#7812) +* feat: PhpUnitTestCaseStaticMethodCallsFixer - cover PHPUnit v11 methods (#7822) +* feat: Support for multi-use statements in `NamespaceUsesAnalyzer` (#7814) +* feat: `MbStrFunctionsFixer` - add support for `mb_trim`, `mb_ltrim` and `mb_rtrim` functions (#7840) +* feat: `NoEmptyPhpdocFixer` - do not leave empty line after removing PHPDoc (#7820) +* feat: `no_superfluous_phpdoc_tags` - introduce `allow_future_params` option (#7743) +* fix: do not use wrongly named arguments in data providers (#7823) +* fix: Ensure PCNTL extension is always installed in Docker (#7782) +* fix: PhpdocListTypeFixer - support key types containing `<…>` (#7817) +* fix: Proper build target for local Docker Compose (#7834) +* fix: union PHPDoc support in `fully_qualified_strict_types` fixer (#7719) +* fix: `ExecutorWithoutErrorHandler` - remove invalid PHP 7.4 type (#7845) +* fix: `fully_qualified_strict_types` must honor template/local type identifiers (#7724) +* fix: `MethodArgumentSpaceFixer` - do not break heredoc/nowdoc (#7828) +* fix: `NumericLiteralSeparatorFixer` - do not change `float` to `int` when there is nothing after the dot (#7805) +* fix: `PhpUnitStrictFixer` - do not crash on property having the name of method to fix (#7804) +* fix: `SingleSpaceAroundConstructFixer` - correctly recognise multiple constants (#7700) +* fix: `TypeExpression` - handle array shape key with dash (#7841) + +Changelog for v3.49.0 +--------------------- + +* chore(checkbashisms): update to 2.23.7 (#7780) +* chore: add missing key types in PHPDoc types (#7779) +* chore: Exclude `topic/core` issues/PRs from Stale Bot (#7788) +* chore: `DescribeCommand` - better handling of deprecations (#7778) +* docs: docker - use gitlab reporter in GitLab integration example (#7764) +* docs: docker in CI - don't suggest command that overrides path from config file (#7763) +* DX: check deprecations exactly (#7742) +* feat: Add `ordered_types` to `@Symfony` (#7356) +* feat: introduce `PhpdocListTypeFixer` (#7796) +* feat: introduce `string_implicit_backslashes` as `escape_implicit_backslashes` replacement (#7669) +* feat: update `Symfony.nullable_type_declaration_for_default_null_value` config (#7773) +* feat: `@PhpCsFixer` ruleset - enable `php_unit_data_provider_static` (#7685) +* fix: Allow using cache when running in Docker distribution (#7769) +* fix: ClassDefinitionFixer for anonymous class with phpdoc/attribute on separate line (#7546) +* fix: `ClassKeywordFixer` must run before `FullyQualifiedStrictTypesFixer` (#7767) +* fix: `function_to_constant` `get_class()` replacement (#7770) +* fix: `LowercaseStaticReferenceFixer` - do not change typed constants (#7775) +* fix: `PhpdocTypesFixer` - handle more complex types (#7791) +* fix: `TypeExpression` - do not break type using `walkTypes` method (#7785) + +Changelog for v3.48.0 +--------------------- + +* chore: `FullyQualifiedStrictTypesFixer` must run before `OrderedInterfacesFixer` (#7762) +* docs: Add PHP-CS-Fixer integration in a GitHub Action step (#7757) +* feat: `PhpdocTypesOrderFixer` Support DNF types (#7732) +* fix: Support shebang in fixers operating on PHP opening tag (#7687) +* fix: work correctly for a switch/case with ternary operator (#7756) +* fix: `NoUselessConcatOperatorFixer` - do not remove new line (#7759) + +Changelog for v3.47.1 +--------------------- + +* fix: Do not override short name with relative reference (#7752) +* fix: make `BinaryOperatorSpacesFixer` work as pre-v3.47 (#7751) +* fix: Proper Docker image name suffix (#7739) +* fix: `FullyQualifiedStrictTypesFixer` - do not change case of the symbol when there's name collision between imported class and imported function (#7750) +* fix: `FullyQualifiedStrictTypesFixer` - do not modify statements with property fetch and `::` (#7749) + +Changelog for v3.47.0 +--------------------- + +* chore: better identify EXPERIMENTAL rules (#7729) +* chore: fix issue detected by unlocked PHPStan + upgrade dev-tools (#7678) +* chore: handle extract() (#7684) +* chore: Mention contributors in app info (#7668) +* chore: no need to mark private methods as internal (#7715) +* chore: ProjectCodeTests - dry for function usage extractions (#7690) +* chore: reduce PHPStan baseline (#7644) +* chore: use numeric literal separator for PHP version IDs (#7712) +* chore: use numeric_literal_separator for project (#7713) +* chore: Utils::sortElements - better typing (#7646) +* CI: Allow running Stale Bot on demand (#7711) +* CI: Fix PHP 8.4 (#7702) +* CI: Give write permissions to Stale Bot (#7716) +* CI: Use `actions/stale` v9 (#7710) +* docs: Add information about allowing maintainers to update PRs (#7683) +* docs: CONTRIBUTING.md - update Opening a PR (#7691) +* docs: Display/include tool info/version by default in commands and reports (#7733) +* DX: fix deprecation tests warnings for PHP 7.4 (#7725) +* DX: update `host.docker.internal` in Compose override template (#7661) +* DX: `NumericLiteralSeparatorFixer` - change default strategy to `use_separator` (#7730) +* feat: Add support for official Docker images of Fixer (#7555) +* feat: Add `spacing` option to `PhpdocAlignFixer` (#6505) +* feat: Add `union_types` option to `phpdoc_to_param_type`, `phpdoc_to_property_type`, and `phpdoc_to_return_type` fixers (#7672) +* feat: Introduce `heredoc_closing_marker` fixer (#7660) +* feat: Introduce `multiline_string_to_heredoc` fixer (#7665) +* feat: Introduce `NumericLiteralSeparatorFixer` (#6761) +* feat: no_superfluous_phpdoc_tags - support for arrow function (#7666) +* feat: Simplify closing marker when possible in `heredoc_closing_marker` fixer (#7676) +* feat: Support typed properties and attributes in `fully_qualified_strict_types` (#7659) +* feat: `@PhpCsFixer` ruleset - enable no_whitespace_before_comma_in_array.after_heredoc (#7670) +* fix: Improve progress bar visual layer (#7708) +* fix: indentation of control structure body without braces (#7663) +* fix: make sure all PHP extensions required by PHPUnit are installed (#7727) +* fix: PhpdocToReturnTypeFixerTest - support for arrow functions (#7645) +* fix: Several improvements for `fully_qualified_strict_types` (respect declared symbols, relative imports, leading backslash in global namespace) (#7679) +* fix: SimplifiedNullReturnFixer - support array return typehint (#7728) +* fix: Support numeric values without leading zero in `numeric_literal_separator` (#7735) +* fix: `BinaryOperatorSpacesFixer` - align correctly when multiple shifts occurs in single line (#7593) +* fix: `ClassReferenceNameCasingFixer` capitalizes the property name after the nullsafe operator (#7696) +* fix: `fully_qualified_strict_types` with `leading_backslash_in_global_namespace` enabled - handle reserved types in phpDoc (#7648) +* fix: `NoSpaceAroundDoubleColonFixer` must run before `MethodChainingIndentationFixer` (#7723) +* fix: `no_superfluous_phpdoc_tags` must honor multiline docs (#7697) +* fix: `numeric_literal_separator` - Handle zero-leading floats properly (#7737) +* refactor: increase performance by ~7% thanks to `Tokens::block*Cache` hit increased by ~12% (#6176) +* refactor: Tokens - fast check for non-block in 'detectBlockType', evaluate definitions only once in 'getBlockEdgeDefinitions' (#7655) +* refactor: `Tokens::clearEmptyTokens` - play defensive with cache clearing (#7658) +* test: ensure we do not forget to test any short_open_tag test (#7638) + +Changelog for v3.46.0 +--------------------- + +* chore: fix internal typehints in Tokens (#7656) +* chore: reduce PHPStan baseline (#7643) +* docs: Show class with unit tests and BC promise info (#7667) +* feat: change default ruleset to `@PER-CS` (only behind PHP_CS_FIXER_FUTURE_MODE=1) (#7650) +* feat: Support new/instanceof/use trait in `fully_qualified_strict_types` (#7653) +* fix: FQCN parse phpdoc using full grammar regex (#7649) +* fix: Handle FQCN properly with `leading_backslash_in_global_namespace` option enabled (#7654) +* fix: PhpdocToParamTypeFixerTest - support for arrow functions (#7647) +* fix: PHP_CS_FIXER_FUTURE_MODE - proper boolean validation (#7651) + +Changelog for v3.45.0 +--------------------- + +* feat: Enable symbol importing in `@PhpCsFixer` ruleset (#7629) +* fix: NoUnneededBracesFixer - improve handling of global namespace (#7639) +* test: run tests with "short_open_tag" enabled (#7637) + +Changelog for v3.44.0 +--------------------- + +* feat: Introduce percentage bar as new default progress output (#7603) + +Changelog for v3.43.1 +--------------------- + +* fix: Import only unique symbols' short names (#7635) + +Changelog for v3.43.0 +--------------------- + +* chore: change base of `@Symfony` set to `@PER-CS2.0` (#7627) +* chore: PHPUnit - allow for v10 (#7606) +* chore: Preg - rework catching the error (#7616) +* chore: Revert unneeded peer-dep-pin and re-gen lock file (#7618) +* docs: drop extra note about 8.0.0 bug in README.md (#7614) +* feat: add cast_spaces into `@PER-CS2.0` (#7625) +* feat: Configurable phpDoc tags for FQCN processing (#7628) +* feat: StatementIndentationFixer - introduce stick_comment_to_next_continuous_control_statement config (#7624) +* feat: UnaryOperatorSpacesFixer - introduce only_dec_inc config (#7626) +* fix: FullyQualifiedStrictTypesFixer - better support annotations in inline {} (#7633) +* fix: Improve how FQCN is handled in phpDoc (#7622) +* fix: phpdoc_align - fix multiline tag alignment issue (#7630) + +Changelog for v3.42.0 +--------------------- + +* chore: aim to not rely on internal array pointer but use array_key_first (#7613) +* chore: deprecate Token::isKeyCaseSensitive (#7599) +* chore: deprecate Token::isKeyCaseSensitive, 2nd part (#7601) +* chore: do not check PHP_VERSION_ID (#7602) +* chore: FileFilterIteratorTest - more accurate type in docs (#7542) +* chore: minor code cleanup (#7607) +* chore: more types (#7598) +* chore: PHPDoc key-value spacing (#7592) +* chore: PHPUnit - run defects first (#7570) +* chore: ProjectCodeTest - DRY on Tokens creation (#7574) +* chore: ProjectCodeTest - prepare for symfony/console v7 (#7605) +* chore: ProjectCodeTest::provide*ClassCases to return iterable with key for better tests execution log (#7572) +* chore: ProjectCodeTest::testDataProvidersDeclaredReturnType - use better DataProvider to simplify test logic (#7573) +* chore: TokensAnalyzer - string-enum for better typehinting (#7571) +* chore: unify tests not agnostic of PHP version (#7581) +* chore: use ::class more (#7545) +* CI: Introduce `composer-unused` (#7536) +* DX: add types to anonymous functions (#7561) +* DX: Allow running smoke tests within Docker runtime (#7608) +* DX: check fixer's options for wording (#7543) +* DX: cleanup deprecation message (#7576) +* DX: do not allow overriding constructor of `PHPUnit\Framework\TestCase` (#7563) +* DX: do not import ExpectDeprecationTrait in UtilsTest (#7562) +* DX: Enforce consistent naming in tests (#7556) +* DX: fix checking test class extends `PhpCsFixer\Tests\TestCase` (#7567) +* DX: make sure that exceptions in `AbstractFixerTestCase::testProperMethodNaming` are not already fixed (#7588) +* DX: remove recursion from AbstractIntegrationTestCase::testIntegration (#7577) +* DX: remove `PhpUnitNamespacedFixerTest::testClassIsFixed` (#7564) +* DX: remove `symfony/phpunit-bridge` (#7578) +* DX: replace fixture classes with anonymous ones (#7533) +* DX: Unify Docker mount points and paths (#7549) +* DX: unify fixer's test method names - quick wins (#7584) +* DX: unify tests for casing fixers (#7558) +* DX: use anonymous function over concrete classes (#7553) +* feat(EXPERIMENTAL): ClassKeywordFixer (#2918) +* feat(EXPERIMENTAL): ClassKeywordFixer, part 2 (#7550) +* feat(PhpdocToCommentFixer): Add option to handle return as valid docblock usage (#7401) (#7402) +* feat: Ability to import FQCNs found during analysis (#7597) +* feat: add phpDoc support for `fully_qualified_strict_types` fixer (#5620) +* feat: Handle deprecated rule sets similarly to deprecated fixers (#7288) +* feat: PhpUnitTestCaseStaticMethodCallsFixer - cover PHPUnit v10 methods (#7604) +* feat: Support more FQCNs cases in `fully_qualified_strict_types` (#7459) +* fix: AbstractFixerTestCase - fix checking for correct casing (#7540) +* fix: Better OS detection in integration tests (#7547) +* fix: NativeTypeDeclarationCasingFixe - handle static property without type (#7589) +* test: AutoReview - unify data provider returns (#7544) +* test: check to have DataProviders code agnostic of PHP version (#7575) + +Changelog for v3.41.1 +--------------------- + +* DX: Change `@testWith` to `@dataProvider` (#7535) +* DX: Introduce Markdownlint (#7534) +* fix: NativeTypeDeclarationCasingFixer - do not crash on `var` keyword (#7538) + +Changelog for v3.41.0 +--------------------- + +* chore: Move `mb_str_functions` PHP 8.3 cases to separate test (#7505) +* chore: Symfony v7 is now stable (#7469) +* CI: drop PHP 8.3 hacks (#7519) +* docs: Improve docs for `no_spaces_after_function_name` (#7520) +* DX: Ability to run Sphinx linter locally (#7481) +* DX: AbstractFixerTest - use anonymous classes (#7527) +* DX: Add progress output for `cs:check` script (#7514) +* DX: align doubles naming (#7525) +* DX: remove AbstractFixerTestCase::getTestFile() (#7495) +* DX: remove jangregor/phpstan-prophecy (#7524) +* DX: remove Prophecy (#7509) +* DX: replace Prophecy with anonymous classes in CacheTest (#7503) +* DX: replace Prophecy with anonymous classes in ProcessLintingResultTest (#7501) +* DX: Utilise auto-discovery for PHPStan formatter (#7490) +* feat: Support `mb_str_pad` function in `mb_str_functions` rule (#7499) +* fix: BinaryOperatorSpacesFixer - do not add whitespace inside short function (#7523) +* fix: Downgrade PDepend to version not supporting Symfony 7 (#7513) +* fix: GlobalNamespaceImportFixer - key in PHPDoc's array shape matching class name (#7522) +* fix: SpacesInsideParenthesesFixer - handle class instantiation parentheses (#7531) +* Update PHPstan to 1.10.48 (#7532) + +Changelog for v3.40.2 +--------------------- + +* docs: fix link to source classes (#7493) + +Changelog for v3.40.1 +--------------------- + +* chore: Delete stray file x (#7473) +* chore: Fix editorconfig (#7478) +* chore: Fix typos (#7474) +* chore: Fix YAML line length (#7476) +* chore: Indent JSON files with 4 spaces (#7480) +* chore: Make YAML workflow git-based (#7477) +* chore: Use stable XDebug (#7489) +* CI: Lint docs (#7479) +* CI: Use PHPStan's native Github error formatter (#7487) +* DX: fix PHPStan error (#7488) +* DX: PsrAutoloadingFixerTest - do not build mock in data provider (#7491) +* DX: PsrAutoloadingFixerTest - merge all data providers into one (#7492) +* DX: Update PHPStan to 1.10.46 (#7486) +* fix: `NoSpacesAfterFunctionNameFixer` - do not remove space if the opening parenthesis part of an expression (#7430) + +Changelog for v3.40.0 +--------------------- + +* chore: officially support PHP 8.3 (#7466) +* chore: update deps (#7471) +* CI: add --no-update while dropping non-compat `facile-it/paraunit` (#7470) +* CI: automate --ignore-platform-req=PHP (#7467) +* CI: bump actions/github-script to v7 (#7468) +* CI: move humbug/box out of dev-tools/composer.json (#7472) + +Changelog for v3.39.1 +--------------------- + +* DX: introduce SwitchAnalyzer (#7456) +* fix: NoExtraBlankLinesFixer - do not remove blank line after `? : throw` (#7457) +* fix: OrderedInterfacesFixer - do not comment out interface (#7464) +* test: Improve `ExplicitIndirectVariableFixerTest` (#7451) + +Changelog for v3.39.0 +--------------------- + +* chore: Add support for Symfony 7 (#7453) +* chore: IntegrationTest - move support of php< requirement to main Integration classes (#7448) +* CI: drop Symfony ^7 incompat exceptions of php-coveralls and cli-executor (#7455) +* CI: early compatibility checks with Symfony 7 (#7431) +* docs: drop list.rst and code behind it (#7436) +* docs: remove Gitter mentions (#7441) +* DX: Ability to run Fixer on PHP8.3 for development (#7449) +* DX: describe command - for rules, list also sets that are including them (#7419) +* DX: Docker clean up (#7450) +* DX: more usage of spaceship operator (#7438) +* DX: Put `Preg`'s last error message in exception message (#7443) +* feat: Introduce `@PHP83Migration` ruleset and PHP 8.3 integration test (#7439) +* test: Improve `AbstractIntegrationTestCase` description (#7452) + +Changelog for v3.38.2 +--------------------- + +* docs: fix 'Could not lex literal_block as "php". Highlighting skipped.' (#7433) +* docs: small unification between FixerDocumentGenerator and ListDocumentGenerator (#7435) +* docs: unify ../ <> ./../ (#7434) + +Changelog for v3.38.1 +--------------------- + +* chore: ListSetsCommand::execute - add missing return type (#7432) +* chore: PHPStan - add counter to dataProvider exception, so we do not increase the tech debt on it (#7425) +* CI: Use `actions/checkout` v4 (#7423) +* fix: ClassAttributesSeparationFixer - handle Disjunctive Normal Form types parentheses (#7428) +* fix: Remove all variable names in `@var` callable signature (#7429) +* fix: Satisfy `composer normalize` (#7424) + +Changelog for v3.38.0 +--------------------- + +* chore: upgrade phpstan (#7421) +* CI: add curl and mbstring to build php (#7409) +* CI: cache dev-tools/bin (#7416) +* CI: Composer - move prefer-stable to file config (#7406) +* CI: conditionally install flex (#7412) +* CI: dev-tools/build.sh - no need to repeat 'prefer-stable', but let's use '--no-scripts' (#7408) +* CI: Do not run post-autoload-dump on Composer install (#7403) +* CI: general restructure (#7407) +* CI: GitHub Actions - use actions/cache for Composer in composite action (#7415) +* CI: Improve QA process - suplement (#7411) +* CI: prevent Infection plugins during build time, as we do not use it (#7422) +* CI: simplify setup-php config (#7404) +* DX: Do not mark as stale issues/PRs with milestone assigned (#7398) +* DX: Improve QA process (#7366) +* feat: phpDoc to property/return/param Fixer - allow fixing mixed on PHP >= 8 (#6356) +* feat: phpDoc to property/return/param Fixer - allow fixing union types on PHP >= 8 (#6359) +* feat: Support for array destructuring in `array_indentation` (#7405) +* feat: `@Symfony` - keep Annotation,NamedArgumentConstructor,Target annotations as single group (#7399) +* fix(SelfAccessorFixer): do not touch references inside lambda and/or arrow function (#7349) +* fix: long_to_shorthand_operator - mark as risky fixer (#7418) +* fix: OrderedImportsFixer - handle non-grouped list of const/function imports (#7397) + +Changelog for v3.37.1 +--------------------- + +* docs: config file - provide better examples (#7396) +* docs: config file - provide better link to Finder docs (#6992) + +Changelog for v3.37.0 +--------------------- + +* feat: add parallel cache support (#7131) + +Changelog for v3.36.0 +--------------------- + +* chore: disable `infection-installer` plugin, as we do not use `infection/*` yet (#7391) +* chore: Run dev-tools on PHP 8.2 (#7389) +* CI: Run Symfony 6 compat check on PHP 8.1 (#7383) +* CI: use fast-linter when calculating code coverage (#7390) +* docs: extend example for nullable_type_declaration (#7381) +* DX: FixerFactoryTest - make assertion failing msg more descriptive (#7387) +* feat: PhpdocSummaryFixer - support lists in description (#7385) +* feat: PSR12 - configure unary_operator_spaces (#7388) +* feat: StatementIndentationFixer - support comment for continuous control statement (#7384) + +Changelog for v3.35.1 +--------------------- + +* fix: Mark `PhpdocReadonlyClassCommentToKeywordFixer` as risky (#7372) + +Changelog for v3.35.0 +--------------------- + +* chore: Autoreview: test all formats are listed in `usage.rst` (#7357) +* chore: no need for `phpunitgoodpractices/traits` anymore (#7362) +* chore: Rename `indexes` to `indices` (#7368) +* chore: stop using `phpunitgoodpractices/traits` (#7363) +* chore: typo (#7367) +* docs: Sort options in documentation (#7345) +* feat(PhpdocReadonlyClassCommentToKeywordFixer): Introduction (#7353) +* feat: Ability to keep/enforce leading `\` when in global namespace (#7186) +* feat: Update `@PER-CS2.0` to match short closure space (#6970) +* feat: use `ordered_types` in `@PhpCsFixer` (#7361) +* fix(SingleLineThrowFixer): fixer goes out of range on close tag (#7369) + +Changelog for v3.34.1 +--------------------- + +* deps: revert "prevent using PHPCSFixer along with unfinalize package (#7343)" (#7348) + +Changelog for v3.34.0 +--------------------- + +* feat: Introduce `check` command (alias for `fix --dry-run`) (#7322) + +Changelog for v3.33.0 +--------------------- + +* feat: Introduce `native_type_declaration_casing` fixer (#7330) + +Changelog for v3.32.0 +--------------------- + +* deps: Prevent using PHPCSFixer along with `unfinalize` package (#7343) +* feat: Deprecate `CompactNullableTypehintFixer` and proxy to `CompactNullableTypeDeclarationFixer` (#7339) +* feat: Deprecate `CurlyBracesPositionFixer` and proxy to `BracesPositionFixer` (#7334) +* feat: Deprecate `NewWithBracesFixer` and proxy to `NewWithParenthesesFixer` (#7331) +* feat: Deprecate `NoUnneededCurlyBracesFixer` and proxy to `NoUnneededBracesFixer` (#7335) +* feat: Rename `CurlyBraceTransformer` to `BraceTransformer` (#7333) + +Changelog for v3.31.0 +--------------------- + +* chore: Use type declaration instead of type hint (#7338) +* feat: Introduce `attribute_placement` option for `MethodArgumentSpaceFixer` (#7320) +* fix: Adjust wording related to deprecations (#7332) +* fix: Correct deprecation header in rules' docs (#7337) +* fix: Replace mention of bracket with parenthesis (#7336) +* fix: `FunctionToConstantFixer` should run before `NativeConstantInvocationFixer` (#7344) + +Changelog for v3.30.0 +--------------------- + +* feat: Introduce `AttributeEmptyParenthesesFixer` (#7284) +* fix(method_argument_space): inject new line after trailing space on current line (#7327) +* fix(`YodaStyleFixer`): do not touch `require(_once)`, `include(_once)` and `yield from` statements (#7325) +* fix: illegal offset type on file-wide return in `ReturnToYieldFromFixer` (#7318) + +Changelog for v3.29.0 +--------------------- + +* chore: fix TODO tasks about T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG support (#7316) +* feat(`@PhpCsFixer:risky`): use newest `@PER-CS:risky` instead of locked `@PER-CS2.0:risky` (#7323) +* feat: Introduce `@PER-CS` ruleset (#7321) +* fix: priority issue between array_syntax and space after rules (#7324) + +Changelog for v3.28.0 +--------------------- + +* chore(prlint): allow for 'deps' type (#7304) +* CI(prlint): allow for special chars in parentheses (#7308) +* deps(dev-tools): update dev-tools (#7309) +* DX: Bump XDebug version in Docker services (#7300) +* feat(`@PER-CS2.0`): Add `concat_space` to the ruleset (#7302) + +Changelog for v3.27.0 +--------------------- + +* docs: cleanup old mention of `--show-progress=estimating` in docs (#7287) +* DX: add Composer script for applying CS fixes in parallel (#7274) +* feat: Clone PER-CS1.0 to PER-CS2.0 to prepare for adding new rules (#7249) +* feat: Introduce `LongToShorthandOperatorFixer` (#7295) +* feat: Mark PER-CS v1 as deprecated (#7283) +* feat: Move `single_line_empty_body` to `@PER-CS2.0` (#7282) +* fix: Priorities for fixers related to curly braces, empty lines and trailing whitespace (#7296) +* fix: `OrderedTraitsFixer` - better support for multiple traits in one `use` statement (#7289) + +Changelog for v3.26.1 +--------------------- + +* fix: Handle superfluous asterisk in `no_superfluous_phpdoc_tags` (#7279) + +Changelog for v3.26.0 +--------------------- + +* chore(checkbashisms): update to 2.23.6 (#7276) +* chore(phpstan): reduce baseline (#7275) +* feat: Add `single_line_empty_body` to `@PhpCsFixer` (#7266) +* fix(YieldFromArrayToYieldsFixer): mark as Risky (#7272) +* fix(YieldFromArrayToYieldsFixer): skip touching empty array (#7273) +* test: Introduce common way of creating fake Composer project in `InstallViaComposerTest` (#7265) + +Changelog for v3.25.1 +--------------------- + +* fix: PhpdocTypesFixer - do not crash for type followed by braces/brackets/chevrons/parentheses (#7268) + +Changelog for v3.25.0 +--------------------- + +* feat: Remove Doctrine dependencies (#7263) + +Changelog for v3.24.0 +--------------------- + +* chore: apply CS (#7240) +* chore: apply static_lambda rule (#7239) +* chore: Improve template for creating new issue (#7255) +* CI: Conventional Commits support in PRLint config (#7037) +* CI: Remove Travis leftovers (#7259) +* docs: Add information about installing Fixer as dev dependency (#7129) +* docs: document composer script aliases (#7230) +* DX: Add script for running Composer Require Checker (#7252) +* DX: composer script aliases - ensure templated description (#7235) +* DX: composer-script - count PHPMD as static-analysis (#7231) +* DX: do not allow version specific code sample with minimum PHP version lower than the lowest supported one (#7207) +* DX: ensure version specific code samples are suitable for at least 1 supported PHP version (#7212) +* DX: Improve contributing guide (#7241) +* DX: More descriptive stale messages (#7236) +* feat(@PhpCsFixer:risky): add static_lambda rule (#7237) +* feat: Add literal separator support for `integer_literal_case` (#7081) +* feat: Configurable case sensitivity for more ordering fixers (#7021) +* feat: Support for attributes in `method_argument_space` (#7242) +* fix: import detection for attributes at `NoUnusedImportsFixer` (#7246) +* fix: `no_superfluous_phpdoc_tags` with `null` phpdoc (#7234) +* fix: `phpdoc_types` must not lowercase literal types (#7108) +* test: Add static methods from PHPUnit 9.6.11 (#7243) + +Changelog for v3.23.0 +--------------------- + +* bug: BlankLineBeforeStatementFixer - do not enforce/add a blank line when there is a blank line between the comment and the statement already (#7190) +* bug: Fix detecting classy invocation in catch (#7191) +* bug: Fix names resolving in `no_superfluous_phpdoc_tags` fixer (#7189) +* bug: Fix various bugs in `FullyQualifiedStrictTypesFixer` fixer (#7188) +* bug: Fixed line between general script documentation and require (#7177) +* bug: Support annotations with arguments in `FinalInternalClassFixer` (#7160) +* bug: YieldFromArrayToYieldsFixer - fix for `yield from` after `}` (#7169) +* bug: YieldFromArrayToYieldsFixer - fix handling the comment before the first array element (#7193) +* bug: `HeaderCommentFixer` must run before `BlankLinesBeforeNamespaceFixer` (#7205) +* bug: `NoUselessReturnFixer` must run before `SingleLineEmptyBodyFixer` (#7226) +* bug: `PhpdocInlineTagNormalizerFixer` - do not break tags (#7227) +* docs: Add allowed values of tags in the `phpdoc_align` (#7120) +* docs: Add extra information for GitLab reporter's integration with GitLab Code Quality (#7172) +* docs: Change the single backticks to double in description of the rules option (#7173) +* docs: Condensed output for rule sets' list that fixer is included in (#7182) +* docs: Improve contributing guide (#7204) +* docs: `MethodArgumentSpaceFixer` - mention PSR in Fixer definition (#7157) +* DX: add first auto-review tests for composer.json file (#7210) +* DX: add `YieldFromArrayToYieldsFixer` to `PhpCsFixer` set (#7115) +* DX: Allow OS conditions for integration tests (#7161) +* DX: Apply current CS rules (#7178) +* DX: Apply suggestions from PR 7210 (#7213) +* DX: apply `ReturnToYieldFromFixer` (#7181) +* DX: Do not mark "long term ideas" as stale (#7206) +* DX: enable `HeredocIndentationFixer` for the codebase (#7195) +* DX: enable `UseArrowFunctionsFixer` for the codebase (#7194) +* DX: few phpstan fixes (#7208) +* DX: fix contravariant types in PHPDocs (#7167) +* DX: Fix detecting trailing spaces (#7216) +* DX: Fix some PHPStan issues (#7180) +* DX: Get rid of deprecation warnings in Mess Detector (#7215) +* DX: Improve Composer scripts (#7214) +* DX: Improve Mess Detector Integration (#7224) +* DX: Introduce Composer scripts as common DX (#6839) +* DX: refactor `ErrorOutputTest` (#7183) +* DX: remove unnecessary arrays from data providers (#7170) +* DX: update `CurlyBracesPositionFixer` code samples (#7198) +* DX: update `HeredocIndentationFixer` code samples (#7197) +* DX: update `PhpdocToReturnTypeFixer` code samples (#7199) +* feature: add at least one space around binary operators (#7175) +* feature: BlankLineBeforeStatementFixer - take into account comment before statement (#7166) +* feature: Introduce `ReturnToYieldFromFixer` (#7168) +* feature: Introduce `SpacesInsideParenthesesFixer` (#5709) +* feature: Support array destructuring in `trim_array_spaces` (#7218) +* feature: `BlankLineBeforeStatementFixer` - skip enum cases (#7203) +* minor: more arrow function usage (#7223) +* minor: PhpdocAlignFixerTest - convert CUSTOM tags test to not rely on non-custom tag from TAGS_WITH_NAME (#7209) +* minor: use JSON_THROW_ON_ERROR for trivial cases (#7221) +* minor: use more spread operator (#7222) + +Changelog for v3.22.0 +--------------------- + +* DX: add proper test for `SelfAccessorFixer` must run before `SelfAccessorFixer` (#7153) +* DX: FixerFactoryTest - apply CS (#7154) +* feature: Introduce `PhpUnitDataProviderReturnTypeFixer` (#7156) +* feature: Introduce `YieldFromArrayToYieldsFixer` (#7114) + +Changelog for v3.21.3 +--------------------- + +* Revert "DX: encourage to provide wider description" (#7155) + +Changelog for v3.21.2 +--------------------- + +* docs: check format of FixerDefinition::getDescription() (#7127) +* DX: add phpstan/phpstan-strict-rules (#7143) +* DX: allow for progressive cache (#7132) +* DX: Copy-pasteable `class::getPriority` for phpDoc diffs (#7148) +* DX: do not allow linebreak at the beginning of code sample (#7126) +* DX: encourage to provide wider description (#7128) +* DX: fix function calls (#7136) +* DX: fix PHPDoc types issues (#7135) +* DX: improve `Tokens` checking for found tokens (#7139) +* DX: Make `AbstractFixerTestCase::getTestFile()` final (#7116) +* DX: make `array_search` call strict (#7145) +* DX: remove `empty` calls (#7138) +* DX: store cache to file only if content will get modified (#7151) +* DX: unify Preg:match in logical conditions (#7146) +* DX: use booleans in conditions (#7149) +* DX: Use ParaUnit to speed up tests (#6883) +* DX: Use relative fixture path as integration test case's name (#7147) +* DX: use strict assertions (#7144) +* DX: `AbstractIntegrationTestCase::provideIntegrationCases` - yield over array, better typehinting (#7150) + +Changelog for v3.21.1 +--------------------- + +experimental release + +* Require PHP ^8.0.1 + +Changelog for v3.21.0 +--------------------- + +* bug: Fix and enhance Gitlab reporter (#7089) +* bug: Import with different case must not be removed by non-risky fixer (#7095) +* bug: ordered imports fixer top group only (#7023) +* bug: `FinalPublicMethodForAbstractClassFixer` - fix for readonly classes (#7123) +* DX: do not nest ".editorconfig" files (#7112) +* DX: exclude Dockerfile from dist (#7113) +* DX: fix checkbashisms installation (#7102) +* DX: fix Smoke tests for various git default branch name (#7119) +* DX: Fix `FileRemovalTest` (do not fail when running it standalone) (#7104) +* DX: Progress output refactor (#6848) +* DX: Rename abstract test classes to `*TestCase` convention (#7100) +* DX: test all PHPUnit migration sets (#7107) +* DX: [Docker] Distinguish Alpine version between PHP versions (#7105) +* feature: Create cache path if it does not exist (#7109) +* feature: Introduce `NullableTypeDeclarationFixer` (#7002) +* feature: Introduce `TypeDeclarationSpacesFixer` (#7001) +* feature: `BlankLineBetweenImportGroupsFixer` - keep indent (#7122) +* minor: Parse callable using full phpdoc grammar (#7094) +* minor: PHP8.3 const type tokenizing (#7055) + +Changelog for v3.20.0 +--------------------- + +* DX: fix priority of `FinalClassFixer` (#7091) +* DX: use FAST_LINT_TEST_CASES=1 for CI run on macOS (#7092) +* feature: SingleLineEmptyBodyFixer - support interfaces, traits and enums (#7096) +* feature: `NullableTypeDeclarationForDefaultNullValue` - support for nullability in union types (#5819) + +Changelog for v3.19.2 +--------------------- + +* bug: NoMultipleStatementsPerLineFixer must run before CurlyBracesPositionFixer (#7087) +* bug: PhpdocAddMissingParamAnnotationFixer - fix for promoted properties (#7090) +* DX: fix priority of SingleBlankLineBeforeNamespaceFixer (#7088) +* minor: Parse all phpdoc types using full grammar (#7010) + +Changelog for v3.19.1 +--------------------- + +* bug: CurlyBracesPositionFixer must run before StatementIndentationFixer (#7085) + +Changelog for v3.19.0 +--------------------- + +* bug: SelfAccessorFixer - fix for union types (#7080) +* DX: add `php_unit_data_provider_name` to `@PhpCsFixer:risky` set (#7069) +* DX: make data providers return type "iterable" (#7072) +* DX: rename tests and data providers (#7070) +* feature: Introduce `PhpUnitDataProviderNameFixer` (#7057) + +Changelog for v3.18.0 +--------------------- + +* bug: Fix tokenizing of type hints (#7054) +* bug: CompactNullableTypehintFixer - fix for whitespace between `?` and `static` (#6993) +* bug: consider function modifiers for `statement_indentation` (#6978) +* bug: Exclude `$this` from `TernaryToNullCoalescingFixer` (#7052) +* bug: False positive on used imports when docblock includes it with mismatching case (#6909) +* bug: Fix chained calls semicolon indent in switch case (#7045) +* bug: Fix multiline_whitespace_before_semicolons for echo tags (#7019) +* bug: Fix phpDoc align when there is inconsistent spacing after comment star (#7012) +* bug: Fix phpDoc parsing without PCRE JIT (#7031) +* bug: Fix PhpdocVarWithoutNameFixer with Closure with $this (#6979) +* bug: Fix `return_assignment` not formatting when variables are used in `catch` and `finally` (#6960) +* bug: Fix `TypeExpression::allowsNull()` with nullable (#7000) +* bug: Improve definition of conflicting fixers (#7066) +* bug: LambdaNotUsedImportFixer - fix for anonymous class with a string argument (#6972) +* bug: ListFilesCommand - fix computing of relative path (#7028) +* bug: make `php_unit_namespaced` less greedy (#6952) +* bug: PhpdocToCommentFixer - fix for PHPDoc before fn (#6973) +* bug: Restructure PER-CS rule sets (#6707) +* bug: SelfStaticAccessor - fix static access inside enums (#7024) +* bug: SingleSpaceAroundConstructFixer - fix more cases involving `static` (#6995) +* bug: `FullyQualifiedStrictTypesFixer` - fix shortening when namespace is not empty and import exists (#7027) +* bug: `NoUnneededControlParenthesesFixer` PHP8.0 null-safe operator (#7056) +* bug: `PhpdocToCommentFixer` support for enum cases (#7040) +* DX: add more tests to CommentsAnalyzer (#7041) +* DX: Cleanup duplicate files in finder (#7042) +* DX: ControlCaseStructuresAnalyzerTest - cleanup (#6874) +* DX: Fix warning when running test on PHP<8 (#7008) +* DX: handle `@` in PR title (#6982) +* DX: officially deprecate internal Utils anti-pattern class (#7039) +* DX: Remove Fabbot.io conditional configuration (#7038) +* DX: rename data providers (#7058) +* DX: Use `actions/stale` to handle stale issues and pull requests (#5085) +* DX: Use `Utils::naturalLanguageJoin()` in implode calls (#7032) +* feature: Add support for custom method placement in `ordered_class_elements` (#6360) +* feature: Allow case sensitive order for OrderedClassElementsFixer (#7020) +* feature: PHP8.3 - Add CT and block type for `Dynamic class constant fetch` (#7004) +* feature: Support attributes in `FinalClassFixer` (#6893) +* minor: "Callback" must not be fixed to "callback" by default (#7011) +* minor: Add `Util::naturalLanguageJoin()` (#7022) +* minor: ClassDefinitionFixer - handle attributes and `readonly` in anonymous class definitions (#7014) +* minor: FixerFactory::getFixersConflicts - better type hinting (#7044) +* minor: PHP8.3 - Fix TokensAnalyzer::isAnonymousClass support for `readonly` (#7013) +* minor: PHP8.3 - Typed class constants - handle nullable by transformer (#7009) +* minor: Reduce phpDoc type parser complexity from O(n^2) to O(nlog(n)) (#6988) +* minor: ReturnAssignmentFixer - Better handling of anonymous classes (#7015) +* minor: Transfer `HelpCommand::toString()` to `Utils` (#7034) +* minor: Unify "blank lines before namespace" fixers (#7053) +* minor: `SelfStaticAccessorFixer` improvements for enums (#7026) +* minor: `SingleSpaceAroundConstructFixer` - support space before `as` (#7029) +* minor: `UseArrowFunctionsFixer` - run before `FunctionDeclarationFixer` (#7065) + +Changelog for v3.17.0 +--------------------- + +* bug: Allow string quote to be escaped within phpdoc constant (#6798) +* bug: ConfigurationResolver - fix running without cache (#6915) +* bug: Fix array/object shape phpdoc type parse (#6962) +* bug: Fix FullyQualifiedStrictTypesFixer common prefix bug (#6898) +* bug: Fix non-parenthesized callable return type parse (#6961) +* bug: Fix parsing of edge cases phpdoc types (#6977) +* bug: FullyQualifiedStrictTypesFixer - fix for FQCN type with class with the same name being imported (#6923) +* bug: GroupImportFixer - support for aliased imports (#6951) +* bug: MultilineWhitespaceBeforeSemicolonsFixer - fix chained calls (#6926) +* bug: MultilineWhitespaceBeforeSemicolonsFixer - fix for discovering multi line calls (#6938) +* bug: NoBreakCommentFixer - fix for nested match (#6899) +* bug: NoExtraBlankLinesFixer - fix for attribute in abstract function (#6920) +* bug: PhpdocTypesFixer - handle types with no space between type and variable (#6922) +* bug: PhpUnitMockShortWillReturnFixer - fix for trailing commas (#6900) +* bug: StatementIndentationFixer - fix comments at the end of if/elseif/else blocks (#6918) +* bug: StatementIndentationFixer - fix for multiline arguments starting with "new" keyword (#6913) +* bug: StatementIndentationFixer - fix for multiline arguments starting with "new" keyword preceded by class instantiation (#6914) +* bug: VoidReturnFixer - fix for intervening attributes (#6863) +* docs: improve code samples for MultilineWhitespaceBeforeSemicolonsFixer (#6919) +* docs: improve cookbook (#6880) +* DX: add cache related tests (#6916) +* DX: Apply `self_static_accessor` fixer to the project (again) (#6927) +* DX: cancel running builds on subsequent pushes in CI (#6940) +* DX: convert more `static` to `self` assert calls (#6931) +* DX: fix GitHub Actions errors and warnings (#6917) +* DX: fix Unsafe call to private method errors reported by PHPStan (#6879) +* DX: Improve performance of FunctionsAnalyzer (#6939) +* DX: improve test method names to avoid confusion (#6974) +* DX: Include self_static_accessor fixer in PhpCsFixer set (#6882) +* DX: make data providers static with straight-forward changes (#6907) +* DX: Mark Tokens::getNamespaceDeclarations as @internal (#6949) +* DX: PHPStan improvements (#6868) +* DX: refactor PhpdocAlignFixerTest (#6925) +* DX: Remove @inheritdoc PHPDoc (#6955) +* DX: Run AutoReview tests only once (#6889) +* DX: simplify EncodingFixer (#6956) +* DX: update Symfony rule set (#6958) +* DX: Use $tokens->getNamespaceDeclarations() to improve performance (#6942) +* DX: use force option for php_unit_data_provider_static in PHPUnit 10.0 migration set (#6908) +* DX: use only PHP modules that are required (#6954) +* DX: use PHPUnit's "requires" instead of "if" condition (#6975) +* feature: Add align_multiline_comment rule to @Symfony (#6875) +* feature: Add no_null_property_initialization rule to @Symfony (#6876) +* feature: Add operator_linebreak rule to @Symfony (#6877) +* feature: add SingleLineEmptyBodyFixer (#6933) +* feature: DescribeCommand - allow describing custom fixers (#6957) +* feature: Introduce `OrderedTypesFixer` (#6571) +* feature: Order of PHPDoc @param annotations (#3909) +* feature: Parse parenthesized & conditional phpdoc type (#6796) +* feature: PhpUnitInternalClassFixer - add empty line before PHPDoc (#6924) +* feature: [PhpdocAlignFixer] Add support for every tag (#6564) +* minor: align NoSuperfluousPhpdocTagsFixer with actual Symfony configuration (#6953) +* minor: do not add empty line in PHPDoc when adding annotation in PHPUnit class (#6928) +* minor: PhpdocAlignFixer - support cases with type and variable separated with no space (#6921) +* minor: PhpdocSeparationFixer - add integration tests (#6929) +* minor: update PHPStan (to fix CI on master branch) (#6901) +* minor: Use single Dockerfile with multiple build targets (#6840) + +Changelog for v3.16.0 +--------------------- + +* bug: ControlStructureBracesFixer - handle closing tag (#6873) +* bug: CurlyBracesPositionFixer - fix for callable return type (#6855) +* bug: CurlyBracesPositionFixer - fix for DNF types (#6859) +* bug: Fix MultilineWhitespaceBeforeSemicolonsFixer (#5126) +* docs: Fix rule description (#6844) +* DX: fix checkbashisms installation (#6843) +* DX: make data providers static for fixer's tests (#6860) +* DX: refactor PHPUnit fixers adding class-level annotation to use shared code (#6756) +* DX: unify option's descriptions (#6856) +* feature: AbstractPhpUnitFixer - support attribute detection in docblock insertion (#6858) +* feature: add "force" option to PhpUnitDataProviderStaticFixer (#6757) +* feature: introduce single_space_around_construct, deprecate single_space_after_construct (#6857) +* feature: PhpUnitTestClassRequiresCoversFixer - support single-line PHPDocs (#6847) +* minor: Deprecate BracesFixer (#4885) +* minor: Fix autocompletion for `Tokens::offsetGet()` (#6838) +* minor: PHP8.2 Docker runtime (#6833) +* minor: Use Composer binary-only images instead of installer script (#6834) + +Changelog for v3.15.1 +--------------------- + +* bug: BinaryOperatorSpacesFixer - fix for static in type (#6835) +* bug: BinaryOperatorSpacesFixer - fix parameters with union types passed by reference (#6826) +* bug: NoUnusedImportsFixer - fix for splat operator (#6836) +* DX: fix CI (#6837) +* feature: Support for type casing in arrow functions (#6831) +* minor: fix CI on PHP 8.3 (#6827) + +Changelog for v3.15.0 +--------------------- + +* bug: VisibilityRequiredFixer - handle DNF types (#6806) +* DX: officially enable 8.2 support (#6825) + +Changelog for v3.14.5 +--------------------- + +* bug: EmptyLoopBodyFixer must keep comments inside (#6800) +* bug: FunctionsAnalyzer - fix detecting global function (#6792) +* bug: NativeFunctionTypeDeclarationCasingFixer - do not require T_STRING present in code (#6812) +* bug: PhpdocTypesFixer - do not change case of array keys (#6810) +* bug: PhpUnitTestAnnotationFixer - do not break single line @depends (#6824) +* docs: Add supported PHP versions section to the README (#6768) +* docs: drop Atom from readme, due to it's sunsetting (#6778) +* DX: Add composer keywords (#6781) +* DX: update PHPStan to 1.10.3 (#6805) +* feature: [PHP8.2] Support for readonly classes (#6745) +* minor: add custom tokens for Disjunctive Normal Form types parentheses (#6823) +* minor: PHP8.2 - handle union and intersection types for DNF types (#6804) +* minor: PHP8.2 - support property in const expressions (#6803) + +Changelog for v3.14.4 +--------------------- + +* bug: CurlyBracesPositionFixer - fix for open brace not preceded by space and followed by a comment (#6776) +* docs: drop license end year (#6767) +* DX: use numeric_literal_separator (#6766) +* feature: Allow installation of `sebastian/diff:^5.0.0` (#6771) + +Changelog for v3.14.3 +--------------------- + +* DX: Drop doctrine/annotations 1, allow doctrine/lexer 3 (#6730) + +Changelog for v3.14.2 +--------------------- + +* DX: Drop support for doctrine/lexer 1 (#6729) + +Changelog for v3.14.1 +--------------------- + +* DX: Allow doctrine/annotations 2 (#6721) + +Changelog for v3.14.0 +--------------------- + +* bug: Fix indentation for comment at end of function followed by a comma (#6542) +* bug: Fix PHPDoc alignment fixer containing callbacks using `\Closure` (#6746) +* bug: Fix type error when using paths intersection mode (#6734) +* bug: PhpdocSeparationFixer - Make groups handling more flexible (#6668) +* docs: make bug_report.md template more explicit (#6736) +* docs: PhpUnitTestCaseIndicator - fix docs (#6727) +* DX: apply CS (#6759) +* DX: bump doctrine/annotations to prevent installing version with unintentional BC break (#6739) +* DX: update deps (#6760) +* DX: upgrade dev-tools/composer.json (#6737) +* DX: upgrade PHPStan to 1.9.7 (#6741) +* feature: Add php 7.4 types to Cookbook docs (#6763) +* feature: add PhpUnitDataProviderStaticFixer (#6702) +* feature: binary_operator_spaces - Revert change about => alignment and use option instead (#6724) +* feature: make OrderedInterfacesFixer non-risky (#6722) +* feature: OctalNotationFixer - support _ notation (#6762) +* fix: enum case "PARENT" must not be renamed (#6732) +* minor: Follow PSR12 ordered imports in Symfony ruleset (#6712) +* minor: improve rule sets order (#6738) + +Changelog for v3.13.2 +--------------------- + +* bug: Fix type error when using paths intersection mode (#6734) + +Changelog for v3.13.1 +--------------------- + +* bug: Align all the arrows inside the same array (#6590) +* bug: Fix priority between `modernize_types_casting` and `no_unneeded_control_parentheses` (#6687) +* bug: TrailingCommaInMultilineFixer - do not add trailing comma when there is no break line after last element (#6677) +* docs: Fix docs for disabled rules in rulesets (#6679) +* docs: fix the cookbook_fixers.rst (#6672) +* docs: Update installation recommended commands for `mkdir` argument (`-p` insteadof `--parents`). (#6689) +* Make static data providers that are not using dynamic calls (#6696) +* minor: displaying number of checked files (#6674) + +Changelog for v3.13.0 +--------------------- + +* bug: BracesFixer - Fix unexpected extra blank line (#6667) +* bug: fix CI on master branch (#6663) +* bug: IsNullFixer - handle casting (#6661) +* docs: feature or bug (#6652) +* docs: Use case insensitive sorting for options (#6666) +* docs: [DateTimeCreateFromFormatCallFixer] Fix typos in the code sample (#6671) +* DX: update cli-executor (#6664) +* DX: update dev-tools (#6665) +* feature: Add global_namespace_import to @Symfony ruleset (#6662) +* feature: Add separate option for closure_fn_spacing (#6658) +* feature: general_phpdoc_annotation_remove - allow add case_sensitive option (#6660) +* minor: AllowedValueSubset - possible values are sorted (#6651) +* minor: Use md5 for file hashing to reduce possible collisions (#6597) + +Changelog for v3.12.0 +--------------------- + +* bug: SingleLineThrowFixer - Handle throw expression inside block (#6653) +* DX: create TODO to change default ruleset for v4 (#6601) +* DX: Fix SCA findings (#6626) +* DX: HelpCommand - fix docblock (#6584) +* DX: Narrow some docblock types (#6581) +* DX: Remove redundant check for PHP <5.2.7 (#6620) +* DX: Restore PHPDoc to type rules workflow step (#6615) +* DX: SCA - scope down types (#6630) +* DX: Specify value type in iterables in tests (#6594) +* DX: Test on PHP 8.2 (#6558) +* DX: Update GitHub Actions (#6606) +* DX: Update PHPStan (#6616) +* feature: Add `@PHP82Migration` ruleset (#6621) +* feature: ArrayPushFixer now fix short arrays (#6639) +* feature: NoSuperfluousPhpdocTagsFixer - support untyped and empty annotations in phpdoc (#5792) +* feature: NoUselessConcatOperatorFixer - Introduction (#6447) +* feature: Support for constants in traits (#6607) +* feature: [PHP8.2] Support for new standalone types (`null`, `true`, `false`) (#6623) +* minor: GitHub Workflows security hardening (#6644) +* minor: prevent BC break in ErrorOutput (#6633) +* minor: prevent BC break in Runner (#6634) +* minor: Revert "minor: prevent BC break in Runner" (#6637) +* minor: Update dev tools (#6554) + +Changelog for v3.11.0 +--------------------- + +* bug: DateTimeCreateFromFormatCallFixer - Mark as risky (#6575) +* bug: Do not treat implements list comma as array comma (#6595) +* bug: Fix MethodChainingIndentationFixer with arrow functions and class instantiation (#5587) +* bug: MethodChainingIndentationFixer - Fix bug with attribute access (#6573) +* bug: NoMultilineWhitespaceAroundDoubleArrowFixer - fix for single line comment (#6589) +* bug: TypeAlternationTransformer - TypeIntersectionTransformer - Bug: handle attributes (#6579) +* bug: [BinaryOperatorFixer] Fix more issues with scoped operators (#6559) +* docs: Remove `$` from console command snippets (#6600) +* docs: Remove `$` from console command snippets in documentation (#6599) +* DX: AllowedValueSubset::getAllowedValues - fix method prototype (#6585) +* DX: Narrow docblock types in FixerConfiguration (#6580) +* DX: updagte @PhpCsFixer set config for phpdoc_order rule (#6555) +* DX: Update PHPUnit config (#6566) +* feature: Introduce configurability to PhpdocSeparationFixer (#6501) +* feature: Introduce PER set (#6545) +* feature: NoTrailingCommaInSinglelineFixer - Introduction (#6529) +* feature: Support removing superfluous PHPDocs involving `self` (#6583) +* minor: NoUnneededControlParenthesesFixer - Support instanceof static cases (#6587) +* minor: PhpdocToCommentFixer - allow phpdoc comments before trait use statement. Fixes #6092 (#6565) + +Changelog for v3.10.0 +--------------------- + +* bug: Fix error in `regular_callable_call` with static property (#6539) +* bug: Fix indentation for multiline class definition (#6540) +* bug: Fix indentation for switch ending with empty case (#6538) +* bug: Fix indentation of comment at end of switch case (#6493) +* bug: PhpdocAlignFixer - fix static `@method` (#6366) +* bug: SingleSpaceAfterConstructFixer - fix handling open tag (#6549) +* bug: VisibilityRequiredFixer must run before ClassAttributesSeparationFixer (#6548) +* DX: Assert dataproviders of tests of project itself return "array" or "iterable". (#6524) +* feature: Introduce configurability to PhpdocOrderFixer (#6466) +* feature: WhitespaceAfterCommaInArrayFixer - add option "ensure_single_space" (#6527) +* minor: Add test for indentation of trait conflict resolution (#6541) +* minor: Split BracesFixer (#4884) +* minor: TrailingCommaInMultilineFixer - Add comma to multiline `new static` (#6380) + +Changelog for v3.9.6 +-------------------- + +* bug: BinaryOperatorSpacesFixer: Solve issues with scoped arrow and equal alignments (#6515) +* bug: Fix 3 weird behavior about BinaryOperatorSpacesFixer (#6450) +* docs: Add intersection type to types_spaces rule description (#6479) +* DX: no need to use forked diff anymore (#6526) +* DX: remove unused FixerFileProcessedEvent::STATUS_UNKNOWN (#6516) +* Improve `statement_indentation` compatibility with `braces` (#6401) +* minor: add test: multi-line comments before else indented correctly. (#3573) +* minor: ReturnAssignmentFixer - Support for anonymous classes, lambda and match (#6391) + +Changelog for v3.9.5 +-------------------- + +* bug: AlternativeSyntaxAnalyzer - fix for nested else (#6495) +* bug: Fix cases related to binary strings (#6432) +* bug: Fix trailing whitespace after moving brace (#6489) +* bug: NoUnneededControlParenthesesFixer - Fix some curly close cases (#6502) +* bug: TypeColonTransformer - fix for backed enum types (#6494) +* DX: Add tests for type colon in backed enums (#6497) +* DX: Fix CI static analysis workflow (#6506) +* DX: Fix PHPStan errors (#6504) +* DX: Increase PHPStan level to 6 (#6468) +* DX: Narrow docblock types in Runner and Report (#6465) +* DX: Narrow docblock types in Tokenizer (#6293) +* minor: extract NoMultipleStatementsPerLineFixer from BracesFixer (#6458) +* minor: Let PhpdocLineSpan fixer detect docblocks when separator from token with attribute (#6343) + +Changelog for v3.9.4 +-------------------- + +* bug: Fix various indentation issues (#6480) +* bug: Fix wrong brace position after static return type (#6485) +* bug: Prevent breaking functions returning by reference (#6487) + +Changelog for v3.9.3 +-------------------- + +* bug: Fix BinaryOperatorSpacesFixer adding whitespace outside PHP blocks (#6476) +* bug: Fix brace location after multiline function signature (#6475) + +Changelog for v3.9.2 +-------------------- + +* bug: Fix indentation after control structure in switch (#6473) + +Changelog for v3.9.1 +-------------------- + +* bug: Add attributes support to `statement_indentation` (#6429) +* bug: BinaryOperatorSpacesFixer - Allow to align `=` inside array definitions (#6444) +* bug: BinaryOperatorSpacesFixer - Fix align of operator with function declaration (#6445) +* bug: ConstantCaseFixer - Do not touch enum case (#6367) +* bug: CurlyBracesPositionFixer - multiple elseifs (#6459) +* bug: Fix #6439 issue in `StaticLambda` fixer (#6440) +* bug: FullOpeningTagFixer - fix substr check for pre PHP8 (#6388) +* bug: IncrementStyleFixer - NoSpacesInsideParenthesisFixer - prio (#6416) +* bug: LambdaNotUsedImportFixer must run before MethodArgumentSpaceFixer (#6453) +* bug: MethodArgumentSpaceFixer - first element in same line, space before comma and inconsistent indent (#6438) +* bug: NoSuperfluousPhpdocTagsFixer - fix for promoted properties (#6403) +* bug: StatementIndentationFixer - Fix indentation for multiline traits use (#6402) +* bug: StrictComparisonFixer must rune before ModernizeStrposFixer (#6455) +* bug: TokensAnalyzer - fix intersection types considered as binary operator (#6414) +* DX: `ISSUE_TEMPLATE` hints to check applied rules (#6398) +* DX: Add more type hints (#6383) +* DX: Fix CI/CD issues (#6411) +* DX: cleanup test (#6410) +* DX: integrate PRLint (#6406) +* feature: BlankLineBetweenImportGroupsFixer - Introduction (#6365) +* feature: DateTimeCreateFromFormatCallFixer - Add DateTimeImmutable support (#6350) +* feature: Extract StatementIndentationFixer from BracesFixer (#5960) +* feature: ModernizeStrposFixer - fix leading backslash with yoda (#6377) +* feature: NoExtraBlankLinesFixer - Add `attributes` option - Fix support for `enum` `case` (#6426) +* feature: NoUnneededControlParenthesesFixer - Fix more cases (#6409) +* feature: NoUselessNullsafeOperatorFixer - Introduction (#6425) +* feature: OrderedTrait - Move Phpdoc with trait import (#6361) +* feature: PhpdocOrderByValueFixer - Allow sorting of mixin annotations by value (#6446) +* feature: TrailingCommaInMultiline - Add `match` support (#6381) +* minor: Allow Composer Normalize plugin (#6454) +* minor: ExplicitStringVariableFixer - Fix to PHP8.2 compat code (#6424) +* minor: Extract ControlStructureBracesFixer from BracesFixer (#6399) +* minor: NoBinaryStringFixer - Fix more cases (#6442) +* minor: NoSuperfluousPhpdocTagsFixer - Attribute handling (#6382) +* minor: PhpCsFixerSet - Update blank_line_before_statement config (#6389) +* minor: Remove unnecessary PHP version constraints (#6461) +* minor: SingleImportPerStatementFixer - fix PSR12 set (#6415) +* minor: SingleSpaceAfterConstructFixer - add option `type_colon` (#6434) +* minor: SymfonySet - Add SimpleToComplexStringVariableFixer (#6423) +* minor: Update PHPStan (#6467) +* minor: extract CurlyBracesPositionFixer from BracesFixer (#6452) + Changelog for v3.8.0 -------------------- @@ -20,7 +1348,7 @@ Changelog for v3.7.0 * bug #6112 [BinaryOperatorSpacesFixer] Fix align of `=` inside calls of methods (VincentLanglet) * bug #6279 ClassReferenceNameCasingFixer - Fix for double arrow (SpacePossum) -* bug #6280 Fix bunch of enum issus (SpacePossum) +* bug #6280 Fix bunch of enum issues (SpacePossum) * bug #6283 ClassReferenceNameCasingFixer - detect imports (SpacePossum) * feature #5892 NewWithBracesFixer - option to remove braces (jrmajor) * feature #6081 Allow multiline constructor arguments in an anonymous classes (jrmajor, SpacePossum) @@ -50,7 +1378,7 @@ Changelog for v3.6.0 * bug #6241 NoSuperfluousPhpdocTagsFixer - fix for reference and splat operator (kubawerlos) * bug #6243 PhpdocTypesOrderFixer - fix for intersection types (kubawerlos) * bug #6254 PhpUnitDedicateAssertFixer - remove `is_resource`. (drupol) -* bug #6264 TokensAnalyzer - fix isConstantInvocation detection for mulitple exce… (SpacePossum) +* bug #6264 TokensAnalyzer - fix isConstantInvocation detection for multiple exce… (SpacePossum) * bug #6265 NullableTypeDeclarationForDefaultNullValueFixer - handle "readonly" a… (SpacePossum) * bug #6266 SimplifiedIfReturnFixer - handle statement in loop without braces (SpacePossum) * feature #6262 ClassReferenceNameCasingFixer - introduction (SpacePossum) @@ -72,7 +1400,7 @@ Changelog for v3.5.0 * bug #6165 DeclareEqualNormalizeFixer - fix for declare having multiple directives (kubawerlos) * bug #6170 NonPrintableCharacterFixer - fix for string in single quotes, having non-breaking space, linebreak, and single quote inside (kubawerlos) * bug #6181 UseTransformer - Trait import in enum fix (PHP8.1) (SpacePossum) -* bug #6188 PhpdocTo(Param|Property|Return)TypeFixer - fix for type intersections (kubawerlos) +* bug #6188 `PhpdocTo(Param|Property|Return)TypeFixer` - fix for type intersections (kubawerlos) * bug #6202 SquareBraceTransformer - fix for destructing square brace after double arrow (kubawerlos) * bug #6209 OrderedClassElementsFixer - PHP8.0 support abstract private methods in traits (SpacePossum) * bug #6224 ArgumentsAnalyzer - support PHP8.1 readonly (SpacePossum) @@ -128,7 +1456,7 @@ Changelog for v3.4.0 * minor #6109 Add return type to `DummyTestSplFileInfo::getRealPath()` (derrabus) * minor #6115 Remove PHP 7.2 polyfill (derrabus) * minor #6116 CI: remove installation of mbstring polyfill in build script, it's required dependency now (keradus) -* minor #6119 OrderedClassElementsFixer - PHPUnit assert(Pre|Post)Conditions methods support (meyerbaptiste) +* minor #6119 OrderedClassElementsFixer - PHPUnit `assert(Pre|Post)Conditions` methods support (meyerbaptiste) * minor #6121 Use Tokens::ensureWhitespaceAtIndex to simplify code (kubawerlos) * minor #6127 Remove 2nd parameter to XdebugHandler constructor (phil-davis) * minor #6129 clean ups (SpacePossum) @@ -251,7 +1579,7 @@ Changelog for v3.2.0 * minor #6029 PhpUnitDedicateAssertFixer - add "assertStringContainsString" and "as… (SpacePossum) * minor #6030 SingleSpaceAfterConstructFixer - Add `switch` support (SpacePossum) * minor #6033 ArgumentsAnalyzerTest - add more tests (SpacePossum) -* minor #6034 7.0|7.1 - cleanup tests (SpacePossum) +* minor #6034 Cleanup tests for PHP 7.0 and 7.1 (SpacePossum) * minor #6035 Documentation generation split up and add list. (SpacePossum) * minor #6048 Fix "can not" spelling (mvorisek) @@ -710,7 +2038,7 @@ Changelog for v2.17.3 Changelog for v2.17.2 --------------------- -* bug #5345 CleanNamespaceFixer - preserve traling comments (SpacePossum) +* bug #5345 CleanNamespaceFixer - preserve trailing comments (SpacePossum) * bug #5348 PsrAutoloadingFixer - fix for class without namespace (kubawerlos) * bug #5362 SingleSpaceAfterConstructFixer: Do not adjust whitespace before multiple multi-line extends (localheinz, SpacePossum) * minor #5314 Enable testing with PHPUnit 9.x (sanmai) @@ -808,7 +2136,6 @@ Changelog for v2.17.0 * minor #5323 NoUselessSprintfFixer - Fix test on PHP5.6 (SpacePossum) * minor #5326 DX: relax composer requirements to not block installation under PHP v8, support for PHP v8 is not yet ready (keradus) - Changelog for v2.16.10 ---------------------- @@ -1432,7 +2759,7 @@ Changelog for v2.15.4 * minor #4564 Move readme-update command to Section 3 (iwasherefirst2) * minor #4566 Update symfony ruleset (gharlan) * minor #4570 Command::execute() should always return an integer (derrabus) -* minor #4580 Add suport for true/false return type hints. (SpacePossum) +* minor #4580 Add support for true/false return type hints. (SpacePossum) * minor #4584 Increase PHPStan level to 1 (julienfalque) * minor #4585 Fix deprecation notices (julienfalque) * minor #4587 Output details - Explain why a file was skipped (SpacePossum) @@ -1593,8 +2920,8 @@ Changelog for v2.14.3 * minor #4340 Travis: build against 7.4snapshot instead of nightly (Slamdunk) * minor #4351 code grooming (SpacePossum) * minor #4353 Add more priority tests (SpacePossum) -* minor #4364 DX: MethodChainingIndentationFixer - remove unneccesary loop (Sijun Zhu) -* minor #4366 Unset the auxillary variable $a (GrahamCampbell) +* minor #4364 DX: MethodChainingIndentationFixer - remove unnecessary loop (Sijun Zhu) +* minor #4366 Unset the auxiliary variable $a (GrahamCampbell) * minor #4368 Fixed TypeShortNameResolverTest::testResolver (GrahamCampbell) * minor #4380 PHP7.4 - Add "str_split" => "mb_str_split" mapping. (SpacePossum) * minor #4381 PHP7.4 - Add support for magic methods (un)serialize. (SpacePossum) @@ -1768,7 +3095,7 @@ Changelog for v2.13.0 * feature #3812 Add FopenFlagOrderFixer & FopenFlagsFixer (SpacePossum) * feature #3826 Add CombineNestedDirnameFixer (gharlan) * feature #3833 BinaryOperatorSpacesFixer - Add "no space" fix strategy (SpacePossum) -* feature #3841 NoAliasFunctionsFixer - add opt in option for ext-mbstring aliasses (SpacePossum) +* feature #3841 NoAliasFunctionsFixer - add opt in option for ext-mbstring aliases (SpacePossum) * feature #3876 NativeConstantInvocationFixer - add the scope option (stof, keradus) * feature #3886 Add PhpUnitMethodCasingFixer (Slamdunk) * feature #3907 Add ImplodeCallFixer (kubawerlos) @@ -1849,8 +3176,8 @@ Changelog for v2.12.9 * minor #4340 Travis: build against 7.4snapshot instead of nightly (Slamdunk) * minor #4351 code grooming (SpacePossum) * minor #4353 Add more priority tests (SpacePossum) -* minor #4364 DX: MethodChainingIndentationFixer - remove unneccesary loop (Sijun Zhu) -* minor #4366 Unset the auxillary variable $a (GrahamCampbell) +* minor #4364 DX: MethodChainingIndentationFixer - remove unnecessary loop (Sijun Zhu) +* minor #4366 Unset the auxiliary variable $a (GrahamCampbell) * minor #4368 Fixed TypeShortNameResolverTest::testResolver (GrahamCampbell) * minor #4380 PHP7.4 - Add "str_split" => "mb_str_split" mapping. (SpacePossum) * minor #4393 DX: add missing explicit return types (kubawerlos) @@ -2105,7 +3432,7 @@ Changelog for v2.11.2 * bug #3673 PhpdocScalarFixer - Add "types" option (julienfalque, keradus) * bug #3674 YodaStyleFixer - Fix variable detection for multidimensional arrays (julienfalque, SpacePossum) * bug #3684 PhpUnitStrictFixer - Do not fix if not correct # of arguments are used (SpacePossum) -* bug #3708 EspaceImplicitBackslashesFixer - Fix escaping multiple backslashes (julienfalque) +* bug #3708 EscapeImplicitBackslashesFixer - Fix escaping multiple backslashes (julienfalque) * bug #3715 SingleImportPerStatementFixer - Fix handling whitespace before opening brace (julienfalque) * bug #3731 PhpdocIndentFixer - crash fix (SpacePossum) * bug #3755 YodaStyleFixer - handle space between var name and index (SpacePossum) @@ -2239,7 +3566,7 @@ Changelog for v2.10.3 * minor #3484 Create Tokens::findBlockStart (ntzm) * minor #3512 Add missing array typehints (ntzm) * minor #3513 Making AppVeyor happy (kubawerlos) -* minor #3516 Use null|type instead of ?type in PHPDocs (ntzm) +* minor #3516 Use `null|type` instead of `?type` in PHPDocs (ntzm) * minor #3518 FixerFactoryTest - Test each priority test file is listed as test (SpacePossum) * minor #3519 Fix typo (SpacePossum) * minor #3520 Fix typos: ran vs. run (SpacePossum) @@ -2424,7 +3751,7 @@ Changelog for v2.8.1 * bug #3199 TokensAnalyzer - getClassyElements (SpacePossum) * bug #3208 BracesFixer - Fix for instantiation in control structures (julienfalque, SpacePossum) -* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (|) (ntzm) +* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (ntzm) * bug #3216 AbstractLinesBeforeNamespaceFixer - add min. and max. option, not only single target count (SpacePossum) * bug #3217 TokenizerLinter - fix lack of linting when code is cached (SpacePossum, keradus) * minor #3200 Skip slow test when Xdebug is loaded (julienfalque) @@ -2471,7 +3798,7 @@ Changelog for v2.7.4 * bug #3199 TokensAnalyzer - getClassyElements (SpacePossum) * bug #3208 BracesFixer - Fix for instantiation in control structures (julienfalque, SpacePossum) -* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (|) (ntzm) +* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (ntzm) * bug #3216 AbstractLinesBeforeNamespaceFixer - add min. and max. option, not only single target count (SpacePossum) * bug #3217 TokenizerLinter - fix lack of linting when code is cached (SpacePossum, keradus) * minor #3200 Skip slow test when Xdebug is loaded (julienfalque) @@ -2493,7 +3820,7 @@ Changelog for v2.7.2 * bug #3062 BraceClassInstantiationTransformer - Fix instantiation inside method call braces case (julienfalque, keradus) * bug #3083 SingleBlankLineBeforeNamespaceFixer - Fix handling namespace right after opening tag (mlocati) * bug #3109 SwitchCaseSemicolonToColonFixer - Fix bug with nested constructs (SpacePossum) -* bug #3117 Multibyte character in array key makes alignment incorect (kubawerlos) +* bug #3117 Multibyte character in array key makes alignment incorrect (kubawerlos) * bug #3123 Cache - File permissions (SpacePossum) * bug #3138 NoHomoglyphNamesFixer - fix crash on non-ascii but not mapped either (SpacePossum) * bug #3172 IndentationTypeFixer - do not touch whitespace that is not indentation (SpacePossum) @@ -2693,7 +4020,7 @@ Changelog for v2.3.3 * bug #2807 NoUselessElseFixer - Fix detection of conditional block (SpacePossum) * bug #2809 Phar release - fix readme generation (SpacePossum, keradus) * bug #2827 MethodArgumentSpaceFixer - Always remove trailing spaces (julienfalque) -* bug #2835 SelfAcessorFixer - class property fix (mnabialek) +* bug #2835 SelfAccessorFixer - class property fix (mnabialek) * bug #2848 PhpdocIndentFixer - fix edge case with inline phpdoc (keradus) * bug #2849 BracesFixer - Fix indentation issues with comments (julienfalque) * bug #2851 Tokens - ensureWhitespaceAtIndex (GrahamCampbell, SpacePossum) @@ -2882,7 +4209,7 @@ Changelog for v2.2.17 * minor #3435 Add tests for general_phpdoc_annotation_remove (BackEndTea) * minor #3484 Create Tokens::findBlockStart (ntzm) * minor #3512 Add missing array typehints (ntzm) -* minor #3516 Use null|type instead of ?type in PHPDocs (ntzm) +* minor #3516 Use `null|type` instead of `?type` in PHPDocs (ntzm) * minor #3518 FixerFactoryTest - Test each priority test file is listed as test (SpacePossum) * minor #3520 Fix typos: ran vs. run (SpacePossum) * minor #3521 Use HTTPS (carusogabriel) @@ -2975,7 +4302,7 @@ Changelog for v2.2.10 * bug #3199 TokensAnalyzer - getClassyElements (SpacePossum) * bug #3208 BracesFixer - Fix for instantiation in control structures (julienfalque, SpacePossum) -* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (|) (ntzm) +* bug #3215 BinaryOperatorSpacesFixer - Fix spaces around multiple exception catching (ntzm) * bug #3216 AbstractLinesBeforeNamespaceFixer - add min. and max. option, not only single target count (SpacePossum) * bug #3217 TokenizerLinter - fix lack of linting when code is cached (SpacePossum, keradus) * minor #3200 Skip slow test when Xdebug is loaded (julienfalque) @@ -3081,7 +4408,7 @@ Changelog for v2.2.5 * bug #2807 NoUselessElseFixer - Fix detection of conditional block (SpacePossum) * bug #2809 Phar release - fix readme generation (SpacePossum, keradus) * bug #2827 MethodArgumentSpaceFixer - Always remove trailing spaces (julienfalque) -* bug #2835 SelfAcessorFixer - class property fix (mnabialek) +* bug #2835 SelfAccessorFixer - class property fix (mnabialek) * bug #2848 PhpdocIndentFixer - fix edge case with inline phpdoc (keradus) * bug #2849 BracesFixer - Fix indentation issues with comments (julienfalque) * bug #2851 Tokens - ensureWhitespaceAtIndex (GrahamCampbell, SpacePossum) @@ -3170,7 +4497,9 @@ Changelog for v2.2.2 -------------------- Warning, this release breaks BC due to introduction of: + * minor #2554 Add short diff. output format (SpacePossum, keradus) + That PR was reverted in v2.2.3, which should be used instead of v2.2.2. * bug #2545 RuleSet - change resolvement (SpacePossum) @@ -3369,7 +4698,7 @@ Changelog for v2.0.1 * minor #2499 FileSpecificCodeSample - Specify class name relative to root namespace (localheinz, keradus) * minor #2506 SCA (SpacePossum) * minor #2515 Fix code indentation (keradus) -* minor #2521 SCA trailing spces check - ouput lines with trailing white space (SpacePossum) +* minor #2521 SCA trailing spces check - output lines with trailing white space (SpacePossum) * minor #2522 Fix docs and small code issues (keradus) Changelog for v2.0.0 @@ -3420,7 +4749,7 @@ Changelog for v2.0.0 * feature #1628 Added OrderedClassElementsFixer (gharlan) * feature #1742 path argument is used to create an intersection with existing finder (keradus, gharlan) * feature #1779 Added GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocAnnotationRenameFixer (keradus) -* feature #1811 Added NoSpacesInsideOfssetFixer (phansys) +* feature #1811 Added NoSpacesInsideOffsetFixer (phansys) * feature #1819 Added DirConstantFixer, ModernizeTypesCastingFixer, RandomApiMigrationFixer (kalessil, SpacePossum, keradus) * feature #1825 Added junit format (ekho) * feature #1862 FixerFactory - Do not allow conflicting fixers (SpacePossum) @@ -3537,7 +4866,7 @@ Changelog for v2.0.0 * minor #1846 FileFilterIterator - Corrected an iterator typehint (GrahamCampbell) * minor #1848 DocBlock - Remove some old unused phpdoc tags (GrahamCampbell) * minor #1856 NoDuplicateSemicolonsFixer - Remove overcomplete fixer (SpacePossum) -* minor #1861 Fix: Ofsset should be Offset (localheinz) +* minor #1861 Fix: Offset should be Offset (localheinz) * minor #1867 Print non-report output to stdErr (SpacePossum, keradus) * minor #1873 Enhancement: Show path to cache file if it exists (localheinz) * minor #1875 renamed Composer package (fabpot) @@ -3600,7 +4929,7 @@ Changelog for v2.0.0 * minor #2318 *TestCase - Reduce visibility of setUp() (localheinz) * minor #2319 Code grooming (keradus) * minor #2322 DX: use whitemessy aware assertion (keradus) -* minor #2324 Echo|Print*Fixer - unify printing fixers (SpacePossum, keradus) +* minor #2324 `Echo|Print*Fixer` - unify printing fixers (SpacePossum, keradus) * minor #2337 Normalize rule naming (keradus) * minor #2338 Drop hacks for unsupported HHVM (keradus) * minor #2339 Add some Fixer descriptions (SpacePossum, keradus) @@ -3678,7 +5007,7 @@ Changelog for v1.12.3 * bug #2209 LinefeedFixer - Fix in a safe way (SpacePossum) * bug #2228 NoEmptyLinesAfterPhpdocs, SingleBlankLineBeforeNamespace - Fix priority (SpacePossum) * bug #2230 FunctionDeclarationFixer - Fix T_USE case (SpacePossum) -* bug #2232 Add a test for style of varaible decalration : var (daiglej) +* bug #2232 Add a test for style of variable declaration : var (daiglej) * bug #2246 Fix itest requirements (keradus) * minor #2238 .gitattributes - specified line endings (keradus) * minor #2239 IntegrationCase - no longer internal (keradus) @@ -3967,7 +5296,7 @@ Changelog for v1.10.1 * bug #1444 OrderedUseFixer - fix next case (keradus) * bug #1441 BracesFixer - fix next case (keradus) * bug #1422 AlignDoubleArrowFixer - fix handling of nested array (SpacePossum) -* bug #1425 PhpdocInlineTagFixerTest - fix case when met inalid PHPDoc (keradus) +* bug #1425 PhpdocInlineTagFixerTest - fix case when met invalid PHPDoc (keradus) * bug #1419 AlignDoubleArrowFixer, AlignEqualsFixer - fix priorities (keradus) * bug #1415 BlanklineAfterOpenTagFixer - Do not add a line break if there is one already. (SpacePossum) * bug #1410 PhpdocIndentFixer - Fix for open tag (SpacePossum) @@ -4115,7 +5444,7 @@ Changelog for v1.5.1 -------------------- * bug #1054 VisibilityFixer - fix var with array value assigned (localheinz, keradus) -* bug #1048 MultilineArrayTrailingCommaFixer, SingleArrayNoTrailingCommaFixer - using heredoc inside array not cousing to treat it as multiline array (keradus) +* bug #1048 MultilineArrayTrailingCommaFixer, SingleArrayNoTrailingCommaFixer - using heredoc inside array not causing to treat it as multiline array (keradus) * bug #1043 PhpdocToCommentFixer - also check other control structures, besides foreach (ceeram) * bug #1045 OrderedUseFixer - fix namespace order for trailing digits (rusitschka) * bug #1035 PhpdocToCommentFixer - Add static as valid keyword for structural element (ceeram) @@ -4149,7 +5478,7 @@ Changelog for v1.5 * minor #957 Fix Fixers methods order (GrahamCampbell) * minor #944 Enable caching of composer downloads on Travis (stof) * minor #941 EncodingFixer - enhance tests (keradus) -* minor #938 Psr0Fixer - remove unneded assignment (keradus) +* minor #938 Psr0Fixer - remove unneeded assignment (keradus) * minor #936 FixerTest - test description consistency (keradus) * minor #933 NoEmptyLinesAfterPhpdocsFixer - remove unneeded code, clarify description (ceeram) * minor #934 StdinFileInfo::getFilename - Replace phpdoc with normal comment and add back empty line before return (ceeram) @@ -4192,7 +5521,7 @@ Changelog for v1.4 * bug #945 Skip files containing __halt_compiler() on PHP 5.3 (stof) * bug #946 BracesFixer - fix typo in exception name (keradus) * bug #940 Tokens::setCode - apply missing transformation (keradus) -* bug #908 BracesFixer - fix invalide inserting brace for control structure without brace and lambda inside of it (keradus) +* bug #908 BracesFixer - fix invalid inserting brace for control structure without brace and lambda inside of it (keradus) * bug #903 NoEmptyLinesAfterPhpdocsFixer - fix bug with Windows style lines (GrahamCampbell) * bug #895 [PSR-2] Preserve blank line after control structure opening brace (marcaube) * bug #892 Fixed the double arrow multiline whitespace fixer (GrahamCampbell) @@ -4256,7 +5585,7 @@ Changelog for v1.2 * minor #779 Fixed a docblock type (GrahamCampbell) * minor #765 Typehinting in FileCacheManager, remove unused variable in Tokens (keradus) * minor #764 SelfUpdateCommand - get local version only if remote version was successfully obtained (keradus) -* minor #761 aling => (keradus) +* minor #761 align => (keradus) * minor #757 Some minor code simplify and extra test (keradus) * minor #713 Download php-cs-fixer.phar without sudo (michaelsauter) * minor #742 Various Minor Improvements (GrahamCampbell) diff --git a/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md b/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md index a3eb381f27..f0f5a429de 100644 --- a/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md +++ b/vendor/friendsofphp/php-cs-fixer/CONTRIBUTING.md @@ -1,99 +1,98 @@ # Contributions Are Welcome! -If you need any help, don't hesitate to ask the community on [Gitter](https://gitter.im/PHP-CS-Fixer/Lobby). +If you need any help, don't hesitate to ask the community using [GitHub Discussions](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/categories/q-a). -## Quick Guide +## Glossary ### Fixer -A *fixer* is a class that tries to fix one code style issue (a ``Fixer`` class -must implement ``FixerInterface``). +A *fixer* is a class that tries to fix a single code style issue (a ``Fixer`` class must implement ``FixerInterface``). + +### Ruleset + +A *ruleset* is a collection of rules (*fixers*) that may be referenced in the config file similarly to a single *fixer*. When you work on existing fixer please keep in mind it can be a part of a *ruleset*(s) and changes may affect many users. When working on new *fixer* please consider if it should be added to some *ruleset*(s). ### Config -A *config* knows about the code style rules and the files and directories that -must be scanned by the tool when run in the directory of your project. It is -useful for projects that follow a well-known directory structures (like for -Symfony projects for instance). - -### How-To - -* [Fork](https://help.github.com/articles/fork-a-repo/) the repo. -* [Checkout](https://git-scm.com/docs/git-checkout) the branch you want to make changes on: - * If you are fixing a bug or typo, improving tests or for any small tweak: the lowest branch where the changes can be applied. Once your Pull Request is accepted, the changes will get merged up to highest branches. - * `master` in other cases (new feature, deprecation, or backwards compatibility breaking changes). Note that most of the time, `master` represents the next minor release of PHP CS Fixer, so Pull Requests that break backwards compatibility might be postponed. -* Install dependencies: `composer install`. -* Create a new branch, e.g. `feature-foo` or `bugfix-bar`. -* Make changes. -* If you are adding functionality or fixing a bug - add a test! Prefer adding new test cases over modifying existing ones. -* Make sure there is no wrong file permissions in the repository: `./dev-tools/check_file_permissions.sh`. -* Make sure there is no trailing spaces in the code: `./dev-tools/check_trailing_spaces.sh`. -* Update documentation: `php dev-tools/doc.php`. This requires the highest version of PHP supported by PHP CS Fixer. If it is not installed on your system, you can run it in a Docker container instead: `docker run -it --rm --user="$(id -u):$(id -g)" -w="/app" --volume="$(pwd):/app" php:7.4-cli php dev-tools/doc.php`. -* Install dev tools: `dev-tools/install.sh` -* Run static analysis using PHPStan: `php -d memory_limit=256M dev-tools/vendor/bin/phpstan analyse` -* Check if tests pass: `vendor/bin/phpunit`. -* Fix project itself: `php php-cs-fixer fix`. +A *config* knows about the code style rules and the files and directories that must be scanned by the tool when run in the context of your project. It is useful for projects that follow a well-known directory structures, but the tool is not limited to any specific structure, and you can configure it in a very flexible way. -## Working With Docker +## How to contribute -This project provides a Docker setup that allows working on it using any of the supported PHP versions. +> [!IMPORTANT] +> Before contributing with _really_ significant changes that require a lot of effort or are crucial from this tool's +> architecture perspective, please open [RFC on GitHub Discussion](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/categories/rfc). +> The development effort should start only after the proposal is discussed and the approach aligned. -To use it, you first need to install: +### Development - * [Docker](https://docs.docker.com/get-docker/) - * [Docker Compose](https://docs.docker.com/compose/install/) +* [Fork](https://help.github.com/articles/fork-a-repo/) this repository. You can use native Git approach or use [`gh` CLI tool](https://cli.github.com/). +* Create new branch on top of the latest revision of `master` branch (if you already had project locally, then make sure to update this branch before going to next steps). It's good when branch's name reflects intent of the changes, but this is not strict requirement since pull request provides description of the change. However, with good branch naming it's easier to work on multiple changes simultaneously. +* Install dependencies by running `composer update` (since project does not contain `composer.lock` it's better to ensure latest versions of packages by running `update` command instead of `install`). +* Make changes. Please remember that **all** changes have to be covered by tests. + * if you work on a bug fix, please start with reproducing the problem by adding failing test case(s). When you have failing test case(s), you can [create pull request](#opening-a-pull-request) just to reproduce fail in the CI. Then you can provide fix _in the subsequent commits_, it will make code review easier. It's allowed to modify existing test cases in bug fix pull request, but *only if* current behavior is proved to be invalid. + * if you work on existing fixers then don't change existing test cases, because these are contract between the maintainers and users (they ensure how tool works). Add new test cases that cover provided changes - preferred way of defining test cases is with [data provider](https://docs.phpunit.de/en/10.0/writing-tests-for-phpunit.html#data-providers) which uses `yield` with proper case description as a key (e.g. `yield 'Some specific scenario' => ['some', 'example', 'data'];`). Codebase may still contain test cases in different format, and it's totally acceptable to use `yield` approach next to existing `return` usages. +* Update documentation: `composer docs`. This requires the highest version of PHP supported by PHP CS Fixer. If it is not installed on your system, you can run it in a Docker container instead: `docker compose run php-8.2 php dev-tools/doc.php`. +* Run QA suite: `composer qa`. +* Fix project itself (if needed): `composer cs:fix`. -Make sure the versions installed support [Compose file format 3.8](https://docs.docker.com/compose/compose-file/). +### Opening a [pull request](https://help.github.com/articles/about-pull-requests/) -Next, copy [`docker-compose.override.yaml.dist`](./docker-compose.override.yaml.dist) to `docker-compose.override.yaml` -and edit it to your needs. The relevant parameters that might require some tweaking have comments to help you. +You can do some things to increase the chance that your pull request is accepted without communication ping-pong between you and the reviewers: + +* Submit [single](https://en.wikipedia.org/wiki/Single-responsibility_principle) pull request per fix or feature. +* Keep meaningful commit logs, don't use meaningless messages (e.g. `foo`, `more work`, `more work`, `more work`) and don't push complex PR as a single commit. +* Don't [amend](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---amend) commits because it makes review rounds harder - all commits from your branch will be squashed (without commit messages) during the merge. +* Follow the conventions used in the project. +* Remember about tests and documentation. +* Don't bump `PhpCsFixer\Console\Application::VERSION`, it's done during release. + +> [!IMPORTANT] +> Your pull request will have much higher chance of getting merged if you allow maintainers to push changes to your +> branch. You can do it by ticking "Allow edits and access to secrets by maintainers" checkbox, but please keep in mind +> this option is available only if your PR is created from a user's fork. If your fork is a part of organisation, then +> you can add [Fixer maintainers](https://github.com/orgs/PHP-CS-Fixer/people) as members of that repository. This way +> maintainers will be able to provide required changes or rebase your branch (only up-to-date PRs can be merged). + +## Working With Docker + +This project provides a Docker setup that allows working on it using any of the PHP versions supported by the tool. + +To use it, you first need to install [Docker](https://docs.docker.com/get-docker/) ([Docker Compose](https://docs.docker.com/compose/) is a built-in plugin of the main tool). + +Next, copy [`compose.override.dist.yaml`](./compose.override.dist.yaml) to `compose.override.yaml` and edit it to your needs. The relevant parameters that might require some tweaking have comments to help you. You can then build the images: ```console -$ docker-compose build --parallel +docker compose build --parallel ``` -Now you can run commands needed to work on the project. For example, say you want to run PHPUnit tests on PHP 7.4: +Now you can run commands needed to work on the project. For example, say you want to run PHPUnit tests on PHP 8.2: ```console -$ docker-compose run php-7.4 vendor/bin/phpunit +docker compose run php-8.2 vendor/bin/phpunit ``` Sometimes it can be more convenient to have a shell inside the container: ```console -$ docker-compose run php-7.4 sh -/app $ vendor/bin/phpunit +docker compose run php-7.4 sh +/fixer vendor/bin/phpunit ``` -The images come with an [`xdebug` script](github.com/julienfalque/xdebug/) that allows running any PHP command with -Xdebug enabled to help debug problems. +The images come with an [`xdebug` script](github.com/julienfalque/xdebug/) that allows running any PHP command with Xdebug enabled to help debug problems. ```console -docker-compose run php-7.4 xdebug vendor/bin/phpunit +docker compose run php-8.2 xdebug vendor/bin/phpunit ``` -If you're using PhpStorm, you need to create a [server](https://www.jetbrains.com/help/phpstorm/servers.html) with a -name that matches the `PHP_IDE_CONFIG` environment variable defined in the Docker Compose configuration files, which is -`php-cs-fixer` by default. +If you're using PhpStorm, you need to create a [server](https://www.jetbrains.com/help/phpstorm/servers.html) with a name that matches the `PHP_IDE_CONFIG` environment variable defined in the Docker Compose configuration files, which is `php-cs-fixer` by default. All images use port 9003 for debug connections. -## Opening a [Pull Request](https://help.github.com/articles/about-pull-requests/) - -You can do some things to increase the chance that your Pull Request is accepted the first time: - -* Submit one Pull Request per fix or feature. -* If your changes are not up to date, [rebase](https://git-scm.com/docs/git-rebase) your branch onto the parent branch. -* Follow the conventions used in the project. -* Remember about tests and documentation. -* Don't bump version. - ## Making New Fixers -There is a [cookbook](doc/cookbook_fixers.rst) with basic instructions on how to build a new fixer. Consider reading it -before opening a PR. +There is a [cookbook](doc/cookbook_fixers.rst) with basic instructions on how to build a new fixer. Consider reading it before opening a PR. ## Project's Standards diff --git a/vendor/friendsofphp/php-cs-fixer/LICENSE b/vendor/friendsofphp/php-cs-fixer/LICENSE index d75d64a5dc..871def02eb 100644 --- a/vendor/friendsofphp/php-cs-fixer/LICENSE +++ b/vendor/friendsofphp/php-cs-fixer/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2022 Fabien Potencier, Dariusz Rumiński +Copyright (c) 2012+ Fabien Potencier, Dariusz Rumiński Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/friendsofphp/php-cs-fixer/README.md b/vendor/friendsofphp/php-cs-fixer/README.md index 20c55af0e9..12ae7915fb 100644 --- a/vendor/friendsofphp/php-cs-fixer/README.md +++ b/vendor/friendsofphp/php-cs-fixer/README.md @@ -4,8 +4,7 @@

-PHP Coding Standards Fixer -========================== +# PHP Coding Standards Fixer The PHP Coding Standards Fixer (PHP CS Fixer) tool fixes your code to follow standards; whether you want to follow PHP coding standards as defined in the PSR-1, PSR-2, etc., @@ -19,6 +18,22 @@ If you are already using a linter to identify coding standards problems in your code, you know that fixing them by hand is tedious, especially on large projects. This tool does not only detect them, but also fixes them for you. +## Supported PHP Versions + +* PHP 7.4 +* PHP 8.0 +* PHP 8.1 +* PHP 8.2 +* PHP 8.3 + +> **Note** +> Each new PHP version requires a huge effort to support the new syntax. +> That's why the latest PHP version might not be supported yet. If you need it, +> please, consider supporting the project in any convenient way, for example +> with code contribution or reviewing existing PRs. To run PHP CS Fixer on yet +> unsupported versions "at your own risk" - leverage the +> [PHP_CS_FIXER_IGNORE_ENV](./doc/usage.rst#environment-options). + ## Documentation ### Installation @@ -28,20 +43,40 @@ in a dedicated `composer.json` file in your project, for example in the `tools/php-cs-fixer` directory: ```console -$ mkdir --parents tools/php-cs-fixer -$ composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer +mkdir -p tools/php-cs-fixer +composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer +``` + +Or using the main `composer.json`: + +```console +composer require --dev friendsofphp/php-cs-fixer ``` For more details and other installation methods, see [installation instructions](./doc/installation.rst). +### Run with Docker + +You can use pre-built Docker images to run ``php-cs-fixer``. + +```console +docker run -it --rm -v $(pwd):/code ghcr.io/php-cs-fixer/php-cs-fixer:${FIXER_VERSION:-3-php8.3} fix src +``` + +`$FIXER_VERSION` used in example above is an identifier of a release you want to use, which is based on Fixer and PHP versions combined. There are different tags for each Fixer's SemVer level and PHP version with syntax `-php`. For example: + +* `3.57.0-php7.4` +* `3.57-php8.0` +* `3-php8.3` + ### Usage Assuming you installed PHP CS Fixer as instructed above, you can run the -following command to fix the files PHP files in the `src` directory: +following command to fix the PHP files in the `src` directory: ```console -$ tools/php-cs-fixer/vendor/bin/php-cs-fixer fix src +tools/php-cs-fixer/vendor/bin/php-cs-fixer fix src ``` See [usage](./doc/usage.rst), list of [built-in rules](./doc/rules/index.rst), list of [rule sets](./doc/ruleSets/index.rst) @@ -54,7 +89,6 @@ If you need to apply code styles that are not supported by the tool, you can Dedicated plugins exist for: -* [Atom](https://github.com/Glavin001/atom-beautify) * [NetBeans](https://plugins.netbeans.apache.org/catalogue/?id=36) * [PhpStorm](https://www.jetbrains.com/help/phpstorm/using-php-cs-fixer.html) * [Sublime Text](https://github.com/benmatselby/sublime-phpcs) @@ -63,11 +97,11 @@ Dedicated plugins exist for: ## Community -The PHP CS Fixer is maintained on GitHub at https://github.com/FriendsOfPHP/PHP-CS-Fixer. +The PHP CS Fixer is maintained on GitHub at . Bug reports and ideas about new features are welcome there. -You can reach us at https://gitter.im/PHP-CS-Fixer/Lobby about the project, -configuration, possible improvements, ideas and questions, please visit us! +You can reach us in the [GitHub Discussions](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/) regarding the +project, configuration, possible improvements, ideas and questions. Please visit us there! ## Contribute diff --git a/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md b/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md index c295a57cd5..bea1ff4fba 100644 --- a/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md +++ b/vendor/friendsofphp/php-cs-fixer/UPGRADE-v3.md @@ -1,21 +1,18 @@ -UPGRADE GUIDE FROM 2.x to 3.0 -============================= +# UPGRADE GUIDE FROM 2.x to 3.0 This is guide for upgrade from version 2.x to 3.0 for using the CLI tool. -*Before following this guide, install [v2.19](https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/tag/v2.19.0) and run in verbose mode (`php-cs-fixer fix -v`) or in future mode (`PHP_CS_FIXER_FUTURE_MODE=1 php-cs-fixer fix`) to identify deprecations and fix them first.* +*Before following this guide, install [v2.19](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/tag/v2.19.0) and run in verbose mode (`php-cs-fixer fix -v`) or in future mode (`PHP_CS_FIXER_FUTURE_MODE=1 php-cs-fixer fix`) to identify deprecations and fix them first.* -Rename of files ---------------- +## Rename of files | 2.x | 3.0 | Description | -| ---------------- | ------------------------ | -------------------------------------- | +|------------------|--------------------------|----------------------------------------| | `.php_cs` | `.php-cs-fixer.php` | Configuration file (local) | | `.php_cs.dist` | `.php-cs-fixer.dist.php` | Configuration file (to be distributed) | | `.php_cs.cache` | `.php-cs-fixer.cache` | Cache file | -CLI options ------------ +## CLI options | 2.x | 3.0 | Description | Note | | ---------------- | --------------- | ----------------------------------------------- | -------------------------------------- | @@ -27,94 +24,93 @@ CLI options | --rules | --rules | Default value changed from @PSR2 to @PSR12 | | | --config --rules | | | No longer allowed to pass both | -Changes to rules ----------------- +## Changes to rules ### Renamed rules -Old name | New name | Note --------- | -------- | ---- -`blank_line_before_return` | `blank_line_before_statement` | use configuration `['statements' => ['return']]` -`final_static_access` | `self_static_accessor` | -`hash_to_slash_comment` | `single_line_comment_style` | use configuration `['comment_types' => ['hash']]` -`lowercase_constants` | `constant_case` | use configuration `['case' => 'lower']` -`method_separation` | `class_attributes_separation` | use configuration `['elements' => ['method']]` -`no_extra_consecutive_blank_lines` | `no_extra_blank_lines` | -`no_multiline_whitespace_before_semicolons` | `multiline_whitespace_before_semicolons` | -`no_short_echo_tag` | `echo_tag_syntax` | use configuration `['format' => 'long']` -`php_unit_ordered_covers` | `phpdoc_order_by_value` | use configuration `['annotations' => [ 'covers' ]]` -`phpdoc_inline_tag` | `general_phpdoc_tag_rename`, `phpdoc_inline_tag_normalizer` and `phpdoc_tag_type` | -`pre_increment` | `increment_style` | use configuration `['style' => 'pre']` -`psr0` | `psr_autoloading` | use configuration `['dir' => x ]` -`psr4` | `psr_autoloading` | -`silenced_deprecation_error` | `error_suppression` | -`trailing_comma_in_multiline_array` | `trailing_comma_in_multiline` | use configuration `['elements' => ['arrays']]` +| Old name | New name | Note | +|--------------------------------------------|-----------------------------------------------------------------------------------|------------------------------------------------------| +|`blank_line_before_return` | `blank_line_before_statement` | use configuration `['statements' => ['return']]` | +|`final_static_access` | `self_static_accessor` | | +|`hash_to_slash_comment` | `single_line_comment_style` | use configuration `['comment_types' => ['hash']]` | +|`lowercase_constants` | `constant_case` | use configuration `['case' => 'lower']` | +|`method_separation` | `class_attributes_separation` | use configuration `['elements' => ['method']]` | +|`no_extra_consecutive_blank_lines` | `no_extra_blank_lines` | | +|`no_multiline_whitespace_before_semicolons` | `multiline_whitespace_before_semicolons` | | +|`no_short_echo_tag` | `echo_tag_syntax` | use configuration `['format' => 'long']` | +|`php_unit_ordered_covers` | `phpdoc_order_by_value` | use configuration `['annotations' => [ 'covers' ]]` | +|`phpdoc_inline_tag` | `general_phpdoc_tag_rename`, `phpdoc_inline_tag_normalizer` and `phpdoc_tag_type` | | +|`pre_increment` | `increment_style` | use configuration `['style' => 'pre']` | +|`psr0` | `psr_autoloading` | use configuration `['dir' => x ]` | +|`psr4` | `psr_autoloading` | | +|`silenced_deprecation_error` | `error_suppression` | | +|`trailing_comma_in_multiline_array` | `trailing_comma_in_multiline` | use configuration `['elements' => ['arrays']]` | ### Removed rootless configuration -Rule | Root option | Note ------------------------------------- | -------------- | ---- -`general_phpdoc_annotation_remove` | `annotations` -`no_extra_consecutive_blank_lines` | `tokens` -`no_spaces_around_offset` | `positions` -`no_unneeded_control_parentheses` | `statements` -`ordered_class_elements` | `order` -`php_unit_construct` | `assertions` -`php_unit_dedicate_assert` | `target` | root option works differently than rootless configuration -`php_unit_strict` | `assertions` -`phpdoc_no_alias_tag` | `replacements` -`phpdoc_return_self_reference` | `replacements` -`random_api_migration` | `replacements` -`single_class_element_per_statement` | `elements` -`visibility_required` | `elements` +| Rule | Root option | Note | +|--------------------------------------| -------------- |-----------------------------------------------------------| +| `general_phpdoc_annotation_remove` | `annotations` | | +| `no_extra_consecutive_blank_lines` | `tokens` | | +| `no_spaces_around_offset` | `positions` | | +| `no_unneeded_control_parentheses` | `statements` | | +| `ordered_class_elements` | `order` | | +| `php_unit_construct` | `assertions` | | +| `php_unit_dedicate_assert` | `target` | root option works differently than rootless configuration | +| `php_unit_strict` | `assertions` | | +| `phpdoc_no_alias_tag` | `replacements` | | +| `phpdoc_return_self_reference` | `replacements` | | +| `random_api_migration` | `replacements` | | +| `single_class_element_per_statement` | `elements` | | +| `visibility_required` | `elements` | | ### Changed options -Rule | Option | Change ----- | ------ | ------ -`binary_operator_spaces` | `align_double_arrow` | option was removed, use `operators` instead -`binary_operator_spaces` | `align_equals` | option was removed use `operators` instead -`blank_line_before_statement` | `statements: die` | option `die` was removed from `statements`, use `exit` instead -`class_attributes_separation` | `elements` | option does no longer accept flat array as a value, use map instead -`class_definition` | `multiLineExtendsEachSingleLine` | option was renamed to `multi_line_extends_each_single_line` -`class_definition` | `singleItemSingleLine` | option was renamed to `single_item_single_line` -`class_definition` | `singleLine` | option was renamed to `single_line` -`doctrine_annotation_spaces` | `around_argument_assignments` | option was removed, use `before_argument_assignments` and `after_argument_assignments` instead -`doctrine_annotation_spaces` | `around_array_assignments` | option was removed, use `after_array_assignments_colon`, `after_array_assignments_equals`, `before_array_assignments_colon` and `before_array_assignments_equals` instead -`final_internal_class` | `annotation-black-list` | option was renamed, use `annotation_exclude` -`final_internal_class` | `annotation-white-list` | option was renamed, use `annotation_include` -`final_internal_class` | `consider-absent-docblock-as-internal-class` | option was renamed, use `consider_absent_docblock_as_internal_class` -`header_comment` | `commentType` | option was renamed to `comment_type` -`is_null` | `use_yoda_style` | option was removed, use `yoda_style` rule instead -`no_extra_consecutive_blank_lines` | `tokens` | one of possible values, `useTrait`, was renamed to `use_trait` -`ordered_class_elements` | `sortAlgorithm` | option was renamed, use `sort_algorithm` instead -`ordered_imports` | `importsOrder` | option was renamed, use `imports_order` -`ordered_imports` | `sortAlgorithm` | option was renamed, use `sort_algorithm` -`php_unit_dedicate_assert` | `functions` | option was removed, use `target` instead -`php_unit_test_annotation` | `case` | option was removed, use `php_unit_method_casing` rule instead +| Rule | Option | Change | +|------------------------------------|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `binary_operator_spaces` | `align_double_arrow` | option was removed, use `operators` instead | +| `binary_operator_spaces` | `align_equals` | option was removed use `operators` instead | +| `blank_line_before_statement` | `statements: die` | option `die` was removed from `statements`, use `exit` instead | +| `class_attributes_separation` | `elements` | option does no longer accept flat array as a value, use map instead | +| `class_definition` | `multiLineExtendsEachSingleLine` | option was renamed to `multi_line_extends_each_single_line` | +| `class_definition` | `singleItemSingleLine` | option was renamed to `single_item_single_line` | +| `class_definition` | `singleLine` | option was renamed to `single_line` | +| `doctrine_annotation_spaces` | `around_argument_assignments` | option was removed, use `before_argument_assignments` and `after_argument_assignments` instead | +| `doctrine_annotation_spaces` | `around_array_assignments` | option was removed, use `after_array_assignments_colon`, `after_array_assignments_equals`, `before_array_assignments_colon` and `before_array_assignments_equals` instead | +| `final_internal_class` | `annotation-black-list` | option was renamed, use `annotation_exclude` | +| `final_internal_class` | `annotation-white-list` | option was renamed, use `annotation_include` | +| `final_internal_class` | `consider-absent-docblock-as-internal-class` | option was renamed, use `consider_absent_docblock_as_internal_class` | +| `header_comment` | `commentType` | option was renamed to `comment_type` | +| `is_null` | `use_yoda_style` | option was removed, use `yoda_style` rule instead | +| `no_extra_consecutive_blank_lines` | `tokens` | one of possible values, `useTrait`, was renamed to `use_trait` | +| `ordered_class_elements` | `sortAlgorithm` | option was renamed, use `sort_algorithm` instead | +| `ordered_imports` | `importsOrder` | option was renamed, use `imports_order` | +| `ordered_imports` | `sortAlgorithm` | option was renamed, use `sort_algorithm` | +| `php_unit_dedicate_assert` | `functions` | option was removed, use `target` instead | +| `php_unit_test_annotation` | `case` | option was removed, use `php_unit_method_casing` rule instead | ### Changed default values of options -Rule | Option | Old value | New value ----- | ---- | ---- | ---- -`array_syntax` | `syntax` | `'long'` | `'short'` -`function_to_constant` | `functions` | `['get_class', 'php_sapi_name', 'phpversion', 'pi']` | `['get_called_class', 'get_class', 'php_sapi_name', 'phpversion', 'pi']` -`list_syntax` | `syntax` | `'long'` | `'short'` -`method_argument_space` | `on_multiline` | `'ignore'` | `'ensure_fully_multiline'` -`native_constant_invocation` | `strict` | `false` | `true` -`native_function_casing` | `include` | `'@internal'` | `'@compiler_optimized'` -`native_function_invocation` | `include` | `'@internal'` | `'@compiler_optimized'` -`native_function_invocation` | `strict` | `false` | `true` -`non_printable_character` | `use_escape_sequences_in_strings` | `false` | `true` (when running on PHP 7.0 and up) -`php_unit_dedicate_assert` | `target` | `'5.0'` | `'newest'` -`phpdoc_align` | `tags` | `['param', 'return', 'throws', 'type', 'var']` | `['method', 'param', 'property', 'return', 'throws', 'type', 'var']` -`phpdoc_scalar` | `types` | `['boolean', 'double', 'integer', 'real', 'str']` | `['boolean', 'callback', 'double', 'integer', 'real', 'str']` +| Rule | Option | Old value | New value | +|------------------------------|-----------------------------------|------------------------------------------------------|--------------------------------------------------------------------------| +| `array_syntax` | `syntax` | `'long'` | `'short'` | +| `function_to_constant` | `functions` | `['get_class', 'php_sapi_name', 'phpversion', 'pi']` | `['get_called_class', 'get_class', 'php_sapi_name', 'phpversion', 'pi']` | +| `list_syntax` | `syntax` | `'long'` | `'short'` | +| `method_argument_space` | `on_multiline` | `'ignore'` | `'ensure_fully_multiline'` | +| `native_constant_invocation` | `strict` | `false` | `true` | +| `native_function_casing` | `include` | `'@internal'` | `'@compiler_optimized'` | +| `native_function_invocation` | `include` | `'@internal'` | `'@compiler_optimized'` | +| `native_function_invocation` | `strict` | `false` | `true` | +| `non_printable_character` | `use_escape_sequences_in_strings` | `false` | `true` (when running on PHP 7.0 and up) | +| `php_unit_dedicate_assert` | `target` | `'5.0'` | `'newest'` | +| `phpdoc_align` | `tags` | `['param', 'return', 'throws', 'type', 'var']` | `['method', 'param', 'property', 'return', 'throws', 'type', 'var']` | +| `phpdoc_scalar` | `types` | `['boolean', 'double', 'integer', 'real', 'str']` | `['boolean', 'callback', 'double', 'integer', 'real', 'str']` | ### Removed rule sets -Rule set | Note --------- | ---- -`@PHP56Migration` | was empty +| Rule set | Note | +|-------------------|------------| +| `@PHP56Migration` | was empty | ### Rule behavior changes @@ -124,8 +120,7 @@ Rule set | Note - `udiff` output now includes the file name in the output (if applicable) -Code BC changes -=============== +## Code BC changes ### Removed; various @@ -138,11 +133,11 @@ Code BC changes - `MethodArgumentSpaceFixer` deprecated methods have been removed - `NoMixedEchoPrintFixer` the property `$defaultConfig` has been removed - class `Tokens`, the following methods has been removed: - - `current()` - - `key()` - - `next()` - - `rewind()` - - `valid()` + - `current()` + - `key()` + - `next()` + - `rewind()` + - `valid()` - namespace `PhpCsFixer\Test\` and each class in it has been removed, as it served pure development purpose and should not be part of production code - reach out to community if you are willing to help building dev package ### Interface changes diff --git a/vendor/friendsofphp/php-cs-fixer/ci-integration.sh b/vendor/friendsofphp/php-cs-fixer/ci-integration.sh index 2521e2494c..39d999559b 100644 --- a/vendor/friendsofphp/php-cs-fixer/ci-integration.sh +++ b/vendor/friendsofphp/php-cs-fixer/ci-integration.sh @@ -5,4 +5,4 @@ IFS=' ' CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "${COMMIT_RANGE}") if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php-cs-fixer(\\.dist)?\\.php|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi -vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --dry-run --stop-on-violation --using-cache=no ${EXTRA_ARGS} +vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php -v --show-progress=dots --stop-on-violation --using-cache=no ${EXTRA_ARGS} diff --git a/vendor/friendsofphp/php-cs-fixer/composer.json b/vendor/friendsofphp/php-cs-fixer/composer.json index 5cff2a0ba4..f106058634 100644 --- a/vendor/friendsofphp/php-cs-fixer/composer.json +++ b/vendor/friendsofphp/php-cs-fixer/composer.json @@ -3,6 +3,12 @@ "description": "A tool to automatically fix PHP code style", "license": "MIT", "type": "application", + "keywords": [ + "fixer", + "standards", + "static analysis", + "static code analysis" + ], "authors": [ { "name": "Fabien Potencier", @@ -15,38 +21,43 @@ ], "require": { "php": "^7.4 || ^8.0", + "ext-filter": "*", "ext-json": "*", "ext-tokenizer": "*", - "composer/semver": "^3.2", + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^1.13", - "php-cs-fixer/diff": "^2.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/options-resolver": "^5.4 || ^6.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.25", - "symfony/polyfill-php81": "^1.25", - "symfony/process": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "fidry/cpu-core-counter": "^1.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.10", - "php-coveralls/php-coveralls": "^2.5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^6.0", - "symfony/yaml": "^5.4 || ^6.0" + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -55,17 +66,136 @@ "autoload": { "psr-4": { "PhpCsFixer\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] }, "autoload-dev": { "psr-4": { "PhpCsFixer\\Tests\\": "tests/" - } + }, + "exclude-from-classmap": [ + "tests/Fixtures/" + ] }, "bin": [ "php-cs-fixer" ], "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "infection/extension-installer": false + }, + "prefer-stable": true, "sort-packages": true + }, + "scripts": { + "post-autoload-dump": [ + "@install-tools" + ], + "auto-review": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite auto-review" + ], + "cs:check": "@php php-cs-fixer check --verbose --diff", + "cs:fix": "@php php-cs-fixer fix", + "cs:fix:parallel": [ + "echo '⚠️ This script is deprecated! Utilise built-in parallelisation instead.';", + "@cs:fix" + ], + "docs": "@php dev-tools/doc.php", + "infection": "@test:mutation", + "install-tools": "@composer --working-dir=dev-tools install", + "mess-detector": "@php dev-tools/vendor/bin/phpmd . ansi dev-tools/mess-detector/phpmd.xml --exclude vendor/*,dev-tools/vendor/*,dev-tools/phpstan/*,tests/Fixtures/*", + "normalize": [ + "@composer normalize --working-dir=dev-tools --dry-run ../composer.json", + "@composer normalize --working-dir=dev-tools --dry-run composer.json" + ], + "phpstan": "@php -d memory_limit=512M dev-tools/vendor/bin/phpstan analyse", + "phpstan:baseline": "@php -d memory_limit=512M dev-tools/vendor/bin/phpstan analyse --generate-baseline=./dev-tools/phpstan/baseline.php", + "qa": "@quality-assurance", + "quality-assurance": [ + "Composer\\Config::disableProcessTimeout", + "@install-tools --quiet", + "@self-check", + "@static-analysis", + "@test" + ], + "require-checker": "@php dev-tools/vendor/bin/composer-require-checker check composer.json --config-file .composer-require-checker.json", + "sa": "@static-analysis", + "self-check": [ + "./dev-tools/check_file_permissions.sh", + "./dev-tools/check_trailing_spaces.sh", + "@composer dump-autoload --dry-run --optimize --strict-psr", + "@normalize", + "@unused-deps", + "@require-checker", + "@auto-review" + ], + "static-analysis": [ + "@cs:check", + "@phpstan", + "@mess-detector" + ], + "test": "@test:all", + "test:all": [ + "@test:unit", + "@test:integration" + ], + "test:coverage": [ + "Composer\\Config::disableProcessTimeout", + "@composer show facile-it/paraunit ^2 && (paraunit coverage --testsuite unit --pass-through=--exclude-group=covers-nothing) || (paraunit coverage --testsuite unit --exclude-group covers-nothing)" + ], + "test:integration": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite integration" + ], + "test:mutation": [ + "Composer\\Config::disableProcessTimeout", + "infection --threads=max --only-covered --min-covered-msi=80" + ], + "test:short-open-tag": [ + "Composer\\Config::disableProcessTimeout", + "@php -d short_open_tag=1 ./vendor/bin/phpunit --do-not-cache-result --testsuite short-open-tag" + ], + "test:smoke": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite smoke" + ], + "test:unit": [ + "Composer\\Config::disableProcessTimeout", + "paraunit run --testsuite unit" + ], + "unused-deps": "@php dev-tools/vendor/bin/composer-unused --excludePackage=composer/xdebug-handler" + }, + "scripts-descriptions": { + "auto-review": "Execute Auto-review", + "cs:check": "Check coding standards", + "cs:fix": "Fix coding standards", + "cs:fix:parallel": "⚠️DEPRECATED! Use cs:fix with proper parallel config", + "docs": "Regenerate docs", + "infection": "Alias for 'test:mutation'", + "install-tools": "Install DEV tools", + "mess-detector": "Analyse code with Mess Detector", + "normalize": "Run normalization for composer.json files", + "phpstan": "Run PHPStan analysis", + "phpstan:baseline": "Dump PHPStan baseline file - use only for updating, do not add new errors when possible", + "post-autoload-dump": "Run additional tasks after installing/updating main dependencies", + "qa": "Alias for 'quality-assurance'", + "quality-assurance": "Run QA suite", + "require-checker": "Verifies if codebase does not contain soft dependencies", + "sa": "Alias for 'static-analysis'", + "self-check": "Run set of self-checks ensuring repository's validity", + "static-analysis": "Run static analysis", + "test": "Alias for 'test:all'", + "test:all": "Run Unit and Integration tests (but *NOT* Smoke tests)", + "test:coverage": "Run tests that provide code coverage", + "test:integration": "Run Integration tests", + "test:mutation": "Run mutation tests", + "test:short-open-tag": "Run tests with \"short_open_tag\" enabled", + "test:smoke": "Run Smoke tests", + "test:unit": "Run Unit tests", + "unused-deps": "Verifies if app has dependencies that are not used" } } diff --git a/vendor/friendsofphp/php-cs-fixer/feature-or-bug.rst b/vendor/friendsofphp/php-cs-fixer/feature-or-bug.rst new file mode 100644 index 0000000000..e5959422cc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/feature-or-bug.rst @@ -0,0 +1,24 @@ +========================== +Is it a feature or a bug ? +========================== + +Sometimes it's a bit tricky to define if given change proposal or change request is adding new feature or fixing existing issue. This document is providing more clarity about categorisation we use. + +Bug +--- + +Example of bugs: + +- crash during application or rule execution +- wrong changes are applied during "fixing codebase" process +- issue with generated report + +Feature +------- + +Example of features: + +- introduction of new rule +- enhancement of existing rule to cover more cases (for example adding support for newly introduced PHP syntax) +- introduction of new ruleset +- update of existing ruleset (for example adjusting it to match newest style of given community or adding newly implemented rule that was supposed to be followed by style of given community, yet not implemented as a rule before) diff --git a/vendor/friendsofphp/php-cs-fixer/logo.md b/vendor/friendsofphp/php-cs-fixer/logo.md index c5f7b39914..68e03a582f 100644 --- a/vendor/friendsofphp/php-cs-fixer/logo.md +++ b/vendor/friendsofphp/php-cs-fixer/logo.md @@ -1,3 +1,3 @@ -The logo is © 2010-2022 Sensio Labs. +The logo is © 2010+ Sensio Labs. -Original resolution can be found at https://github.com/PHP-CS-Fixer/logo . +Original resolution can be found at . diff --git a/vendor/friendsofphp/php-cs-fixer/php-cs-fixer b/vendor/friendsofphp/php-cs-fixer/php-cs-fixer old mode 100644 new mode 100755 index c1442aab42..402b49ae62 --- a/vendor/friendsofphp/php-cs-fixer/php-cs-fixer +++ b/vendor/friendsofphp/php-cs-fixer/php-cs-fixer @@ -21,28 +21,15 @@ set_error_handler(static function ($severity, $message, $file, $line) { // check environment requirements (function () { - if (defined('HHVM_VERSION_ID')) { - fwrite(STDERR, "HHVM is not supported.\n"); - - if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { - fwrite(STDERR, "Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.\n"); - } else { - exit(1); - } - } elseif (!defined('PHP_VERSION_ID')) { // PHP_VERSION_ID is available as of PHP 5.2.7 - fwrite(STDERR, 'PHP version no supported, please update. Current PHP version: '.PHP_VERSION.".\n"); - - exit(1); - } elseif (\PHP_VERSION_ID === 80000) { + if (\PHP_VERSION_ID === 80000) { fwrite(STDERR, "PHP CS Fixer is not able run on PHP 8.0.0 due to bug in PHP tokenizer (https://bugs.php.net/bug.php?id=80462).\n"); fwrite(STDERR, "Update PHP version to unblock execution.\n"); exit(1); - } elseif ( - \PHP_VERSION_ID < 70400 - || \PHP_VERSION_ID >= 80200 - ) { - fwrite(STDERR, "PHP needs to be a minimum version of PHP 7.4.0 and maximum version of PHP 8.1.*.\n"); + } + + if (\PHP_VERSION_ID < 70400 || \PHP_VERSION_ID >= 80400) { + fwrite(STDERR, "PHP needs to be a minimum version of PHP 7.4.0 and maximum version of PHP 8.3.*.\n"); fwrite(STDERR, 'Current PHP version: '.PHP_VERSION.".\n"); if (getenv('PHP_CS_FIXER_IGNORE_ENV')) { @@ -50,7 +37,7 @@ set_error_handler(static function ($severity, $message, $file, $line) { } else { fwrite(STDERR, "To ignore this requirement please set `PHP_CS_FIXER_IGNORE_ENV`.\n"); fwrite(STDERR, "If you use PHP version higher than supported, you may experience code modified in a wrong way.\n"); - fwrite(STDERR, "Please report such cases at https://github.com/FriendsOfPHP/PHP-CS-Fixer .\n"); + fwrite(STDERR, "Please report such cases at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer .\n"); exit(1); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php index 79d64171df..7ca969fb27 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractDoctrineAnnotationFixer.php @@ -26,22 +26,30 @@ /** * @internal + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * scalar_types?: bool, + * union_types?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * scalar_types: bool, + * union_types: bool + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ abstract class AbstractDoctrineAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface { - private array $classyElements; - /** - * {@inheritdoc} + * @var array */ + private array $classyElements; + public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // fetch indices one time, this is safe as we never add or remove a token during fixing @@ -56,7 +64,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $doctrineAnnotationTokens = DoctrineAnnotationTokens::createFromDocComment( $docCommentToken, - $this->configuration['ignored_tags'] + $this->configuration['ignored_tags'] // @phpstan-ignore-line ); $this->fixAnnotations($doctrineAnnotationTokens); @@ -69,23 +77,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void */ abstract protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens): void; - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('ignored_tags', 'List of tags that must not be treated as Doctrine Annotations.')) - ->setAllowedTypes(['array']) - ->setAllowedValues([static function (array $values): bool { - foreach ($values as $value) { - if (!\is_string($value)) { - return false; - } - } - - return true; - }]) + ->setAllowedTypes(['string[]']) ->setDefault([ // PHPDocumentor 1 'abstract', @@ -204,13 +200,19 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn private function nextElementAcceptsDoctrineAnnotations(Tokens $tokens, int $index): bool { + $classModifiers = [T_ABSTRACT, T_FINAL]; + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required + $classModifiers[] = T_READONLY; + } + do { $index = $tokens->getNextMeaningfulToken($index); if (null === $index) { return false; } - } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL])); + } while ($tokens[$index]->isGivenKind($classModifiers)); if ($tokens[$index]->isGivenKind(T_CLASS)) { return true; diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php index 3b29809e63..fd193f410a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractFixer.php @@ -14,19 +14,11 @@ namespace PhpCsFixer; -use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; -use PhpCsFixer\ConfigurationException\InvalidForEnvFixerConfigurationException; use PhpCsFixer\ConfigurationException\RequiredFixerConfigurationException; -use PhpCsFixer\Console\Application; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\FixerInterface; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; -use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption; -use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; -use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; use PhpCsFixer\Tokenizer\Tokens; -use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; -use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; /** * @author Dariusz Rumiński @@ -35,21 +27,11 @@ */ abstract class AbstractFixer implements FixerInterface { - /** - * @var null|array - */ - protected $configuration; - /** * @var WhitespacesFixerConfig */ protected $whitespacesConfig; - /** - * @var null|FixerConfigurationResolverInterface - */ - private $configurationDefinition; - public function __construct() { if ($this instanceof ConfigurableFixerInterface) { @@ -67,7 +49,7 @@ public function __construct() final public function fix(\SplFileInfo $file, Tokens $tokens): void { - if ($this instanceof ConfigurableFixerInterface && null === $this->configuration) { + if ($this instanceof ConfigurableFixerInterface && property_exists($this, 'configuration') && null === $this->configuration) { throw new RequiredFixerConfigurationException($this->getName(), 'Configuration is required.'); } @@ -76,17 +58,11 @@ final public function fix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return false; } - /** - * {@inheritdoc} - */ public function getName(): string { $nameParts = explode('\\', static::class); @@ -95,81 +71,16 @@ public function getName(): string return Utils::camelCaseToUnderscore($name); } - /** - * {@inheritdoc} - */ public function getPriority(): int { return 0; } - /** - * {@inheritdoc} - */ public function supports(\SplFileInfo $file): bool { return true; } - public function configure(array $configuration): void - { - if (!$this instanceof ConfigurableFixerInterface) { - throw new \LogicException('Cannot configure using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".'); - } - - foreach ($this->getConfigurationDefinition()->getOptions() as $option) { - if (!$option instanceof DeprecatedFixerOption) { - continue; - } - - $name = $option->getName(); - if (\array_key_exists($name, $configuration)) { - Utils::triggerDeprecation(new \InvalidArgumentException(sprintf( - 'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s', - $name, - $this->getName(), - Application::getMajorVersion() + 1, - str_replace('`', '"', $option->getDeprecationMessage()) - ))); - } - } - - try { - $this->configuration = $this->getConfigurationDefinition()->resolve($configuration); - } catch (MissingOptionsException $exception) { - throw new RequiredFixerConfigurationException( - $this->getName(), - sprintf('Missing required configuration: %s', $exception->getMessage()), - $exception - ); - } catch (InvalidOptionsForEnvException $exception) { - throw new InvalidForEnvFixerConfigurationException( - $this->getName(), - sprintf('Invalid configuration for env: %s', $exception->getMessage()), - $exception - ); - } catch (ExceptionInterface $exception) { - throw new InvalidFixerConfigurationException( - $this->getName(), - sprintf('Invalid configuration: %s', $exception->getMessage()), - $exception - ); - } - } - - public function getConfigurationDefinition(): FixerConfigurationResolverInterface - { - if (!$this instanceof ConfigurableFixerInterface) { - throw new \LogicException(sprintf('Cannot get configuration definition using Abstract parent, child "%s" not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".', static::class)); - } - - if (null === $this->configurationDefinition) { - $this->configurationDefinition = $this->createConfigurationDefinition(); - } - - return $this->configurationDefinition; - } - public function setWhitespacesConfig(WhitespacesFixerConfig $config): void { if (!$this instanceof WhitespacesAwareFixerInterface) { @@ -181,15 +92,6 @@ public function setWhitespacesConfig(WhitespacesFixerConfig $config): void abstract protected function applyFix(\SplFileInfo $file, Tokens $tokens): void; - protected function createConfigurationDefinition(): FixerConfigurationResolverInterface - { - if (!$this instanceof ConfigurableFixerInterface) { - throw new \LogicException('Cannot create configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".'); - } - - throw new \LogicException('Not implemented.'); - } - private function getDefaultWhitespacesFixerConfig(): WhitespacesFixerConfig { static $defaultWhitespacesFixerConfig = null; diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php index e9d7a120ce..da862da736 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractFopenFlagFixer.php @@ -22,17 +22,11 @@ */ abstract class AbstractFopenFlagFixer extends AbstractFunctionReferenceFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php index 350b87cf3d..dfddeb5167 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractFunctionReferenceFixer.php @@ -29,17 +29,11 @@ abstract class AbstractFunctionReferenceFixer extends AbstractFixer */ private $functionsAnalyzer; - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -49,7 +43,7 @@ public function isRisky(): bool * Looks up Tokens sequence for suitable candidates and delivers boundaries information, * which can be supplied by other methods in this abstract class. * - * @return null|int[] returns $functionName, $openParenthesis, $closeParenthesis packed into array + * @return ?array{int, int, int} returns $functionName, $openParenthesis, $closeParenthesis packed into array */ protected function find(string $functionNameToSearch, Tokens $tokens, int $start = 0, ?int $end = null): ?array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php deleted file mode 100644 index f574b97040..0000000000 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractLinesBeforeNamespaceFixer.php +++ /dev/null @@ -1,120 +0,0 @@ - - * Dariusz Rumiński - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace PhpCsFixer; - -use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; - -/** - * This abstract fixer is responsible for ensuring that a certain number of - * lines prefix a namespace declaration. - * - * @author Graham Campbell - * - * @internal - */ -abstract class AbstractLinesBeforeNamespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface -{ - /** - * Make sure # of line breaks prefixing namespace is within given range. - * - * @param int $expectedMin min. # of line breaks - * @param int $expectedMax max. # of line breaks - */ - protected function fixLinesBeforeNamespace(Tokens $tokens, int $index, int $expectedMin, int $expectedMax): void - { - // Let's determine the total numbers of new lines before the namespace - // and the opening token - $openingTokenIndex = null; - $precedingNewlines = 0; - $newlineInOpening = false; - $openingToken = null; - - for ($i = 1; $i <= 2; ++$i) { - if (isset($tokens[$index - $i])) { - $token = $tokens[$index - $i]; - - if ($token->isGivenKind(T_OPEN_TAG)) { - $openingToken = $token; - $openingTokenIndex = $index - $i; - $newlineInOpening = str_contains($token->getContent(), "\n"); - - if ($newlineInOpening) { - ++$precedingNewlines; - } - - break; - } - - if (false === $token->isGivenKind(T_WHITESPACE)) { - break; - } - - $precedingNewlines += substr_count($token->getContent(), "\n"); - } - } - - if ($precedingNewlines >= $expectedMin && $precedingNewlines <= $expectedMax) { - return; - } - - $previousIndex = $index - 1; - $previous = $tokens[$previousIndex]; - - if (0 === $expectedMax) { - // Remove all the previous new lines - if ($previous->isWhitespace()) { - $tokens->clearAt($previousIndex); - } - - // Remove new lines in opening token - if ($newlineInOpening) { - $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, rtrim($openingToken->getContent()).' ']); - } - - return; - } - - $lineEnding = $this->whitespacesConfig->getLineEnding(); - $newlinesForWhitespaceToken = $expectedMax; - - if (null !== $openingToken) { - // Use the configured line ending for the PHP opening tag - $content = rtrim($openingToken->getContent()); - $newContent = $content.$lineEnding; - $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, $newContent]); - --$newlinesForWhitespaceToken; - } - - if (0 === $newlinesForWhitespaceToken) { - // We have all the needed new lines in the opening tag - if ($previous->isWhitespace()) { - // Let's remove the previous token containing extra new lines - $tokens->clearAt($previousIndex); - } - - return; - } - - if ($previous->isWhitespace()) { - // Fix the previous whitespace token - $tokens[$previousIndex] = new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken).substr($previous->getContent(), strrpos($previous->getContent(), "\n") + 1)]); - } else { - // Add a new whitespace token - $tokens->insertAt($index, new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)])); - } - } -} diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php index 96f6bb09f6..b45be5acdb 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractNoUselessElseFixer.php @@ -18,9 +18,6 @@ abstract class AbstractNoUselessElseFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // should be run before NoWhitespaceInBlankLineFixer, NoExtraBlankLinesFixer, BracesFixer and after NoEmptyStatementFixer. @@ -96,7 +93,7 @@ protected function isSuperfluousElse(Tokens $tokens, int $index): bool * * @param int $index T_IF, T_ELSE, T_ELSEIF * - * @return int[] + * @return array{int, int} */ private function getPreviousBlock(Tokens $tokens, int $index): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php index 1a2792f70f..174416cb19 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocToTypeDeclarationFixer.php @@ -16,7 +16,9 @@ use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,19 +30,36 @@ /** * @internal + * + * @phpstan-type _CommonTypeInfo array{commonType: string, isNullable: bool} + * @phpstan-type _AutogeneratedInputConfiguration array{ + * scalar_types?: bool, + * union_types?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * scalar_types: bool, + * union_types: bool + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ abstract class AbstractPhpdocToTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface { - private const CLASS_REGEX = '/^\\\\?[a-zA-Z_\\x7f-\\xff](?:\\\\?[a-zA-Z0-9_\\x7f-\\xff]+)*$/'; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private const REGEX_CLASS = '(?:\\\?+'.TypeExpression::REGEX_IDENTIFIER + .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)'; /** * @var array */ private array $versionSpecificTypes = [ - 'void' => 70100, - 'iterable' => 70100, - 'object' => 70200, - 'mixed' => 80000, + 'void' => 7_01_00, + 'iterable' => 7_01_00, + 'object' => 7_02_00, + 'mixed' => 8_00_00, + 'never' => 8_01_00, ]; /** @@ -58,9 +77,6 @@ abstract class AbstractPhpdocToTypeDeclarationFixer extends AbstractFixer implem */ private static array $syntaxValidationCache = []; - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -68,9 +84,6 @@ public function isRisky(): bool abstract protected function isSkippedType(string $type): bool; - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -78,6 +91,10 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), + (new FixerOptionBuilder('union_types', 'Fix also union types; turned on by default on PHP >= 8.0.0.')) + ->setAllowedTypes(['bool']) + ->setDefault(\PHP_VERSION_ID >= 8_00_00) + ->getOption(), ]); } @@ -106,7 +123,7 @@ protected function findFunctionDocComment(Tokens $tokens, int $index): ?int } /** - * @return Annotation[] + * @return list */ protected function getAnnotationsFromDocComment(string $name, Tokens $tokens, int $docCommentIndex): array { @@ -126,52 +143,53 @@ protected function getAnnotationsFromDocComment(string $name, Tokens $tokens, in } /** - * @return Token[] + * @return list */ protected function createTypeDeclarationTokens(string $type, bool $isNullable): array { - static $specialTypes = [ - 'array' => [CT::T_ARRAY_TYPEHINT, 'array'], - 'callable' => [T_CALLABLE, 'callable'], - 'static' => [T_STATIC, 'static'], - ]; - $newTokens = []; if (true === $isNullable && 'mixed' !== $type) { $newTokens[] = new Token([CT::T_NULLABLE_TYPE, '?']); } - if (isset($specialTypes[$type])) { - $newTokens[] = new Token($specialTypes[$type]); - } else { - $typeUnqualified = ltrim($type, '\\'); - - if (isset($this->scalarTypes[$typeUnqualified]) || isset($this->versionSpecificTypes[$typeUnqualified])) { - // 'scalar's, 'void', 'iterable' and 'object' must be unqualified - $newTokens[] = new Token([T_STRING, $typeUnqualified]); - } else { - foreach (explode('\\', $type) as $nsIndex => $value) { - if (0 === $nsIndex && '' === $value) { - continue; - } - - if (0 < $nsIndex) { - $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); - } + $newTokens = array_merge( + $newTokens, + $this->createTokensFromRawType($type)->toArray() + ); - $newTokens[] = new Token([T_STRING, $value]); + // 'scalar's, 'void', 'iterable' and 'object' must be unqualified + foreach ($newTokens as $i => $token) { + if ($token->isGivenKind(T_STRING)) { + $typeUnqualified = $token->getContent(); + + if ( + (isset($this->scalarTypes[$typeUnqualified]) || isset($this->versionSpecificTypes[$typeUnqualified])) + && isset($newTokens[$i - 1]) + && '\\' === $newTokens[$i - 1]->getContent() + ) { + unset($newTokens[$i - 1]); } } } - return $newTokens; + return array_values($newTokens); } - protected function getCommonTypeFromAnnotation(Annotation $annotation, bool $isReturnType): ?array - { - $typesExpression = $annotation->getTypeExpression(); + /** + * Each fixer inheriting from this class must define a way of creating token collection representing type + * gathered from phpDoc, e.g. `Foo|Bar` should be transformed into 3 tokens (`Foo`, `|` and `Bar`). + * This can't be standardised, because some types may be allowed in one place, and invalid in others. + * + * @param string $type Type determined (and simplified) from phpDoc + */ + abstract protected function createTokensFromRawType(string $type): Tokens; + /** + * @return ?_CommonTypeInfo + */ + protected function getCommonTypeInfo(TypeExpression $typesExpression, bool $isReturnType): ?array + { $commonType = $typesExpression->getCommonType(); $isNullable = $typesExpression->allowsNull(); @@ -183,7 +201,7 @@ protected function getCommonTypeFromAnnotation(Annotation $annotation, bool $isR return null; } - if ('static' === $commonType && (!$isReturnType || \PHP_VERSION_ID < 80000)) { + if ('static' === $commonType && (!$isReturnType || \PHP_VERSION_ID < 8_00_00)) { $commonType = 'self'; } @@ -199,11 +217,75 @@ protected function getCommonTypeFromAnnotation(Annotation $annotation, bool $isR if (false === $this->configuration['scalar_types']) { return null; } - } elseif (1 !== Preg::match(self::CLASS_REGEX, $commonType)) { + } elseif (!Preg::match('/^'.self::REGEX_CLASS.'$/', $commonType)) { + return null; + } + + return ['commonType' => $commonType, 'isNullable' => $isNullable]; + } + + protected function getUnionTypes(TypeExpression $typesExpression, bool $isReturnType): ?string + { + if (\PHP_VERSION_ID < 8_00_00) { + return null; + } + + if (!$typesExpression->isUnionType() || '|' !== $typesExpression->getTypesGlue()) { + return null; + } + + if (false === $this->configuration['union_types']) { + return null; + } + + $types = $typesExpression->getTypes(); + $isNullable = $typesExpression->allowsNull(); + $unionTypes = []; + $containsOtherThanIterableType = false; + $containsOtherThanEmptyType = false; + + foreach ($types as $type) { + if ('null' === $type) { + continue; + } + + if ($this->isSkippedType($type)) { + return null; + } + + if (isset($this->versionSpecificTypes[$type]) && \PHP_VERSION_ID < $this->versionSpecificTypes[$type]) { + return null; + } + + $typeExpression = new TypeExpression($type, null, []); + $commonType = $typeExpression->getCommonType(); + + if (!$containsOtherThanIterableType && !\in_array($commonType, ['array', \Traversable::class, 'iterable'], true)) { + $containsOtherThanIterableType = true; + } + if ($isReturnType && !$containsOtherThanEmptyType && !\in_array($commonType, ['null', 'void', 'never'], true)) { + $containsOtherThanEmptyType = true; + } + + if (!$isNullable && $typesExpression->allowsNull()) { + $isNullable = true; + } + + $unionTypes[] = $commonType; + } + + if (!$containsOtherThanIterableType) { return null; } + if ($isReturnType && !$containsOtherThanEmptyType) { + return null; + } + + if ($isNullable) { + $unionTypes[] = 'null'; + } - return [$commonType, $isNullable]; + return implode($typesExpression->getTypesGlue(), array_unique($unionTypes)); } final protected function isValidSyntax(string $code): bool diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php index 44fa3fb7f8..82b8e57f49 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractPhpdocTypesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -31,13 +32,10 @@ abstract class AbstractPhpdocTypesFixer extends AbstractFixer /** * The annotation tags search inside. * - * @var string[] + * @var list */ - protected $tags; + protected array $tags; - /** - * {@inheritdoc} - */ public function __construct() { parent::__construct(); @@ -45,17 +43,11 @@ public function __construct() $this->tags = Annotation::getTagsWithTypes(); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -102,27 +94,30 @@ private function fixTypes(Annotation $annotation): void } /** - * @param string[] $types + * @param list $types * - * @return string[] + * @return list */ private function normalizeTypes(array $types): array { - foreach ($types as $index => $type) { - $types[$index] = $this->normalizeType($type); - } - - return $types; - } - - /** - * Prepare the type and normalize it. - */ - private function normalizeType(string $type): string - { - return str_ends_with($type, '[]') - ? $this->normalizeType(substr($type, 0, -2)).'[]' - : $this->normalize($type) - ; + return array_map( + function (string $type): string { + $typeExpression = new TypeExpression($type, null, []); + + $typeExpression->walkTypes(function (TypeExpression $type): void { + if (!$type->isUnionType()) { + $value = $this->normalize($type->toString()); + + // TODO TypeExpression should be immutable and walkTypes method should be changed to mapTypes method + \Closure::bind(static function () use ($type, $value): void { + $type->value = $value; + }, null, TypeExpression::class)(); + } + }); + + return $typeExpression->toString(); + }, + $types + ); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php index cdf5dacbcc..96eab156ad 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/AbstractProxyFixer.php @@ -28,7 +28,7 @@ abstract class AbstractProxyFixer extends AbstractFixer /** * @var array */ - protected $proxyFixers; + protected array $proxyFixers = []; public function __construct() { @@ -39,9 +39,6 @@ public function __construct() parent::__construct(); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { foreach ($this->proxyFixers as $fixer) { @@ -53,9 +50,6 @@ public function isCandidate(Tokens $tokens): bool return false; } - /** - * {@inheritdoc} - */ public function isRisky(): bool { foreach ($this->proxyFixers as $fixer) { @@ -67,9 +61,6 @@ public function isRisky(): bool return false; } - /** - * {@inheritdoc} - */ public function getPriority(): int { if (\count($this->proxyFixers) > 1) { @@ -79,9 +70,6 @@ public function getPriority(): int return reset($this->proxyFixers)->getPriority(); } - /** - * {@inheritdoc} - */ public function supports(\SplFileInfo $file): bool { foreach ($this->proxyFixers as $fixer) { @@ -93,9 +81,6 @@ public function supports(\SplFileInfo $file): bool return false; } - /** - * {@inheritdoc} - */ public function setWhitespacesConfig(WhitespacesFixerConfig $config): void { parent::setWhitespacesConfig($config); @@ -107,9 +92,6 @@ public function setWhitespacesConfig(WhitespacesFixerConfig $config): void } } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($this->proxyFixers as $fixer) { @@ -118,7 +100,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @return FixerInterface[] + * @return list */ abstract protected function createProxyFixers(): array; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php index f39c1f8cee..29976749c0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/Cache.php @@ -14,6 +14,8 @@ namespace PhpCsFixer\Cache; +use PhpCsFixer\Utils; + /** * @author Andreas Möller * @@ -24,7 +26,7 @@ final class Cache implements CacheInterface private SignatureInterface $signature; /** - * @var array + * @var array */ private array $hashes = []; @@ -43,7 +45,7 @@ public function has(string $file): bool return \array_key_exists($file, $this->hashes); } - public function get(string $file): ?int + public function get(string $file): ?string { if (!$this->has($file)) { return null; @@ -52,7 +54,7 @@ public function get(string $file): ?int return $this->hashes[$file]; } - public function set(string $file, int $hash): void + public function set(string $file, string $hash): void { $this->hashes[$file] = $hash; } @@ -73,8 +75,8 @@ public function toJson(): string 'hashes' => $this->hashes, ]); - if (JSON_ERROR_NONE !== json_last_error()) { - throw new \UnexpectedValueException(sprintf( + if (JSON_ERROR_NONE !== json_last_error() || false === $json) { + throw new \UnexpectedValueException(\sprintf( 'Cannot encode cache signature to JSON, error: "%s". If you have non-UTF8 chars in your signature, like in license for `header_comment`, consider enabling `ext-mbstring` or install `symfony/polyfill-mbstring`.', json_last_error_msg() )); @@ -91,7 +93,7 @@ public static function fromJson(string $json): self $data = json_decode($json, true); if (null === $data && JSON_ERROR_NONE !== json_last_error()) { - throw new \InvalidArgumentException(sprintf( + throw new \InvalidArgumentException(\sprintf( 'Value needs to be a valid JSON string, got "%s", error: "%s".', $json, json_last_error_msg() @@ -110,9 +112,9 @@ public static function fromJson(string $json): self $missingKeys = array_diff_key(array_flip($requiredKeys), $data); if (\count($missingKeys) > 0) { - throw new \InvalidArgumentException(sprintf( - 'JSON data is missing keys "%s"', - implode('", "', $missingKeys) + throw new \InvalidArgumentException(\sprintf( + 'JSON data is missing keys %s', + Utils::naturalLanguageJoin(array_keys($missingKeys)) )); } @@ -126,8 +128,24 @@ public static function fromJson(string $json): self $cache = new self($signature); - $cache->hashes = $data['hashes']; + // before v3.11.1 the hashes were crc32 encoded and saved as integers + // @TODO: remove the to string cast/array_map in v4.0 + $cache->hashes = array_map(static fn ($v): string => \is_int($v) ? (string) $v : $v, $data['hashes']); return $cache; } + + /** + * @internal + */ + public function backfillHashes(self $oldCache): bool + { + if (!$this->getSignature()->equals($oldCache->getSignature())) { + return false; + } + + $this->hashes = array_merge($oldCache->hashes, $this->hashes); + + return true; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php index 6b5f4bddf5..29ab719793 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheInterface.php @@ -25,9 +25,9 @@ public function getSignature(): SignatureInterface; public function has(string $file): bool; - public function get(string $file): ?int; + public function get(string $file): ?string; - public function set(string $file, int $hash): void; + public function set(string $file, string $hash): void; public function clear(string $file): void; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php index 4e82d0c9cb..b9fb2ff635 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/CacheManagerInterface.php @@ -24,4 +24,6 @@ interface CacheManagerInterface public function needFixing(string $file, string $fileContent): bool; public function setFile(string $file, string $fileContent): void; + + public function setFileHash(string $file, string $hash): void; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php index 90882af0a0..2657389651 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/Directory.php @@ -28,9 +28,6 @@ public function __construct(string $directoryName) $this->directoryName = $directoryName; } - /** - * {@inheritdoc} - */ public function getRelativePathTo(string $file): string { $file = $this->normalizePath($file); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php index 8d91dae7b1..d81e91c166 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileCacheManager.php @@ -14,6 +14,8 @@ namespace PhpCsFixer\Cache; +use PhpCsFixer\Tokenizer\CodeHasher; + /** * Class supports caching information about state of fixing files. * @@ -32,6 +34,8 @@ */ final class FileCacheManager implements CacheManagerInterface { + public const WRITE_FREQUENCY = 10; + private FileHandlerInterface $handler; private SignatureInterface $signature; @@ -40,6 +44,10 @@ final class FileCacheManager implements CacheManagerInterface private DirectoryInterface $cacheDirectory; + private int $writeCounter = 0; + + private bool $signatureWasUpdated = false; + /** * @var CacheInterface */ @@ -61,7 +69,9 @@ public function __construct( public function __destruct() { - $this->writeCache(); + if (true === $this->signatureWasUpdated || 0 !== $this->writeCounter) { + $this->writeCache(); + } } /** @@ -93,17 +103,23 @@ public function needFixing(string $file, string $fileContent): bool public function setFile(string $file, string $fileContent): void { - $file = $this->cacheDirectory->getRelativePathTo($file); + $this->setFileHash($file, $this->calcHash($fileContent)); + } - $hash = $this->calcHash($fileContent); + public function setFileHash(string $file, string $hash): void + { + $file = $this->cacheDirectory->getRelativePathTo($file); if ($this->isDryRun && $this->cache->has($file) && $this->cache->get($file) !== $hash) { $this->cache->clear($file); - - return; + } else { + $this->cache->set($file, $hash); } - $this->cache->set($file, $hash); + if (self::WRITE_FREQUENCY === ++$this->writeCounter) { + $this->writeCounter = 0; + $this->writeCache(); + } } private function readCache(): void @@ -112,6 +128,7 @@ private function readCache(): void if (null === $cache || !$this->signature->equals($cache->getSignature())) { $cache = new Cache($this->signature); + $this->signatureWasUpdated = true; } $this->cache = $cache; @@ -122,8 +139,8 @@ private function writeCache(): void $this->handler->write($this->cache); } - private function calcHash(string $content): int + private function calcHash(string $content): string { - return crc32($content); + return CodeHasher::calculateCodeHash($content); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php index 059e6b4267..a1b83ed78c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/FileHandler.php @@ -18,89 +18,167 @@ /** * @author Andreas Möller + * @author Dariusz Rumiński * * @internal */ final class FileHandler implements FileHandlerInterface { - private string $file; + private \SplFileInfo $fileInfo; + + private int $fileMTime = 0; public function __construct(string $file) { - $this->file = $file; + $this->fileInfo = new \SplFileInfo($file); } public function getFile(): string { - return $this->file; + return $this->fileInfo->getPathname(); } public function read(): ?CacheInterface { - if (!file_exists($this->file)) { + if (!$this->fileInfo->isFile() || !$this->fileInfo->isReadable()) { return null; } - $content = file_get_contents($this->file); + $fileObject = $this->fileInfo->openFile('r'); - try { - $cache = Cache::fromJson($content); - } catch (\InvalidArgumentException $exception) { - return null; - } + $cache = $this->readFromHandle($fileObject); + $this->fileMTime = $this->getFileCurrentMTime(); + + unset($fileObject); // explicitly close file handler return $cache; } public function write(CacheInterface $cache): void { - $content = $cache->toJson(); - - if (file_exists($this->file)) { - if (is_dir($this->file)) { - throw new IOException( - sprintf('Cannot write cache file "%s" as the location exists as directory.', realpath($this->file)), - 0, - null, - $this->file - ); + $this->ensureFileIsWriteable(); + + $fileObject = $this->fileInfo->openFile('r+'); + + if (method_exists($cache, 'backfillHashes') && $this->fileMTime < $this->getFileCurrentMTime()) { + $resultOfFlock = $fileObject->flock(LOCK_EX); + if (false === $resultOfFlock) { + // Lock failed, OK - we continue without the lock. + // noop } - if (!is_writable($this->file)) { - throw new IOException( - sprintf('Cannot write to file "%s" as it is not writable.', realpath($this->file)), - 0, - null, - $this->file - ); + $oldCache = $this->readFromHandle($fileObject); + + $fileObject->rewind(); + + if (null !== $oldCache) { + $cache->backfillHashes($oldCache); } - } else { - $dir = \dirname($this->file); - - if (!is_dir($dir)) { - throw new IOException( - sprintf('Directory of cache file "%s" does not exists.', $this->file), - 0, - null, - $this->file - ); + } + + $resultOfTruncate = $fileObject->ftruncate(0); + if (false === $resultOfTruncate) { + // Truncate failed. OK - we do not save the cache. + return; + } + + $resultOfWrite = $fileObject->fwrite($cache->toJson()); + if (false === $resultOfWrite) { + // Write failed. OK - we did not save the cache. + return; + } + + $resultOfFlush = $fileObject->fflush(); + if (false === $resultOfFlush) { + // Flush failed. OK - part of cache can be missing, in case this was last chunk in this pid. + // noop + } + + $this->fileMTime = time(); // we could take the fresh `mtime` of file that we just modified with `$this->getFileCurrentMTime()`, but `time()` should be good enough here and reduce IO operation + } + + private function getFileCurrentMTime(): int + { + clearstatcache(true, $this->fileInfo->getPathname()); + + $mtime = $this->fileInfo->getMTime(); + + if (false === $mtime) { + // cannot check mtime? OK - let's pretend file is old. + $mtime = 0; + } + + return $mtime; + } + + private function readFromHandle(\SplFileObject $fileObject): ?CacheInterface + { + try { + $size = $fileObject->getSize(); + if (false === $size || 0 === $size) { + return null; + } + + $content = $fileObject->fread($size); + + if (false === $content) { + return null; } - @touch($this->file); - @chmod($this->file, 0666); + return Cache::fromJson($content); + } catch (\InvalidArgumentException $exception) { + return null; } + } - $bytesWritten = @file_put_contents($this->file, $content); + private function ensureFileIsWriteable(): void + { + if ($this->fileInfo->isFile() && $this->fileInfo->isWritable()) { + // all good + return; + } - if (false === $bytesWritten) { - $error = error_get_last(); + if ($this->fileInfo->isDir()) { + throw new IOException( + \sprintf('Cannot write cache file "%s" as the location exists as directory.', $this->fileInfo->getRealPath()), + 0, + null, + $this->fileInfo->getPathname() + ); + } + if ($this->fileInfo->isFile() && !$this->fileInfo->isWritable()) { throw new IOException( - sprintf('Failed to write file "%s", "%s".', $this->file, $error['message'] ?? 'no reason available'), + \sprintf('Cannot write to file "%s" as it is not writable.', $this->fileInfo->getRealPath()), 0, null, - $this->file + $this->fileInfo->getPathname() ); } + + $this->createFile($this->fileInfo->getPathname()); + } + + private function createFile(string $file): void + { + $dir = \dirname($file); + + // Ensure path is created, but ignore if already exists. FYI: ignore EA suggestion in IDE, + // `mkdir()` returns `false` for existing paths, so we can't mix it with `is_dir()` in one condition. + if (!@is_dir($dir)) { + @mkdir($dir, 0777, true); + } + + if (!@is_dir($dir)) { + throw new IOException( + \sprintf('Directory of cache file "%s" does not exists and couldn\'t be created.', $file), + 0, + null, + $file + ); + } + + @touch($file); + @chmod($file, 0666); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php index 63094804d0..3a5fe99141 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/NullCacheManager.php @@ -16,6 +16,7 @@ /** * @author Andreas Möller + * @author Dariusz Rumiński * * @internal */ @@ -26,7 +27,7 @@ public function needFixing(string $file, string $fileContent): bool return true; } - public function setFile(string $file, string $fileContent): void - { - } + public function setFile(string $file, string $fileContent): void {} + + public function setFileHash(string $file, string $hash): void {} } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php index 4ff463095b..48c9628989 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/Signature.php @@ -29,15 +29,21 @@ final class Signature implements SignatureInterface private string $lineEnding; + /** + * @var array|bool> + */ private array $rules; + /** + * @param array|bool> $rules + */ public function __construct(string $phpVersion, string $fixerVersion, string $indent, string $lineEnding, array $rules) { $this->phpVersion = $phpVersion; $this->fixerVersion = $fixerVersion; $this->indent = $indent; $this->lineEnding = $lineEnding; - $this->rules = self::utf8Encode($rules); + $this->rules = self::makeJsonEncodable($rules); } public function getPhpVersion(): string @@ -74,11 +80,16 @@ public function equals(SignatureInterface $signature): bool && $this->rules === $signature->getRules(); } - private static function utf8Encode(array $data): array + /** + * @param array|bool> $data + * + * @return array|bool> + */ + private static function makeJsonEncodable(array $data): array { array_walk_recursive($data, static function (&$item): void { if (\is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { - $item = utf8_encode($item); + $item = base64_encode($item); } }); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php index 062e96ddee..cc9521411e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Cache/SignatureInterface.php @@ -29,10 +29,10 @@ public function getIndent(): string; public function getLineEnding(): string; - public function getRules(): array; - /** - * @param SignatureInterface $signature + * @return array|bool> */ + public function getRules(): array; + public function equals(self $signature): bool; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Config.php b/vendor/friendsofphp/php-cs-fixer/src/Config.php index 88948bfbe6..29ef94c489 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Config.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Config.php @@ -15,25 +15,27 @@ namespace PhpCsFixer; use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Runner\Parallel\ParallelConfig; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; /** * @author Fabien Potencier * @author Katsuhiro Ogawa * @author Dariusz Rumiński */ -class Config implements ConfigInterface +class Config implements ConfigInterface, ParallelAwareConfigInterface { private string $cacheFile = '.php-cs-fixer.cache'; /** - * @var FixerInterface[] + * @var list */ private array $customFixers = []; /** - * @var null|iterable + * @var null|iterable<\SplFileInfo> */ - private $finder; + private ?iterable $finder = null; private string $format = 'txt'; @@ -47,31 +49,46 @@ class Config implements ConfigInterface private string $name; + private ParallelConfig $parallelConfig; + /** * @var null|string */ private $phpExecutable; - private array $rules = ['@PSR12' => true]; + /** + * @TODO: 4.0 - update to @PER + * + * @var array|bool> + */ + private array $rules; private bool $usingCache = true; public function __construct(string $name = 'default') { - $this->name = $name; + // @TODO 4.0 cleanup + if (Utils::isFutureModeEnabled()) { + $this->name = $name.' (future mode)'; + $this->rules = ['@PER-CS' => true]; + } else { + $this->name = $name; + $this->rules = ['@PSR12' => true]; + } + + // @TODO 4.0 cleanup + if (Utils::isFutureModeEnabled() || filter_var(getenv('PHP_CS_FIXER_PARALLEL'), FILTER_VALIDATE_BOOL)) { + $this->parallelConfig = ParallelConfigFactory::detect(); + } else { + $this->parallelConfig = ParallelConfigFactory::sequential(); + } } - /** - * {@inheritdoc} - */ public function getCacheFile(): string { return $this->cacheFile; } - /** - * {@inheritdoc} - */ public function getCustomFixers(): array { return $this->customFixers; @@ -82,88 +99,61 @@ public function getCustomFixers(): array */ public function getFinder(): iterable { - if (null === $this->finder) { - $this->finder = new Finder(); - } + $this->finder ??= new Finder(); return $this->finder; } - /** - * {@inheritdoc} - */ public function getFormat(): string { return $this->format; } - /** - * {@inheritdoc} - */ public function getHideProgress(): bool { return $this->hideProgress; } - /** - * {@inheritdoc} - */ public function getIndent(): string { return $this->indent; } - /** - * {@inheritdoc} - */ public function getLineEnding(): string { return $this->lineEnding; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ + public function getParallelConfig(): ParallelConfig + { + return $this->parallelConfig; + } + public function getPhpExecutable(): ?string { return $this->phpExecutable; } - /** - * {@inheritdoc} - */ public function getRiskyAllowed(): bool { return $this->isRiskyAllowed; } - /** - * {@inheritdoc} - */ public function getRules(): array { return $this->rules; } - /** - * {@inheritdoc} - */ public function getUsingCache(): bool { return $this->usingCache; } - /** - * {@inheritdoc} - */ public function registerCustomFixers(iterable $fixers): ConfigInterface { foreach ($fixers as $fixer) { @@ -173,9 +163,6 @@ public function registerCustomFixers(iterable $fixers): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setCacheFile(string $cacheFile): ConfigInterface { $this->cacheFile = $cacheFile; @@ -183,9 +170,6 @@ public function setCacheFile(string $cacheFile): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setFinder(iterable $finder): ConfigInterface { $this->finder = $finder; @@ -193,9 +177,6 @@ public function setFinder(iterable $finder): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setFormat(string $format): ConfigInterface { $this->format = $format; @@ -203,9 +184,6 @@ public function setFormat(string $format): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setHideProgress(bool $hideProgress): ConfigInterface { $this->hideProgress = $hideProgress; @@ -213,9 +191,6 @@ public function setHideProgress(bool $hideProgress): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setIndent(string $indent): ConfigInterface { $this->indent = $indent; @@ -223,9 +198,6 @@ public function setIndent(string $indent): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setLineEnding(string $lineEnding): ConfigInterface { $this->lineEnding = $lineEnding; @@ -233,9 +205,13 @@ public function setLineEnding(string $lineEnding): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ + public function setParallelConfig(ParallelConfig $config): ConfigInterface + { + $this->parallelConfig = $config; + + return $this; + } + public function setPhpExecutable(?string $phpExecutable): ConfigInterface { $this->phpExecutable = $phpExecutable; @@ -243,9 +219,6 @@ public function setPhpExecutable(?string $phpExecutable): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setRiskyAllowed(bool $isRiskyAllowed): ConfigInterface { $this->isRiskyAllowed = $isRiskyAllowed; @@ -253,9 +226,6 @@ public function setRiskyAllowed(bool $isRiskyAllowed): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setRules(array $rules): ConfigInterface { $this->rules = $rules; @@ -263,9 +233,6 @@ public function setRules(array $rules): ConfigInterface return $this; } - /** - * {@inheritdoc} - */ public function setUsingCache(bool $usingCache): ConfigInterface { $this->usingCache = $usingCache; diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php index bc16c199bf..5308f860ab 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigInterface.php @@ -32,14 +32,14 @@ public function getCacheFile(): ?string; /** * Returns the custom fixers to use. * - * @return FixerInterface[] + * @return list */ public function getCustomFixers(): array; /** * Returns files to scan. * - * @return iterable|\Traversable + * @return iterable<\SplFileInfo> */ public function getFinder(): iterable; @@ -77,6 +77,8 @@ public function getRiskyAllowed(): bool; * Get rules. * * Keys of array are names of fixers/sets, values are true/false. + * + * @return array|bool> */ public function getRules(): array; @@ -90,7 +92,7 @@ public function getUsingCache(): bool; * * Name of custom fixer should follow `VendorName/rule_name` convention. * - * @param FixerInterface[]|iterable|\Traversable $fixers + * @param iterable $fixers */ public function registerCustomFixers(iterable $fixers): self; @@ -99,6 +101,9 @@ public function registerCustomFixers(iterable $fixers): self; */ public function setCacheFile(string $cacheFile): self; + /** + * @param iterable<\SplFileInfo> $finder + */ public function setFinder(iterable $finder): self; public function setFormat(string $format): self; @@ -126,6 +131,8 @@ public function setRiskyAllowed(bool $isRiskyAllowed): self; * Value for set must be bool (turn it on or off). * Value for fixer may be bool (turn it on or off) or array of configuration * (turn it on and contains configuration for FixerInterface::configure method). + * + * @param array|bool> $rules */ public function setRules(array $rules): self; diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php index ab1185f09b..87babf8f7b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidConfigurationException.php @@ -20,6 +20,7 @@ * Exceptions of this type are thrown on misconfiguration of the Fixer. * * @internal + * * @final Only internal extending this class is supported */ class InvalidConfigurationException extends \InvalidArgumentException diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php index 1419a248e2..8607bbfa44 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidFixerConfigurationException.php @@ -20,6 +20,7 @@ * Exception thrown by Fixers on misconfiguration. * * @internal + * * @final Only internal extending this class is supported */ class InvalidFixerConfigurationException extends InvalidConfigurationException @@ -29,7 +30,7 @@ class InvalidFixerConfigurationException extends InvalidConfigurationException public function __construct(string $fixerName, string $message, ?\Throwable $previous = null) { parent::__construct( - sprintf('[%s] %s', $fixerName, $message), + \sprintf('[%s] %s', $fixerName, $message), FixCommandExitStatusCalculator::EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG, $previous ); diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php index 6e4dcd4bed..440b247040 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/InvalidForEnvFixerConfigurationException.php @@ -19,6 +19,4 @@ * * @internal */ -final class InvalidForEnvFixerConfigurationException extends InvalidFixerConfigurationException -{ -} +final class InvalidForEnvFixerConfigurationException extends InvalidFixerConfigurationException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php index d229cda383..bcc07bdf49 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ConfigurationException/RequiredFixerConfigurationException.php @@ -19,6 +19,4 @@ * * @internal */ -final class RequiredFixerConfigurationException extends InvalidFixerConfigurationException -{ -} +final class RequiredFixerConfigurationException extends InvalidFixerConfigurationException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php index 0799b7498d..36ec4fc372 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Application.php @@ -14,18 +14,22 @@ namespace PhpCsFixer\Console; +use PhpCsFixer\Console\Command\CheckCommand; use PhpCsFixer\Console\Command\DescribeCommand; use PhpCsFixer\Console\Command\FixCommand; use PhpCsFixer\Console\Command\HelpCommand; use PhpCsFixer\Console\Command\ListFilesCommand; use PhpCsFixer\Console\Command\ListSetsCommand; use PhpCsFixer\Console\Command\SelfUpdateCommand; +use PhpCsFixer\Console\Command\WorkerCommand; use PhpCsFixer\Console\SelfUpdate\GithubClient; use PhpCsFixer\Console\SelfUpdate\NewVersionChecker; use PhpCsFixer\PharChecker; +use PhpCsFixer\Runner\Parallel\WorkerException; use PhpCsFixer\ToolInfo; use PhpCsFixer\Utils; use Symfony\Component\Console\Application as BaseApplication; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; @@ -39,19 +43,22 @@ */ final class Application extends BaseApplication { - public const VERSION = '3.8.0'; - public const VERSION_CODENAME = 'BerSzcz against war!'; + public const NAME = 'PHP CS Fixer'; + public const VERSION = '3.61.1'; + public const VERSION_CODENAME = '7th Gear'; private ToolInfo $toolInfo; + private ?Command $executedCommand = null; public function __construct() { - parent::__construct('PHP CS Fixer', self::VERSION); + parent::__construct(self::NAME, self::VERSION); $this->toolInfo = new ToolInfo(); // in alphabetical order $this->add(new DescribeCommand()); + $this->add(new CheckCommand($this->toolInfo)); $this->add(new FixCommand($this->toolInfo)); $this->add(new ListFilesCommand($this->toolInfo)); $this->add(new ListSetsCommand()); @@ -60,6 +67,7 @@ public function __construct() $this->toolInfo, new PharChecker() )); + $this->add(new WorkerCommand($this->toolInfo)); } public static function getMajorVersion(): int @@ -67,15 +75,11 @@ public static function getMajorVersion(): int return (int) explode('.', self::VERSION)[0]; } - /** - * {@inheritdoc} - */ public function doRun(InputInterface $input, OutputInterface $output): int { $stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() - : ($input->hasParameterOption('--format', true) && 'txt' !== $input->getParameterOption('--format', null, true) ? null : $output) - ; + : ($input->hasParameterOption('--format', true) && 'txt' !== $input->getParameterOption('--format', null, true) ? null : $output); if (null !== $stdErr) { $warningsDetector = new WarningsDetector($this->toolInfo); @@ -85,7 +89,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int if (\count($warnings) > 0) { foreach ($warnings as $warning) { - $stdErr->writeln(sprintf($stdErr->isDecorated() ? '%s' : '%s', $warning)); + $stdErr->writeln(\sprintf($stdErr->isDecorated() ? '%s' : '%s', $warning)); } $stdErr->writeln(''); } @@ -103,7 +107,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int $stdErr->writeln(''); $stdErr->writeln($stdErr->isDecorated() ? 'Detected deprecations in use:' : 'Detected deprecations in use:'); foreach ($triggeredDeprecations as $deprecation) { - $stdErr->writeln(sprintf('- %s', $deprecation)); + $stdErr->writeln(\sprintf('- %s', $deprecation)); } } } @@ -112,10 +116,12 @@ public function doRun(InputInterface $input, OutputInterface $output): int } /** - * {@inheritdoc} + * @internal */ - public function getLongVersion(): string + public static function getAbout(bool $decorated = false): string { + $longVersion = \sprintf('%s %s', self::NAME, self::VERSION); + $commit = '@git-commit@'; $versionCommit = ''; @@ -123,20 +129,81 @@ public function getLongVersion(): string $versionCommit = substr($commit, 0, 7); } - return implode('', [ - parent::getLongVersion(), - $versionCommit ? sprintf(' (%s)', $versionCommit) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.` - self::VERSION_CODENAME ? sprintf(' %s', self::VERSION_CODENAME) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.` - ' by Fabien Potencier and Dariusz Ruminski.', - "\nPHP runtime: ".PHP_VERSION.'', + $about = implode('', [ + $longVersion, + $versionCommit ? \sprintf(' (%s)', $versionCommit) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.` + self::VERSION_CODENAME ? \sprintf(' %s', self::VERSION_CODENAME) : '', // @phpstan-ignore-line to avoid `Ternary operator condition is always true|false.` + ' by Fabien Potencier, Dariusz Ruminski and contributors.', ]); + + if (false === $decorated) { + return strip_tags($about); + } + + return $about; } /** - * {@inheritdoc} + * @internal */ + public static function getAboutWithRuntime(bool $decorated = false): string + { + $about = self::getAbout(true)."\nPHP runtime: ".PHP_VERSION.''; + if (false === $decorated) { + return strip_tags($about); + } + + return $about; + } + + public function getLongVersion(): string + { + return self::getAboutWithRuntime(true); + } + protected function getDefaultCommands(): array { return [new HelpCommand(), new ListCommand()]; } + + /** + * @throws \Throwable + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int + { + $this->executedCommand = $command; + + return parent::doRunCommand($command, $input, $output); + } + + protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void + { + // Since parallel analysis utilises child processes, and they have their own output, + // we need to capture the output of the child process to determine it there was an exception. + // Default render format is not machine-friendly, so we need to override it for `worker` command, + // in order to be able to easily parse exception data for further displaying on main process' side. + if ($this->executedCommand instanceof WorkerCommand) { + $output->writeln(WorkerCommand::ERROR_PREFIX.json_encode( + [ + 'class' => \get_class($e), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'code' => $e->getCode(), + 'trace' => $e->getTraceAsString(), + ] + )); + + return; + } + + parent::doRenderThrowable($e, $output); + + if ($output->isVeryVerbose() && $e instanceof WorkerException) { + $output->writeln('Original trace from worker:'); + $output->writeln(''); + $output->writeln($e->getOriginalTraceAsString()); + $output->writeln(''); + } + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/CheckCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/CheckCommand.php new file mode 100644 index 0000000000..1e7d7537f1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/CheckCommand.php @@ -0,0 +1,66 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; + +/** + * @author Greg Korba + * + * @internal + */ +#[AsCommand(name: 'check', description: 'Checks if configured files/directories comply with configured rules.')] +final class CheckCommand extends FixCommand +{ + /** @var string */ + protected static $defaultName = 'check'; + + /** @var string */ + protected static $defaultDescription = 'Checks if configured files/directories comply with configured rules.'; + + public function __construct(ToolInfoInterface $toolInfo) + { + parent::__construct($toolInfo); + } + + public function getHelp(): string + { + $help = explode('--dry-run', parent::getHelp()); + + return substr($help[0], 0, strrpos($help[0], "\n") - 1) + .substr($help[1], strpos($help[1], "\n")); + } + + protected function configure(): void + { + parent::configure(); + + $this->setDefinition([ + ...array_values($this->getDefinition()->getArguments()), + ...array_values(array_filter( + $this->getDefinition()->getOptions(), + static fn (InputOption $option): bool => 'dry-run' !== $option->getName() + )), + ]); + } + + protected function isDryRun(InputInterface $input): bool + { + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php index 07de38644c..4153c88661 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DescribeCommand.php @@ -14,11 +14,17 @@ namespace PhpCsFixer\Console\Command; +use PhpCsFixer\Config; +use PhpCsFixer\Console\Application; +use PhpCsFixer\Console\ConfigurationResolver; use PhpCsFixer\Differ\DiffConsoleFormatter; use PhpCsFixer\Differ\FullDiffer; +use PhpCsFixer\Documentation\FixerDocumentGenerator; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; use PhpCsFixer\Fixer\FixerInterface; +use PhpCsFixer\Fixer\InternalFixerInterface; use PhpCsFixer\FixerConfiguration\AliasedFixerOption; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption; @@ -30,12 +36,15 @@ use PhpCsFixer\RuleSet\RuleSets; use PhpCsFixer\StdinFileInfo; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\ToolInfo; use PhpCsFixer\Utils; use PhpCsFixer\WordMatcher; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -44,15 +53,14 @@ * * @internal */ +#[AsCommand(name: 'describe')] final class DescribeCommand extends Command { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'describe'; /** - * @var string[] + * @var ?list */ private $setNames; @@ -75,31 +83,35 @@ public function __construct(?FixerFactory $fixerFactory = null) $this->fixerFactory = $fixerFactory; } - /** - * {@inheritdoc} - */ protected function configure(): void { $this ->setDefinition( [ new InputArgument('name', InputArgument::REQUIRED, 'Name of rule / set.'), + new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'), ] ) ->setDescription('Describe rule / ruleset.') ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { - if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity() && $output instanceof ConsoleOutputInterface) { + if ($output instanceof ConsoleOutputInterface) { $stdErr = $output->getErrorOutput(); - $stdErr->writeln($this->getApplication()->getLongVersion()); + $stdErr->writeln(Application::getAboutWithRuntime(true)); } + $resolver = new ConfigurationResolver( + new Config(), + ['config' => $input->getOption('config')], + getcwd(), + new ToolInfo() + ); + + $this->fixerFactory->registerCustomFixers($resolver->getConfig()->getCustomFixers()); + $name = $input->getArgument('name'); try { @@ -119,7 +131,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->describeList($output, $e->getType()); - throw new \InvalidArgumentException(sprintf( + throw new \InvalidArgumentException(\sprintf( '%s "%s" not found.%s', ucfirst($e->getType()), $name, @@ -143,24 +155,28 @@ private function describeRule(OutputInterface $output, string $name): void $definition = $fixer->getDefinition(); - $summary = $definition->getSummary(); + $output->writeln(\sprintf('Description of the `%s` rule.', $name)); + $output->writeln(''); + + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $output->writeln(\sprintf('Fixer class: %s.', \get_class($fixer))); + $output->writeln(''); + } if ($fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $message = [] === $successors - ? 'will be removed on next major version' - : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); - $message = Preg::replace('/(`.+?`)/', '$1', $message); - $summary .= sprintf(' DEPRECATED: %s.', $message); - } + ? \sprintf('it will be removed in version %d.0', Application::getMajorVersion() + 1) + : \sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors)); - $output->writeln(sprintf('Description of %s rule.', $name)); - - if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { - $output->writeln(sprintf('Fixer class: %s.', \get_class($fixer))); + $endMessage = '. '.ucfirst($message); + Utils::triggerDeprecation(new \RuntimeException(str_replace('`', '"', "Rule \"{$name}\" is deprecated{$endMessage}."))); + $message = Preg::replace('/(`[^`]+`)/', '$1', $message); + $output->writeln(\sprintf('DEPRECATED: %s.', $message)); + $output->writeln(''); } - $output->writeln($summary); + $output->writeln($definition->getSummary()); $description = $definition->getDescription(); @@ -170,8 +186,22 @@ private function describeRule(OutputInterface $output, string $name): void $output->writeln(''); + if ($fixer instanceof ExperimentalFixerInterface) { + $output->writeln('Fixer applying this rule is EXPERIMENTAL..'); + $output->writeln('It is not covered with backward compatibility promise and may produce unstable or unexpected results.'); + + $output->writeln(''); + } + + if ($fixer instanceof InternalFixerInterface) { + $output->writeln('Fixer applying this rule is INTERNAL..'); + $output->writeln('It is expected to be used only on PHP CS Fixer project itself.'); + + $output->writeln(''); + } + if ($fixer->isRisky()) { - $output->writeln('Fixer applying this rule is risky.'); + $output->writeln('Fixer applying this rule is RISKY.'); $riskyDescription = $definition->getRiskyDescription(); @@ -186,7 +216,7 @@ private function describeRule(OutputInterface $output, string $name): void $configurationDefinition = $fixer->getConfigurationDefinition(); $options = $configurationDefinition->getOptions(); - $output->writeln(sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's')); + $output->writeln(\sprintf('Fixer is configurable using following option%s:', 1 === \count($options) ? '' : 's')); foreach ($options as $option) { $line = '* '.OutputFormatter::escape($option->getName()).''; @@ -194,30 +224,24 @@ private function describeRule(OutputInterface $output, string $name): void if (null === $allowed) { $allowed = array_map( - static function (string $type): string { - return ''.$type.''; - }, - $option->getAllowedTypes() + static fn (string $type): string => ''.$type.'', + $option->getAllowedTypes(), ); } else { - foreach ($allowed as &$value) { - if ($value instanceof AllowedValueSubset) { - $value = 'a subset of '.HelpCommand::toString($value->getAllowedValues()).''; - } else { - $value = ''.HelpCommand::toString($value).''; - } - } + $allowed = array_map(static fn ($value): string => $value instanceof AllowedValueSubset + ? 'a subset of '.Utils::toString($value->getAllowedValues()).'' + : ''.Utils::toString($value).'', $allowed); } - $line .= ' ('.implode(', ', $allowed).')'; + $line .= ' ('.Utils::naturalLanguageJoin($allowed, '').')'; $description = Preg::replace('/(`.+?`)/', '$1', OutputFormatter::escape($option->getDescription())); $line .= ': '.lcfirst(Preg::replace('/\.$/', '', $description)).'; '; if ($option->hasDefault()) { - $line .= sprintf( + $line .= \sprintf( 'defaults to %s', - HelpCommand::toString($option->getDefault()) + Utils::toString($option->getDefault()) ); } else { $line .= 'required'; @@ -241,7 +265,7 @@ static function (string $type): string { $output->writeln(''); } - /** @var CodeSampleInterface[] $codeSamples */ + /** @var list $codeSamples */ $codeSamples = array_filter($definition->getCodeSamples(), static function (CodeSampleInterface $codeSample): bool { if ($codeSample instanceof VersionSpecificCodeSampleInterface) { return $codeSample->isSuitableFor(\PHP_VERSION_ID); @@ -250,9 +274,14 @@ static function (string $type): string { return true; }); - if (0 === \count($codeSamples)) { + if (0 === \count($definition->getCodeSamples())) { + $output->writeln([ + 'Fixing examples are not available for this rule.', + '', + ]); + } elseif (0 === \count($codeSamples)) { $output->writeln([ - 'Fixing examples cannot be demonstrated on the current PHP version.', + 'Fixing examples cannot be demonstrated on the current PHP version.', '', ]); } else { @@ -261,7 +290,7 @@ static function (string $type): string { $differ = new FullDiffer(); $diffFormatter = new DiffConsoleFormatter( $output->isDecorated(), - sprintf( + \sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL @@ -288,17 +317,35 @@ static function (string $type): string { if ($fixer instanceof ConfigurableFixerInterface) { if (null === $configuration) { - $output->writeln(sprintf(' * Example #%d. Fixing with the default configuration.', $index + 1)); + $output->writeln(\sprintf(' * Example #%d. Fixing with the default configuration.', $index + 1)); } else { - $output->writeln(sprintf(' * Example #%d. Fixing with configuration: %s.', $index + 1, HelpCommand::toString($codeSample->getConfiguration()))); + $output->writeln(\sprintf(' * Example #%d. Fixing with configuration: %s.', $index + 1, Utils::toString($codeSample->getConfiguration()))); } } else { - $output->writeln(sprintf(' * Example #%d.', $index + 1)); + $output->writeln(\sprintf(' * Example #%d.', $index + 1)); } $output->writeln([$diffFormatter->format($diff, ' %s'), '']); } } + + $ruleSetConfigs = FixerDocumentGenerator::getSetsOfRule($name); + + if ([] !== $ruleSetConfigs) { + ksort($ruleSetConfigs); + $plural = 1 !== \count($ruleSetConfigs) ? 's' : ''; + $output->writeln("Fixer is part of the following rule set{$plural}:"); + + foreach ($ruleSetConfigs as $set => $config) { + if (null !== $config) { + $output->writeln(\sprintf('* %s with config: %s', $set, Utils::toString($config))); + } else { + $output->writeln(\sprintf('* %s with default config', $set)); + } + } + + $output->writeln(''); + } } private function describeSet(OutputInterface $output, string $name): void @@ -310,21 +357,23 @@ private function describeSet(OutputInterface $output, string $name): void $ruleSetDefinitions = RuleSets::getSetDefinitions(); $fixers = $this->getFixers(); - $output->writeln(sprintf('Description of the %s set.', $ruleSetDefinitions[$name]->getName())); + $output->writeln(\sprintf('Description of the `%s` set.', $ruleSetDefinitions[$name]->getName())); + $output->writeln(''); + $output->writeln($this->replaceRstLinks($ruleSetDefinitions[$name]->getDescription())); + $output->writeln(''); if ($ruleSetDefinitions[$name]->isRisky()) { - $output->writeln('This set contains risky rules.'); + $output->writeln('This set contains risky rules.'); + $output->writeln(''); } - $output->writeln(''); - $help = ''; foreach ($ruleSetDefinitions[$name]->getRules() as $rule => $config) { if (str_starts_with($rule, '@')) { $set = $ruleSetDefinitions[$rule]; - $help .= sprintf( + $help .= \sprintf( " * %s%s\n | %s\n\n", $rule, $set->isRisky() ? ' risky' : '', @@ -338,12 +387,12 @@ private function describeSet(OutputInterface $output, string $name): void $fixer = $fixers[$rule]; $definition = $fixer->getDefinition(); - $help .= sprintf( + $help .= \sprintf( " * %s%s\n | %s\n%s\n", $rule, $fixer->isRisky() ? ' risky' : '', $definition->getSummary(), - true !== $config ? sprintf(" | Configuration: %s\n", HelpCommand::toString($config)) : '' + true !== $config ? \sprintf(" | Configuration: %s\n", Utils::toString($config)) : '' ); } @@ -372,7 +421,7 @@ private function getFixers(): array } /** - * @return string[] + * @return list */ private function getSetNames(): array { @@ -390,23 +439,25 @@ private function getSetNames(): array */ private function describeList(OutputInterface $output, string $type): void { - if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) { - $describe = [ - 'sets' => $this->getSetNames(), - 'rules' => $this->getFixers(), - ]; - } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { - $describe = 'set' === $type ? ['sets' => $this->getSetNames()] : ['rules' => $this->getFixers()]; - } else { + if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { return; } - /** @var string[] $items */ - foreach ($describe as $list => $items) { - $output->writeln(sprintf('Defined %s:', $list)); + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE || 'set' === $type) { + $output->writeln('Defined sets:'); + + $items = $this->getSetNames(); + foreach ($items as $item) { + $output->writeln(\sprintf('* %s', $item)); + } + } + + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE || 'rule' === $type) { + $output->writeln('Defined rules:'); - foreach ($items as $name => $item) { - $output->writeln(sprintf('* %s', \is_string($name) ? $name : $item)); + $items = array_keys($this->getFixers()); + foreach ($items as $item) { + $output->writeln(\sprintf('* %s', $item)); } } } @@ -415,15 +466,11 @@ private function replaceRstLinks(string $content): string { return Preg::replaceCallback( '/(`[^<]+<[^>]+>`_)/', - static function (array $matches) { - return Preg::replaceCallback( - '/`(.*)<(.*)>`_/', - static function (array $matches): string { - return $matches[1].'('.$matches[2].')'; - }, - $matches[1] - ); - }, + static fn (array $matches) => Preg::replaceCallback( + '/`(.*)<(.*)>`_/', + static fn (array $matches): string => $matches[1].'('.$matches[2].')', + $matches[1] + ), $content ); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php index 22102c0d60..e1adc98725 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/DocumentationCommand.php @@ -16,10 +16,10 @@ use PhpCsFixer\Documentation\DocumentationLocator; use PhpCsFixer\Documentation\FixerDocumentGenerator; -use PhpCsFixer\Documentation\ListDocumentGenerator; use PhpCsFixer\Documentation\RuleSetDocumentationGenerator; use PhpCsFixer\FixerFactory; use PhpCsFixer\RuleSet\RuleSets; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -30,13 +30,20 @@ /** * @internal */ +#[AsCommand(name: 'documentation')] final class DocumentationCommand extends Command { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'documentation'; + private Filesystem $filesystem; + + public function __construct(Filesystem $filesystem) + { + parent::__construct(); + $this->filesystem = $filesystem; + } + protected function configure(): void { $this @@ -47,7 +54,6 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $filesystem = new Filesystem(); $locator = new DocumentationLocator(); $fixerFactory = new FixerFactory(); @@ -58,7 +64,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $fixerDocumentGenerator = new FixerDocumentGenerator($locator); $ruleSetDocumentationGenerator = new RuleSetDocumentationGenerator($locator); - $listDocumentGenerator = new ListDocumentGenerator($locator); // Array of existing fixer docs. // We first override existing files, and then we will delete files that are no longer needed. @@ -68,7 +73,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($fixers as $fixer) { $docForFixerRelativePaths[] = $locator->getFixerDocumentationFileRelativePath($fixer); - $filesystem->dumpFile( + $this->filesystem->dumpFile( $locator->getFixerDocumentationFilePath($fixer), $fixerDocumentGenerator->generateFixerDocumentation($fixer) ); @@ -80,12 +85,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->in($locator->getFixersDocumentationDirectoryPath()) ->notPath($docForFixerRelativePaths) as $file ) { - $filesystem->remove($file->getPathname()); + $this->filesystem->remove($file->getPathname()); } // Fixer doc. index - $filesystem->dumpFile( + $this->filesystem->dumpFile( $locator->getFixersDocumentationIndexFilePath(), $fixerDocumentGenerator->generateFixersDocumentationIndex($fixers) ); @@ -94,31 +99,24 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var SplFileInfo $file */ foreach ((new Finder())->files()->in($locator->getRuleSetsDocumentationDirectoryPath()) as $file) { - $filesystem->remove($file->getPathname()); + $this->filesystem->remove($file->getPathname()); } $paths = []; foreach ($setDefinitions as $name => $definition) { $path = $locator->getRuleSetsDocumentationFilePath($name); - $paths[$name] = $path; - $filesystem->dumpFile($path, $ruleSetDocumentationGenerator->generateRuleSetsDocumentation($definition, $fixers)); + $paths[$path] = $definition; + $this->filesystem->dumpFile($path, $ruleSetDocumentationGenerator->generateRuleSetsDocumentation($definition, $fixers)); } // RuleSet doc. index - $filesystem->dumpFile( + $this->filesystem->dumpFile( $locator->getRuleSetsDocumentationIndexFilePath(), $ruleSetDocumentationGenerator->generateRuleSetsDocumentationIndex($paths) ); - // List file / Appendix - - $filesystem->dumpFile( - $locator->getListingFilePath(), - $listDocumentGenerator->generateListingDocumentation($fixers) - ); - $output->writeln('Docs updated.'); return 0; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php index c3619b4e8f..45c4163cb3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommand.php @@ -17,15 +17,20 @@ use PhpCsFixer\Config; use PhpCsFixer\ConfigInterface; use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\Console\Application; use PhpCsFixer\Console\ConfigurationResolver; use PhpCsFixer\Console\Output\ErrorOutput; -use PhpCsFixer\Console\Output\NullOutput; -use PhpCsFixer\Console\Output\ProcessOutput; +use PhpCsFixer\Console\Output\OutputContext; +use PhpCsFixer\Console\Output\Progress\ProgressOutputFactory; +use PhpCsFixer\Console\Output\Progress\ProgressOutputType; use PhpCsFixer\Console\Report\FixReport\ReportSummary; use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\FixerFileProcessedEvent; use PhpCsFixer\Runner\Runner; use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -40,15 +45,19 @@ * @author Fabien Potencier * @author Dariusz Rumiński * + * @final + * * @internal */ -final class FixCommand extends Command +#[AsCommand(name: 'fix', description: 'Fixes a directory or a file.')] +/* final */ class FixCommand extends Command { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'fix'; + /** @var string */ + protected static $defaultDescription = 'Fixes a directory or a file.'; + private EventDispatcherInterface $eventDispatcher; private ErrorsManager $errorsManager; @@ -59,6 +68,8 @@ final class FixCommand extends Command private ToolInfoInterface $toolInfo; + private ProgressOutputFactory $progressOutputFactory; + public function __construct(ToolInfoInterface $toolInfo) { parent::__construct(); @@ -68,6 +79,7 @@ public function __construct(ToolInfoInterface $toolInfo) $this->stopwatch = new Stopwatch(); $this->defaultConfig = new Config(); $this->toolInfo = $toolInfo; + $this->progressOutputFactory = new ProgressOutputFactory(); } /** @@ -78,139 +90,136 @@ public function __construct(ToolInfoInterface $toolInfo) public function getHelp(): string { return <<<'EOF' -The %command.name% command tries to fix as much coding standards -problems as possible on a given file or files in a given directory and its subdirectories: + The %command.name% command tries to %command.name% as much coding standards + problems as possible on a given file or files in a given directory and its subdirectories: + + $ php %command.full_name% /path/to/dir + $ php %command.full_name% /path/to/file - $ php %command.full_name% /path/to/dir - $ php %command.full_name% /path/to/file + By default --path-mode is set to `override`, which means, that if you specify the path to a file or a directory via + command arguments, then the paths provided to a `Finder` in config file will be ignored. You can use --path-mode=intersection + to merge paths from the config file and from the argument: -By default --path-mode is set to `override`, which means, that if you specify the path to a file or a directory via -command arguments, then the paths provided to a `Finder` in config file will be ignored. You can use --path-mode=intersection -to merge paths from the config file and from the argument: + $ php %command.full_name% --path-mode=intersection /path/to/dir - $ php %command.full_name% --path-mode=intersection /path/to/dir + The --format option for the output format. Supported formats are `txt` (default one), `json`, `xml`, `checkstyle`, `junit` and `gitlab`. -The --format option for the output format. Supported formats are `txt` (default one), `json`, `xml`, `checkstyle`, `junit` and `gitlab`. + NOTE: the output for the following formats are generated in accordance with schemas -NOTE: the output for the following formats are generated in accordance with schemas + * `checkstyle` follows the common `"checkstyle" XML schema `_ + * `gitlab` follows the `codeclimate JSON schema `_ + * `json` follows the `own JSON schema `_ + * `junit` follows the `JUnit XML schema from Jenkins `_ + * `xml` follows the `own XML schema `_ -* `checkstyle` follows the common `"checkstyle" XML schema `_ -* `json` follows the `own JSON schema `_ -* `junit` follows the `JUnit XML schema from Jenkins `_ -* `xml` follows the `own XML schema `_ + The --quiet Do not output any message. -The --quiet Do not output any message. + The --verbose option will show the applied rules. When using the `txt` format it will also display progress notifications. -The --verbose option will show the applied rules. When using the `txt` format it will also display progress notifications. + NOTE: if there is an error like "errors reported during linting after fixing", you can use this to be even more verbose for debugging purpose -NOTE: if there is an error like "errors reported during linting after fixing", you can use this to be even more verbose for debugging purpose + * `-v`: verbose + * `-vv`: very verbose + * `-vvv`: debug -* `-v`: verbose -* `-vv`: very verbose -* `-vvv`: debug + The --rules option limits the rules to apply to the + project: -The --rules option limits the rules to apply to the -project: + EOF. /* @TODO: 4.0 - change to @PER */ <<<'EOF' - $ php %command.full_name% /path/to/project --rules=@PSR12 + $ php %command.full_name% /path/to/project --rules=@PSR12 -By default the PSR-12 rules are used. + By default the PSR-12 rules are used. -The --rules option lets you choose the exact rules to -apply (the rule names must be separated by a comma): + The --rules option lets you choose the exact rules to + apply (the rule names must be separated by a comma): - $ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type + $ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type -You can also exclude the rules you don't want by placing a dash in front of the rule name, if this is more convenient, -using -name_of_fixer: + You can also exclude the rules you don't want by placing a dash in front of the rule name, if this is more convenient, + using -name_of_fixer: - $ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type + $ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type -When using combinations of exact and exclude rules, applying exact rules along with above excluded results: + When using combinations of exact and exclude rules, applying exact rules along with above excluded results: - $ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison + $ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison -Complete configuration for rules can be supplied using a `json` formatted string. + Complete configuration for rules can be supplied using a `json` formatted string. - $ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}' + $ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}' -The --dry-run flag will run the fixer without making changes to your files. + The --dry-run flag will run the fixer without making changes to your files. -The --diff flag can be used to let the fixer output all the changes it makes. + The --sequential flag will enforce sequential analysis even if parallel config is provided. -The --allow-risky option (pass `yes` or `no`) allows you to set whether risky rules may run. Default value is taken from config file. -A rule is considered risky if it could change code behaviour. By default no risky rules are run. + The --diff flag can be used to let the fixer output all the changes it makes. -The --stop-on-violation flag stops the execution upon first file that needs to be fixed. + The --allow-risky option (pass `yes` or `no`) allows you to set whether risky rules may run. Default value is taken from config file. + A rule is considered risky if it could change code behaviour. By default no risky rules are run. -The --show-progress option allows you to choose the way process progress is rendered: + The --stop-on-violation flag stops the execution upon first file that needs to be fixed. -* none: disables progress output; -* dots: multiline progress output with number of files and percentage on each line. + The --show-progress option allows you to choose the way process progress is rendered: -If the option is not provided, it defaults to dots unless a config file that disables output is used, in which case it defaults to none. This option has no effect if the verbosity of the command is less than verbose. + * none: disables progress output; + * dots: multiline progress output with number of files and percentage on each line. + * bar: single line progress output with number of files and calculated percentage. - $ php %command.full_name% --verbose --show-progress=dots + If the option is not provided, it defaults to bar unless a config file that disables output is used, in which case it defaults to none. This option has no effect if the verbosity of the command is less than verbose. -By using --using-cache option with `yes` or `no` you can set if the caching -mechanism should be used. + $ php %command.full_name% --verbose --show-progress=dots -The command can also read from standard input, in which case it won't -automatically fix anything: + By using --using-cache option with `yes` or `no` you can set if the caching + mechanism should be used. - $ cat foo.php | php %command.full_name% --diff - + The command can also read from standard input, in which case it won't + automatically fix anything: -Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that -would be default in next MAJOR release and to forbid using deprecated configuration: + $ cat foo.php | php %command.full_name% --diff - - $ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff + Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that + would be default in next MAJOR release and to forbid using deprecated configuration: -Exit code ---------- + $ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff -Exit code of the fix command is built using following bit flags: + Exit code + --------- -* 0 - OK. -* 1 - General error (or PHP minimal requirement not matched). -* 4 - Some files have invalid syntax (only in dry-run mode). -* 8 - Some files need fixing (only in dry-run mode). -* 16 - Configuration error of the application. -* 32 - Configuration error of a Fixer. -* 64 - Exception raised within the application. + Exit code of the `%command.name%` command is built using following bit flags: -EOF - ; + * 0 - OK. + * 1 - General error (or PHP minimal requirement not matched). + * 4 - Some files have invalid syntax (only in dry-run mode). + * 8 - Some files need fixing (only in dry-run mode). + * 16 - Configuration error of the application. + * 32 - Configuration error of a Fixer. + * 64 - Exception raised within the application. + + EOF; } - /** - * {@inheritdoc} - */ protected function configure(): void { - $this - ->setDefinition( - [ - new InputArgument('path', InputArgument::IS_ARRAY, 'The path.'), - new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be override or intersection).', ConfigurationResolver::PATH_MODE_OVERRIDE), - new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be yes or no).'), - new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a .php-cs-fixer.php file.'), - new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), - new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'The rules.'), - new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Does cache should be used (can be yes or no).'), - new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'), - new InputOption('diff', '', InputOption::VALUE_NONE, 'Also produce diff for each file.'), - new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'), - new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'), - new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, dots).'), - ] - ) - ->setDescription('Fixes a directory or a file.') - ; + $this->setDefinition( + [ + new InputArgument('path', InputArgument::IS_ARRAY, 'The path(s) that rules will be run against (each path can be a file or directory).'), + new InputOption('path-mode', '', InputOption::VALUE_REQUIRED, 'Specify path mode (can be `override` or `intersection`).', ConfigurationResolver::PATH_MODE_OVERRIDE), + new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be `yes` or `no`).'), + new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a config file.'), + new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), + new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'List of rules that should be run against configured paths.'), + new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Should cache be used (can be `yes` or `no`).'), + new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'), + new InputOption('diff', '', InputOption::VALUE_NONE, 'Prints diff for each file.'), + new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'), + new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'), + new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, dots).'), + new InputOption('sequential', '', InputOption::VALUE_NONE, 'Enforce sequential analysis.'), + ] + ); } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $verbosity = $output->getVerbosity(); @@ -227,7 +236,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int [ 'allow-risky' => $input->getOption('allow-risky'), 'config' => $passedConfig, - 'dry-run' => $input->getOption('dry-run'), + 'dry-run' => $this->isDryRun($input), 'rules' => $passedRules, 'path' => $input->getArgument('path'), 'path-mode' => $input->getOption('path-mode'), @@ -238,6 +247,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'stop-on-violation' => $input->getOption('stop-on-violation'), 'verbosity' => $verbosity, 'show-progress' => $input->getOption('show-progress'), + 'sequential' => $input->getOption('sequential'), ], getcwd(), $this->toolInfo @@ -247,63 +257,87 @@ protected function execute(InputInterface $input, OutputInterface $output): int $stdErr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() - : ('txt' === $reporter->getFormat() ? $output : null) - ; + : ('txt' === $reporter->getFormat() ? $output : null); if (null !== $stdErr) { - if (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) { - $stdErr->writeln($this->getApplication()->getLongVersion()); - } + $stdErr->writeln(Application::getAboutWithRuntime(true)); + $isParallel = $resolver->getParallelConfig()->getMaxProcesses() > 1; + + $stdErr->writeln(\sprintf( + 'Running analysis on %d core%s.', + $resolver->getParallelConfig()->getMaxProcesses(), + $isParallel ? \sprintf( + 's with %d file%s per process', + $resolver->getParallelConfig()->getFilesPerProcess(), + $resolver->getParallelConfig()->getFilesPerProcess() > 1 ? 's' : '' + ) : ' sequentially' + )); + + /** @TODO v4 remove warnings related to parallel runner */ + $usageDocs = 'https://cs.symfony.com/doc/usage.html'; + $stdErr->writeln(\sprintf( + $stdErr->isDecorated() ? '%s' : '%s', + $isParallel + ? 'Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!' + : \sprintf( + 'You can enable parallel runner and speed up the analysis! Please see %s for more information.', + $stdErr->isDecorated() + ? \sprintf('usage docs', OutputFormatter::escape($usageDocs)) + : $usageDocs + ) + )); $configFile = $resolver->getConfigFile(); - $stdErr->writeln(sprintf('Loaded config %s%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"')); + $stdErr->writeln(\sprintf('Loaded config %s%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"')); if ($resolver->getUsingCache()) { $cacheFile = $resolver->getCacheFile(); if (is_file($cacheFile)) { - $stdErr->writeln(sprintf('Using cache file "%s".', $cacheFile)); + $stdErr->writeln(\sprintf('Using cache file "%s".', $cacheFile)); } } } - $progressType = $resolver->getProgress(); - $finder = $resolver->getFinder(); + $finder = new \ArrayIterator(iterator_to_array($resolver->getFinder())); if (null !== $stdErr && $resolver->configFinderIsOverridden()) { $stdErr->writeln( - sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Paths from configuration file have been overridden by paths provided as command arguments.') + \sprintf($stdErr->isDecorated() ? '%s' : '%s', 'Paths from configuration file have been overridden by paths provided as command arguments.') ); } - if ('none' === $progressType || null === $stdErr) { - $progressOutput = new NullOutput(); - } else { - $finder = new \ArrayIterator(iterator_to_array($finder)); - $progressOutput = new ProcessOutput( + $progressType = $resolver->getProgressType(); + $progressOutput = $this->progressOutputFactory->create( + $progressType, + new OutputContext( $stdErr, - $this->eventDispatcher, (new Terminal())->getWidth(), \count($finder) - ); - } + ) + ); $runner = new Runner( $finder, $resolver->getFixers(), $resolver->getDiffer(), - 'none' !== $progressType ? $this->eventDispatcher : null, + ProgressOutputType::NONE !== $progressType ? $this->eventDispatcher : null, $this->errorsManager, $resolver->getLinter(), $resolver->isDryRun(), $resolver->getCacheManager(), $resolver->getDirectory(), - $resolver->shouldStopOnViolation() + $resolver->shouldStopOnViolation(), + $resolver->getParallelConfig(), + $input, + $resolver->getConfigFile() ); + $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']); $this->stopwatch->start('fixFiles'); $changed = $runner->fix(); $this->stopwatch->stop('fixFiles'); + $this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']); $progressOutput->printLegend(); @@ -311,6 +345,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $reportSummary = new ReportSummary( $changed, + \count($finder), $fixEvent->getDuration(), $fixEvent->getMemory(), OutputInterface::VERBOSITY_VERBOSE <= $verbosity, @@ -320,8 +355,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->isDecorated() ? $output->write($reporter->generate($reportSummary)) - : $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW) - ; + : $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW); $invalidErrors = $this->errorsManager->getInvalidErrors(); $exceptionErrors = $this->errorsManager->getExceptionErrors(); @@ -353,4 +387,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int \count($lintErrors) > 0 ); } + + protected function isDryRun(InputInterface $input): bool + { + return $input->getOption('dry-run'); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php index 727dfff52b..28d6b258fb 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/FixCommandExitStatusCalculator.php @@ -28,8 +28,13 @@ final class FixCommandExitStatusCalculator public const EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG = 32; public const EXIT_STATUS_FLAG_EXCEPTION_IN_APP = 64; - public function calculate(bool $isDryRun, bool $hasChangedFiles, bool $hasInvalidErrors, bool $hasExceptionErrors, bool $hasLintErrorsAfterFixing): int - { + public function calculate( + bool $isDryRun, + bool $hasChangedFiles, + bool $hasInvalidErrors, + bool $hasExceptionErrors, + bool $hasLintErrorsAfterFixing + ): int { $exitStatus = 0; if ($isDryRun) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php index a5f22047c2..192c735deb 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/HelpCommand.php @@ -16,7 +16,8 @@ use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerOptionInterface; -use PhpCsFixer\Preg; +use PhpCsFixer\Utils; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\HelpCommand as BaseHelpCommand; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputInterface; @@ -28,35 +29,23 @@ * * @internal */ +#[AsCommand(name: 'help')] final class HelpCommand extends BaseHelpCommand { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'help'; - /** - * @param mixed $value - */ - public static function toString($value): string - { - return \is_array($value) - ? static::arrayToString($value) - : static::scalarToString($value) - ; - } - /** * Returns the allowed values of the given option that can be converted to a string. + * + * @return null|list */ public static function getDisplayableAllowedValues(FixerOptionInterface $option): ?array { $allowed = $option->getAllowedValues(); if (null !== $allowed) { - $allowed = array_filter($allowed, static function ($value): bool { - return !($value instanceof \Closure); - }); + $allowed = array_filter($allowed, static fn ($value): bool => !$value instanceof \Closure); usort($allowed, static function ($valueA, $valueB): int { if ($valueA instanceof AllowedValueSubset) { @@ -68,8 +57,8 @@ public static function getDisplayableAllowedValues(FixerOptionInterface $option) } return strcasecmp( - self::toString($valueA), - self::toString($valueB) + Utils::toString($valueA), + Utils::toString($valueB) ); }); @@ -81,44 +70,8 @@ public static function getDisplayableAllowedValues(FixerOptionInterface $option) return $allowed; } - /** - * {@inheritdoc} - */ protected function initialize(InputInterface $input, OutputInterface $output): void { $output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue')); } - - /** - * @param mixed $value - */ - private static function scalarToString($value): string - { - $str = var_export($value, true); - - return Preg::replace('/\bNULL\b/', 'null', $str); - } - - private static function arrayToString(array $value): string - { - if (0 === \count($value)) { - return '[]'; - } - - $isHash = !array_is_list($value); - $str = '['; - - foreach ($value as $k => $v) { - if ($isHash) { - $str .= static::scalarToString($k).' => '; - } - - $str .= \is_array($v) - ? static::arrayToString($v).', ' - : static::scalarToString($v).', ' - ; - } - - return substr($str, 0, -2).']'; - } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php index fe21ecea1c..dcfabd093b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListFilesCommand.php @@ -18,22 +18,22 @@ use PhpCsFixer\ConfigInterface; use PhpCsFixer\Console\ConfigurationResolver; use PhpCsFixer\ToolInfoInterface; -use SplFileInfo; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Path; /** * @author Markus Staab * * @internal */ +#[AsCommand(name: 'list-files')] final class ListFilesCommand extends Command { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'list-files'; private ConfigInterface $defaultConfig; @@ -48,9 +48,6 @@ public function __construct(ToolInfoInterface $toolInfo) $this->toolInfo = $toolInfo; } - /** - * {@inheritdoc} - */ protected function configure(): void { $this @@ -79,10 +76,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $finder = $resolver->getFinder(); - /** @var SplFileInfo $file */ + /** @var \SplFileInfo $file */ foreach ($finder as $file) { if ($file->isFile()) { - $relativePath = str_replace($cwd, '.', $file->getRealPath()); + $relativePath = './'.Path::makeRelative($file->getRealPath(), $cwd); // unify directory separators across operating system $relativePath = str_replace('/', \DIRECTORY_SEPARATOR, $relativePath); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php index ad2a2914c3..1f7e83fb72 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/ListSetsCommand.php @@ -20,6 +20,8 @@ use PhpCsFixer\Console\Report\ListSetsReport\ReportSummary; use PhpCsFixer\Console\Report\ListSetsReport\TextReporter; use PhpCsFixer\RuleSet\RuleSets; +use PhpCsFixer\Utils; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputInterface; @@ -31,16 +33,12 @@ * * @internal */ +#[AsCommand(name: 'list-sets')] final class ListSetsCommand extends Command { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'list-sets'; - /** - * {@inheritdoc} - */ protected function configure(): void { $this @@ -53,7 +51,7 @@ protected function configure(): void ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $reporter = $this->resolveReporterWithFactory( $input->getOption('format'), @@ -68,8 +66,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->isDecorated() ? $output->write(OutputFormatter::escape($report)) - : $output->write($report, false, OutputInterface::OUTPUT_RAW) - ; + : $output->write($report, false, OutputInterface::OUTPUT_RAW); return 0; } @@ -83,7 +80,7 @@ private function resolveReporterWithFactory(string $format, ReporterFactory $fac $formats = $factory->getFormats(); sort($formats); - throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats))); + throw new InvalidConfigurationException(\sprintf('The format "%s" is not defined, supported are %s.', $format, Utils::naturalLanguageJoin($formats))); } return $reporter; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php index 9a3efd6ed4..229e1c8dd9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/SelfUpdateCommand.php @@ -14,10 +14,12 @@ namespace PhpCsFixer\Console\Command; +use PhpCsFixer\Console\Application; use PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface; use PhpCsFixer\PharCheckerInterface; use PhpCsFixer\Preg; use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -32,11 +34,10 @@ * * @internal */ +#[AsCommand(name: 'self-update')] final class SelfUpdateCommand extends Command { - /** - * @var string - */ + /** @var string */ protected static $defaultName = 'self-update'; private NewVersionCheckerInterface $versionChecker; @@ -57,9 +58,6 @@ public function __construct( $this->pharChecker = $pharChecker; } - /** - * {@inheritdoc} - */ protected function configure(): void { $this @@ -72,25 +70,22 @@ protected function configure(): void ->setDescription('Update php-cs-fixer.phar to the latest stable version.') ->setHelp( <<<'EOT' -The %command.name% command replace your php-cs-fixer.phar by the -latest version released on: -https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases + The %command.name% command replace your php-cs-fixer.phar by the + latest version released on: + https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases -$ php php-cs-fixer.phar %command.name% + $ php php-cs-fixer.phar %command.name% -EOT + EOT ) ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { - if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity() && $output instanceof ConsoleOutputInterface) { + if ($output instanceof ConsoleOutputInterface) { $stdErr = $output->getErrorOutput(); - $stdErr->writeln($this->getApplication()->getLongVersion()); + $stdErr->writeln(Application::getAboutWithRuntime(true)); } if (!$this->toolInfo->isInstalledAsPhar()) { @@ -107,7 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $latestVersion = $this->versionChecker->getLatestVersion(); $latestVersionOfCurrentMajor = $this->versionChecker->getLatestVersionOfMajor($currentMajor); } catch (\Exception $exception) { - $output->writeln(sprintf( + $output->writeln(\sprintf( 'Unable to determine newest version: %s', $exception->getMessage() )); @@ -127,8 +122,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int 0 !== $this->versionChecker->compareVersions($latestVersionOfCurrentMajor, $latestVersion) && true !== $input->getOption('force') ) { - $output->writeln(sprintf('A new major version of PHP CS Fixer is available (%s)', $latestVersion)); - $output->writeln(sprintf('Before upgrading please read https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/%s/UPGRADE-v%s.md', $latestVersion, $currentMajor + 1)); + $output->writeln(\sprintf('A new major version of PHP CS Fixer is available (%s)', $latestVersion)); + $output->writeln(\sprintf('Before upgrading please read https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/%s/UPGRADE-v%s.md', $latestVersion, $currentMajor + 1)); $output->writeln('If you are ready to upgrade run this command with -f'); $output->writeln('Checking for new minor/patch version...'); @@ -141,10 +136,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $remoteTag = $latestVersionOfCurrentMajor; } - $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; + $localFilename = $_SERVER['argv'][0]; + $realPath = realpath($localFilename); + if (false !== $realPath) { + $localFilename = $realPath; + } if (!is_writable($localFilename)) { - $output->writeln(sprintf('No permission to update "%s" file.', $localFilename)); + $output->writeln(\sprintf('No permission to update "%s" file.', $localFilename)); return 1; } @@ -153,7 +152,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag); if (false === @copy($remoteFilename, $tempFilename)) { - $output->writeln(sprintf('Unable to download new version %s from the server.', $remoteTag)); + $output->writeln(\sprintf('Unable to download new version %s from the server.', $remoteTag)); return 1; } @@ -163,7 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $pharInvalidityReason = $this->pharChecker->checkFileValidity($tempFilename); if (null !== $pharInvalidityReason) { unlink($tempFilename); - $output->writeln(sprintf('The download of %s is corrupt (%s).', $remoteTag, $pharInvalidityReason)); + $output->writeln(\sprintf('The download of %s is corrupt (%s).', $remoteTag, $pharInvalidityReason)); $output->writeln('Please re-run the "self-update" command to try again.'); return 1; @@ -171,7 +170,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int rename($tempFilename, $localFilename); - $output->writeln(sprintf('PHP CS Fixer updated (%s -> %s)', $currentVersion, $remoteTag)); + $output->writeln(\sprintf('PHP CS Fixer updated (%s -> %s)', $currentVersion, $remoteTag)); return 0; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Command/WorkerCommand.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/WorkerCommand.php new file mode 100644 index 0000000000..1ed2dfb4e9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Command/WorkerCommand.php @@ -0,0 +1,245 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use Clue\React\NDJson\Decoder; +use Clue\React\NDJson\Encoder; +use PhpCsFixer\Cache\NullCacheManager; +use PhpCsFixer\Config; +use PhpCsFixer\Console\ConfigurationResolver; +use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\FixerFileProcessedEvent; +use PhpCsFixer\Runner\Parallel\ParallelAction; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; +use PhpCsFixer\Runner\Parallel\ParallelisationException; +use PhpCsFixer\Runner\Runner; +use PhpCsFixer\ToolInfoInterface; +use React\EventLoop\StreamSelectLoop; +use React\Socket\ConnectionInterface; +use React\Socket\TcpConnector; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Greg Korba + * + * @internal + */ +#[AsCommand(name: 'worker', description: 'Internal command for running fixers in parallel', hidden: true)] +final class WorkerCommand extends Command +{ + /** @var string Prefix used before JSON-encoded error printed in the worker's process */ + public const ERROR_PREFIX = 'WORKER_ERROR::'; + + /** @var string */ + protected static $defaultName = 'worker'; + + /** @var string */ + protected static $defaultDescription = 'Internal command for running fixers in parallel'; + + private ToolInfoInterface $toolInfo; + private ConfigurationResolver $configurationResolver; + private ErrorsManager $errorsManager; + private EventDispatcherInterface $eventDispatcher; + + /** @var list */ + private array $events; + + public function __construct(ToolInfoInterface $toolInfo) + { + parent::__construct(); + + $this->setHidden(true); + $this->toolInfo = $toolInfo; + $this->errorsManager = new ErrorsManager(); + $this->eventDispatcher = new EventDispatcher(); + } + + protected function configure(): void + { + $this->setDefinition( + [ + new InputOption('port', null, InputOption::VALUE_REQUIRED, 'Specifies parallelisation server\'s port.'), + new InputOption('identifier', null, InputOption::VALUE_REQUIRED, 'Specifies parallelisation process\' identifier.'), + new InputOption('allow-risky', '', InputOption::VALUE_REQUIRED, 'Are risky fixers allowed (can be `yes` or `no`).'), + new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a config file.'), + new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'), + new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'List of rules that should be run against configured paths.'), + new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Should cache be used (can be `yes` or `no`).'), + new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'), + new InputOption('diff', '', InputOption::VALUE_NONE, 'Prints diff for each file.'), + new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'), + ] + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $errorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $identifier = $input->getOption('identifier'); + $port = $input->getOption('port'); + + if (null === $identifier || !is_numeric($port)) { + throw new ParallelisationException('Missing parallelisation options'); + } + + try { + $runner = $this->createRunner($input); + } catch (\Throwable $e) { + throw new ParallelisationException('Unable to create runner: '.$e->getMessage(), 0, $e); + } + + $loop = new StreamSelectLoop(); + $tcpConnector = new TcpConnector($loop); + $tcpConnector + ->connect(\sprintf('127.0.0.1:%d', $port)) + ->then( + /** @codeCoverageIgnore */ + function (ConnectionInterface $connection) use ($loop, $runner, $identifier): void { + $jsonInvalidUtf8Ignore = \defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; + $out = new Encoder($connection, $jsonInvalidUtf8Ignore); + $in = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore); + + // [REACT] Initialise connection with the parallelisation operator + $out->write(['action' => ParallelAction::WORKER_HELLO, 'identifier' => $identifier]); + + $handleError = static function (\Throwable $error) use ($out): void { + $out->write([ + 'action' => ParallelAction::WORKER_ERROR_REPORT, + 'class' => \get_class($error), + 'message' => $error->getMessage(), + 'file' => $error->getFile(), + 'line' => $error->getLine(), + 'code' => $error->getCode(), + 'trace' => $error->getTraceAsString(), + ]); + }; + $out->on('error', $handleError); + $in->on('error', $handleError); + + // [REACT] Listen for messages from the parallelisation operator (analysis requests) + $in->on('data', function (array $json) use ($loop, $runner, $out): void { + $action = $json['action'] ?? null; + + // Parallelisation operator does not have more to do, let's close the connection + if (ParallelAction::RUNNER_THANK_YOU === $action) { + $loop->stop(); + + return; + } + + if (ParallelAction::RUNNER_REQUEST_ANALYSIS !== $action) { + // At this point we only expect analysis requests, if any other action happen, we need to fix the code. + throw new \LogicException(\sprintf('Unexpected action ParallelAction::%s.', $action)); + } + + /** @var iterable $files */ + $files = $json['files']; + + foreach ($files as $absolutePath) { + // Reset events because we want to collect only those coming from analysed files chunk + $this->events = []; + $runner->setFileIterator(new \ArrayIterator([new \SplFileInfo($absolutePath)])); + $analysisResult = $runner->fix(); + + if (1 !== \count($this->events)) { + throw new ParallelisationException('Runner did not report a fixing event or reported too many.'); + } + + if (1 < \count($analysisResult)) { + throw new ParallelisationException('Runner returned more analysis results than expected.'); + } + + $out->write([ + 'action' => ParallelAction::WORKER_RESULT, + 'file' => $absolutePath, + 'fileHash' => $this->events[0]->getFileHash(), + 'status' => $this->events[0]->getStatus(), + 'fixInfo' => array_pop($analysisResult), + 'errors' => $this->errorsManager->forPath($absolutePath), + ]); + } + + // Request another file chunk (if available, the parallelisation operator will request new "run" action) + $out->write(['action' => ParallelAction::WORKER_GET_FILE_CHUNK]); + }); + }, + static function (\Throwable $error) use ($errorOutput): void { + // @TODO Verify onRejected behaviour → https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777#discussion_r1590399285 + $errorOutput->writeln($error->getMessage()); + } + ) + ; + + $loop->run(); + + return Command::SUCCESS; + } + + private function createRunner(InputInterface $input): Runner + { + $passedConfig = $input->getOption('config'); + $passedRules = $input->getOption('rules'); + + if (null !== $passedConfig && null !== $passedRules) { + throw new \RuntimeException('Passing both `--config` and `--rules` options is not allowed'); + } + + // There's no one single source of truth when it comes to fixing single file, we need to collect statuses from events. + $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, function (FixerFileProcessedEvent $event): void { + $this->events[] = $event; + }); + + $this->configurationResolver = new ConfigurationResolver( + new Config(), + [ + 'allow-risky' => $input->getOption('allow-risky'), + 'config' => $passedConfig, + 'dry-run' => $input->getOption('dry-run'), + 'rules' => $passedRules, + 'path' => [], + 'path-mode' => ConfigurationResolver::PATH_MODE_OVERRIDE, // IMPORTANT! WorkerCommand is called with file that already passed filtering, so here we can rely on PATH_MODE_OVERRIDE. + 'using-cache' => $input->getOption('using-cache'), + 'cache-file' => $input->getOption('cache-file'), + 'diff' => $input->getOption('diff'), + 'stop-on-violation' => $input->getOption('stop-on-violation'), + ], + getcwd(), // @phpstan-ignore-line + $this->toolInfo + ); + + return new Runner( + null, // Paths are known when parallelisation server requests new chunk, not now + $this->configurationResolver->getFixers(), + $this->configurationResolver->getDiffer(), + $this->eventDispatcher, + $this->errorsManager, + $this->configurationResolver->getLinter(), + $this->configurationResolver->isDryRun(), + new NullCacheManager(), // IMPORTANT! We pass null cache, as cache is read&write in main process and we do not need to do it again. + $this->configurationResolver->getDirectory(), + $this->configurationResolver->shouldStopOnViolation(), + ParallelConfigFactory::sequential(), // IMPORTANT! Worker must run in sequential mode. + null, + $this->configurationResolver->getConfigFile() + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php b/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php index bad1a6a6f9..1b594bed4d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/ConfigurationResolver.php @@ -23,7 +23,7 @@ use PhpCsFixer\Cache\Signature; use PhpCsFixer\ConfigInterface; use PhpCsFixer\ConfigurationException\InvalidConfigurationException; -use PhpCsFixer\Console\Command\HelpCommand; +use PhpCsFixer\Console\Output\Progress\ProgressOutputType; use PhpCsFixer\Console\Report\FixReport\ReporterFactory; use PhpCsFixer\Console\Report\FixReport\ReporterInterface; use PhpCsFixer\Differ\DifferInterface; @@ -35,14 +35,16 @@ use PhpCsFixer\FixerFactory; use PhpCsFixer\Linter\Linter; use PhpCsFixer\Linter\LinterInterface; +use PhpCsFixer\ParallelAwareConfigInterface; use PhpCsFixer\RuleSet\RuleSet; use PhpCsFixer\RuleSet\RuleSetInterface; +use PhpCsFixer\Runner\Parallel\ParallelConfig; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; use PhpCsFixer\StdinFileInfo; use PhpCsFixer\ToolInfoInterface; use PhpCsFixer\Utils; use PhpCsFixer\WhitespacesFixerConfig; use PhpCsFixer\WordMatcher; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder as SymfonyFinder; @@ -75,10 +77,7 @@ final class ConfigurationResolver */ private $configFile; - /** - * @var string - */ - private $cwd; + private string $cwd; private ConfigInterface $defaultConfig; @@ -98,7 +97,7 @@ final class ConfigurationResolver private $isDryRun; /** - * @var null|FixerInterface[] + * @var null|list */ private $fixers; @@ -109,6 +108,9 @@ final class ConfigurationResolver private ToolInfoInterface $toolInfo; + /** + * @var array + */ private array $options = [ 'allow-risky' => null, 'cache-file' => null, @@ -119,6 +121,7 @@ final class ConfigurationResolver 'path' => [], 'path-mode' => self::PATH_MODE_OVERRIDE, 'rules' => null, + 'sequential' => null, 'show-progress' => null, 'stop-on-violation' => null, 'using-cache' => null, @@ -146,18 +149,21 @@ final class ConfigurationResolver private $directory; /** - * @var null|iterable + * @var null|iterable<\SplFileInfo> */ - private $finder; + private ?iterable $finder = null; - private $format; + private ?string $format = null; /** * @var null|Linter */ private $linter; - private $path; + /** + * @var null|list + */ + private ?array $path = null; /** * @var null|string @@ -179,6 +185,9 @@ final class ConfigurationResolver */ private $fixerFactory; + /** + * @param array $options + */ public function __construct( ConfigInterface $config, array $options, @@ -269,6 +278,15 @@ public function getConfig(): ConfigInterface return $this->config; } + public function getParallelConfig(): ParallelConfig + { + $config = $this->getConfig(); + + return true !== $this->options['sequential'] && $config instanceof ParallelAwareConfigInterface + ? $config->getParallelConfig() + : ParallelConfigFactory::sequential(); + } + public function getConfigFile(): ?string { if (null === $this->configFile) { @@ -281,11 +299,7 @@ public function getConfigFile(): ?string public function getDiffer(): DifferInterface { if (null === $this->differ) { - if ($this->options['diff']) { - $this->differ = new UnifiedDiffer(); - } else { - $this->differ = new NullDiffer(); - } + $this->differ = (true === $this->options['diff']) ? new UnifiedDiffer() : new NullDiffer(); } return $this->differ; @@ -303,16 +317,17 @@ public function getDirectory(): DirectoryInterface $absolutePath = $filesystem->isAbsolutePath($path) ? $path : $this->cwd.\DIRECTORY_SEPARATOR.$path; + $absolutePath = \dirname($absolutePath); } - $this->directory = new Directory(\dirname($absolutePath)); + $this->directory = new Directory($absolutePath); } return $this->directory; } /** - * @return FixerInterface[] An array of FixerInterface + * @return list */ public function getFixers(): array { @@ -325,19 +340,15 @@ public function getFixers(): array if (false === $this->getRiskyAllowed()) { $riskyFixers = array_map( - static function (FixerInterface $fixer): string { - return $fixer->getName(); - }, + static fn (FixerInterface $fixer): string => $fixer->getName(), array_filter( $this->fixers, - static function (FixerInterface $fixer): bool { - return $fixer->isRisky(); - } + static fn (FixerInterface $fixer): bool => $fixer->isRisky() ) ); if (\count($riskyFixers) > 0) { - throw new InvalidConfigurationException(sprintf('The rules contain risky fixers ("%s"), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode('", "', $riskyFixers))); + throw new InvalidConfigurationException(\sprintf('The rules contain risky fixers (%s), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', Utils::naturalLanguageJoin($riskyFixers))); } } } @@ -357,7 +368,7 @@ public function getLinter(): LinterInterface /** * Returns path. * - * @return string[] + * @return list */ public function getPath(): array { @@ -381,7 +392,7 @@ static function (string $rawPath) use ($cwd, $filesystem): string { : $cwd.\DIRECTORY_SEPARATOR.$path; if (!file_exists($absolutePath)) { - throw new InvalidConfigurationException(sprintf( + throw new InvalidConfigurationException(\sprintf( 'The path "%s" is not readable.', $path )); @@ -400,26 +411,27 @@ static function (string $rawPath) use ($cwd, $filesystem): string { /** * @throws InvalidConfigurationException */ - public function getProgress(): string + public function getProgressType(): string { if (null === $this->progress) { - if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) { + if ('txt' === $this->getFormat()) { $progressType = $this->options['show-progress']; - $progressTypes = ['none', 'dots']; if (null === $progressType) { - $progressType = $this->getConfig()->getHideProgress() ? 'none' : 'dots'; - } elseif (!\in_array($progressType, $progressTypes, true)) { - throw new InvalidConfigurationException(sprintf( - 'The progress type "%s" is not defined, supported are "%s".', + $progressType = $this->getConfig()->getHideProgress() + ? ProgressOutputType::NONE + : ProgressOutputType::BAR; + } elseif (!\in_array($progressType, ProgressOutputType::all(), true)) { + throw new InvalidConfigurationException(\sprintf( + 'The progress type "%s" is not defined, supported are %s.', $progressType, - implode('", "', $progressTypes) + Utils::naturalLanguageJoin(ProgressOutputType::all()) )); } $this->progress = $progressType; } else { - $this->progress = 'none'; + $this->progress = ProgressOutputType::NONE; } } @@ -440,7 +452,7 @@ public function getReporter(): ReporterInterface $formats = $reporterFactory->getFormats(); sort($formats); - throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats))); + throw new InvalidConfigurationException(\sprintf('The format "%s" is not defined, supported are %s.', $format, Utils::naturalLanguageJoin($formats))); } } @@ -462,6 +474,8 @@ public function getRiskyAllowed(): bool /** * Returns rules. + * + * @return array|bool> */ public function getRules(): array { @@ -478,11 +492,14 @@ public function getUsingCache(): bool } } - $this->usingCache = $this->usingCache && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer()); + $this->usingCache = $this->usingCache && $this->isCachingAllowedForRuntime(); return $this->usingCache; } + /** + * @return iterable<\SplFileInfo> + */ public function getFinder(): iterable { if (null === $this->finder) { @@ -526,7 +543,7 @@ public function configFinderIsOverridden(): bool /** * Compute file candidates for config file. * - * @return string[] + * @return list */ private function computeConfigFiles(): array { @@ -534,7 +551,7 @@ private function computeConfigFiles(): array if (null !== $configFile) { if (false === file_exists($configFile) || false === is_readable($configFile)) { - throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile)); + throw new InvalidConfigurationException(\sprintf('Cannot read config file "%s".', $configFile)); } return [$configFile]; @@ -550,7 +567,7 @@ private function computeConfigFiles(): array $configDir = $path[0]; } else { $dirName = pathinfo($path[0], PATHINFO_DIRNAME); - $configDir = $dirName ?: $path[0]; + $configDir = is_dir($dirName) ? $dirName : $path[0]; } $candidates = [ @@ -613,13 +630,20 @@ private function isStdIn(): bool return $this->isStdIn; } + /** + * @template T + * + * @param iterable $iterable + * + * @return \Traversable + */ private function iterableToTraversable(iterable $iterable): \Traversable { return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; } /** - * Compute rules. + * @return array */ private function parseRules(): array { @@ -636,7 +660,7 @@ private function parseRules(): array $rules = json_decode($rules, true); if (JSON_ERROR_NONE !== json_last_error()) { - throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg())); + throw new InvalidConfigurationException(\sprintf('Invalid JSON rules input: "%s".', json_last_error_msg())); } return $rules; @@ -662,6 +686,8 @@ private function parseRules(): array } /** + * @param array $rules + * * @throws InvalidConfigurationException */ private function validateRules(array $rules): void @@ -675,7 +701,7 @@ private function validateRules(array $rules): void foreach ($rules as $key => $value) { if (\is_int($key)) { - throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value)); + throw new InvalidConfigurationException(\sprintf('Missing value for "%s" rule/set.', $value)); } $ruleSet[$key] = true; @@ -683,20 +709,13 @@ private function validateRules(array $rules): void $ruleSet = new RuleSet($ruleSet); - /** @var string[] $configuredFixers */ $configuredFixers = array_keys($ruleSet->getRules()); $fixers = $this->createFixerFactory()->getFixers(); - /** @var string[] $availableFixers */ - $availableFixers = array_map(static function (FixerInterface $fixer): string { - return $fixer->getName(); - }, $fixers); + $availableFixers = array_map(static fn (FixerInterface $fixer): string => $fixer->getName(), $fixers); - $unknownFixers = array_diff( - $configuredFixers, - $availableFixers - ); + $unknownFixers = array_diff($configuredFixers, $availableFixers); if (\count($unknownFixers) > 0) { $renamedRules = [ @@ -758,16 +777,16 @@ private function validateRules(array $rules): void foreach ($unknownFixers as $unknownFixer) { if (isset($renamedRules[$unknownFixer])) { // Check if present as old renamed rule $hasOldRule = true; - $message .= sprintf( + $message .= \sprintf( '"%s" is renamed (did you mean "%s"?%s), ', $unknownFixer, $renamedRules[$unknownFixer]['new_name'], - isset($renamedRules[$unknownFixer]['config']) ? ' (note: use configuration "'.HelpCommand::toString($renamedRules[$unknownFixer]['config']).'")' : '' + isset($renamedRules[$unknownFixer]['config']) ? ' (note: use configuration "'.Utils::toString($renamedRules[$unknownFixer]['config']).'")' : '' ); } else { // Go to normal matcher if it is not a renamed rule $matcher = new WordMatcher($availableFixers); $alternative = $matcher->match($unknownFixer); - $message .= sprintf( + $message .= \sprintf( '"%s"%s, ', $unknownFixer, null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)' @@ -778,7 +797,7 @@ private function validateRules(array $rules): void $message = substr($message, 0, -2).'.'; if ($hasOldRule) { - $message .= "\nFor more info about updating see: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless."; + $message .= "\nFor more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v3.0.0/UPGRADE-v3.md#renamed-ruless."; } throw new InvalidConfigurationException($message); @@ -789,8 +808,8 @@ private function validateRules(array $rules): void if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) { $successors = $fixer->getSuccessorsNames(); $messageEnd = [] === $successors - ? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1) - : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors))); + ? \sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1) + : \sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors))); Utils::triggerDeprecation(new \RuntimeException("Rule \"{$fixerName}\" is deprecated{$messageEnd}")); } @@ -799,6 +818,8 @@ private function validateRules(array $rules): void /** * Apply path on config instance. + * + * @return iterable<\SplFileInfo> */ private function resolveFinder(): iterable { @@ -815,21 +836,19 @@ private function resolveFinder(): iterable $modes, true )) { - throw new InvalidConfigurationException(sprintf( - 'The path-mode "%s" is not defined, supported are "%s".', + throw new InvalidConfigurationException(\sprintf( + 'The path-mode "%s" is not defined, supported are %s.', $this->options['path-mode'], - implode('", "', $modes) + Utils::naturalLanguageJoin($modes) )); } $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode']; - $paths = array_filter(array_map( - static function (string $path) { - return realpath($path); - }, + $paths = array_map( + static fn (string $path) => realpath($path), $this->getPath() - )); + ); if (0 === \count($paths)) { if ($isIntersectionPathMode) { @@ -907,7 +926,7 @@ static function (\SplFileInfo $current) use ($pathsByType): bool { private function setOption(string $name, $value): void { if (!\array_key_exists($name, $this->options)) { - throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name)); + throw new InvalidConfigurationException(\sprintf('Unknown option name: "%s".', $name)); } $this->options[$name] = $value; @@ -918,7 +937,7 @@ private function resolveOptionBooleanValue(string $optionName): bool $value = $this->options[$optionName]; if (!\is_string($value)) { - throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName)); + throw new InvalidConfigurationException(\sprintf('Expected boolean or string value for option "%s".', $optionName)); } if ('yes' === $value) { @@ -929,7 +948,7 @@ private function resolveOptionBooleanValue(string $optionName): bool return false; } - throw new InvalidConfigurationException(sprintf('Expected "yes" or "no" for option "%s", got "%s".', $optionName, $value)); + throw new InvalidConfigurationException(\sprintf('Expected "yes" or "no" for option "%s", got "%s".', $optionName, $value)); } private static function separatedContextLessInclude(string $path): ConfigInterface @@ -938,9 +957,17 @@ private static function separatedContextLessInclude(string $path): ConfigInterfa // verify that the config has an instance of Config if (!$config instanceof ConfigInterface) { - throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $path, \is_object($config) ? \get_class($config) : \gettype($config))); + throw new InvalidConfigurationException(\sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $path, \is_object($config) ? \get_class($config) : \gettype($config))); } return $config; } + + private function isCachingAllowedForRuntime(): bool + { + return $this->toolInfo->isInstalledAsPhar() + || $this->toolInfo->isInstalledByComposer() + || $this->toolInfo->isRunInsideDocker() + || filter_var(getenv('PHP_CS_FIXER_ENFORCE_CACHE'), FILTER_VALIDATE_BOOL); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php index 3e0efdaf74..2c54e1376b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ErrorOutput.php @@ -17,6 +17,7 @@ use PhpCsFixer\Differ\DiffConsoleFormatter; use PhpCsFixer\Error\Error; use PhpCsFixer\Linter\LintingException; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Output\OutputInterface; @@ -39,11 +40,11 @@ public function __construct(OutputInterface $output) } /** - * @param Error[] $errors + * @param list $errors */ public function listErrors(string $process, array $errors): void { - $this->output->writeln(['', sprintf( + $this->output->writeln(['', \sprintf( 'Files that were not fixed due to errors reported during %s:', $process )]); @@ -51,13 +52,13 @@ public function listErrors(string $process, array $errors): void $showDetails = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE; $showTrace = $this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG; foreach ($errors as $i => $error) { - $this->output->writeln(sprintf('%4d) %s', $i + 1, $error->getFilePath())); + $this->output->writeln(\sprintf('%4d) %s', $i + 1, $error->getFilePath())); $e = $error->getSource(); if (!$showDetails || null === $e) { continue; } - $class = sprintf('[%s]', \get_class($e)); + $class = \sprintf('[%s]', \get_class($e)); $message = $e->getMessage(); $code = $e->getCode(); if (0 !== $code) { @@ -79,14 +80,14 @@ public function listErrors(string $process, array $errors): void $line .= str_repeat(' ', $length - \strlen($line)); } - $this->output->writeln(sprintf(' %s ', $this->prepareOutput($line))); + $this->output->writeln(\sprintf(' %s ', $this->prepareOutput($line))); } if ($showTrace && !$e instanceof LintingException) { // stack trace of lint exception is of no interest $this->output->writeln(''); $stackTrace = $e->getTrace(); foreach ($stackTrace as $trace) { - if (isset($trace['class'], $trace['function']) && \Symfony\Component\Console\Command\Command::class === $trace['class'] && 'run' === $trace['function']) { + if (isset($trace['class']) && Command::class === $trace['class'] && 'run' === $trace['function']) { $this->output->writeln(' [ ... ]'); break; @@ -98,13 +99,13 @@ public function listErrors(string $process, array $errors): void if (Error::TYPE_LINT === $error->getType() && 0 < \count($error->getAppliedFixers())) { $this->output->writeln(''); - $this->output->writeln(sprintf(' Applied fixers: %s', implode(', ', $error->getAppliedFixers()))); + $this->output->writeln(\sprintf(' Applied fixers: %s', implode(', ', $error->getAppliedFixers()))); $diff = $error->getDiff(); - if (!empty($diff)) { + if (null !== $diff) { $diffFormatter = new DiffConsoleFormatter( $this->isDecorated, - sprintf( + \sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL @@ -117,21 +118,32 @@ public function listErrors(string $process, array $errors): void } } + /** + * @param array{ + * function?: string, + * line?: int, + * file?: string, + * class?: class-string, + * type?: '::'|'->', + * args?: mixed[], + * object?: object, + * } $trace + */ private function outputTrace(array $trace): void { if (isset($trace['class'], $trace['type'], $trace['function'])) { - $this->output->writeln(sprintf( + $this->output->writeln(\sprintf( ' %s%s%s()', $this->prepareOutput($trace['class']), $this->prepareOutput($trace['type']), $this->prepareOutput($trace['function']) )); } elseif (isset($trace['function'])) { - $this->output->writeln(sprintf(' %s()', $this->prepareOutput($trace['function']))); + $this->output->writeln(\sprintf(' %s()', $this->prepareOutput($trace['function']))); } if (isset($trace['file'])) { - $this->output->writeln(sprintf(' in %s at line %d', $this->prepareOutput($trace['file']), $trace['line'])); + $this->output->writeln(\sprintf(' in %s at line %d', $this->prepareOutput($trace['file']), $trace['line'])); } } @@ -139,7 +151,6 @@ private function prepareOutput(string $string): string { return $this->isDecorated ? OutputFormatter::escape($string) - : $string - ; + : $string; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/OutputContext.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/OutputContext.php new file mode 100644 index 0000000000..84a0ea6f8d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/OutputContext.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @internal + */ +final class OutputContext +{ + private ?OutputInterface $output; + private int $terminalWidth; + private int $filesCount; + + public function __construct( + ?OutputInterface $output, + int $terminalWidth, + int $filesCount + ) { + $this->output = $output; + $this->terminalWidth = $terminalWidth; + $this->filesCount = $filesCount; + } + + public function getOutput(): ?OutputInterface + { + return $this->output; + } + + public function getTerminalWidth(): int + { + return $this->terminalWidth; + } + + public function getFilesCount(): int + { + return $this->filesCount; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/DotsOutput.php similarity index 63% rename from vendor/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php rename to vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/DotsOutput.php index 6ca9d2760f..afe8322b4c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutput.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/DotsOutput.php @@ -12,37 +12,35 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\Console\Output; +namespace PhpCsFixer\Console\Output\Progress; +use PhpCsFixer\Console\Output\OutputContext; use PhpCsFixer\FixerFileProcessedEvent; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** - * Output writer to show the process of a FixCommand. + * Output writer to show the progress of a FixCommand using dots and meaningful letters. * * @internal */ -final class ProcessOutput implements ProcessOutputInterface +final class DotsOutput implements ProgressOutputInterface { /** * File statuses map. + * + * @var array */ private static array $eventStatusMap = [ - FixerFileProcessedEvent::STATUS_UNKNOWN => ['symbol' => '?', 'format' => '%s', 'description' => 'unknown'], - FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '%s', 'description' => 'invalid file syntax (file ignored)'], - FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '%s', 'description' => 'skipped (cached or empty file)'], FixerFileProcessedEvent::STATUS_NO_CHANGES => ['symbol' => '.', 'format' => '%s', 'description' => 'no changes'], FixerFileProcessedEvent::STATUS_FIXED => ['symbol' => 'F', 'format' => '%s', 'description' => 'fixed'], + FixerFileProcessedEvent::STATUS_SKIPPED => ['symbol' => 'S', 'format' => '%s', 'description' => 'skipped (cached or empty file)'], + FixerFileProcessedEvent::STATUS_INVALID => ['symbol' => 'I', 'format' => '%s', 'description' => 'invalid file syntax (file ignored)'], FixerFileProcessedEvent::STATUS_EXCEPTION => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '%s', 'description' => 'error'], ]; - private OutputInterface $output; - - private EventDispatcherInterface $eventDispatcher; - - private int $files; + /** @readonly */ + private OutputContext $context; private int $processedFiles = 0; @@ -51,22 +49,14 @@ final class ProcessOutput implements ProcessOutputInterface */ private $symbolsPerLine; - public function __construct(OutputInterface $output, EventDispatcherInterface $dispatcher, int $width, int $nbFiles) + public function __construct(OutputContext $context) { - $this->output = $output; - $this->eventDispatcher = $dispatcher; - $this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); - $this->files = $nbFiles; + $this->context = $context; // max number of characters per line // - total length x 2 (e.g. " 1 / 123" => 6 digits and padding spaces) // - 11 (extra spaces, parentheses and percentage characters, e.g. " x / x (100%)") - $this->symbolsPerLine = max(1, $width - \strlen((string) $this->files) * 2 - 11); - } - - public function __destruct() - { - $this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']); + $this->symbolsPerLine = max(1, $context->getTerminalWidth() - \strlen((string) $context->getFilesCount()) * 2 - 11); } /** @@ -75,7 +65,7 @@ public function __destruct() */ public function __sleep(): array { - throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + throw new \BadMethodCallException('Cannot serialize '.self::class); } /** @@ -86,30 +76,30 @@ public function __sleep(): array */ public function __wakeup(): void { - throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + throw new \BadMethodCallException('Cannot unserialize '.self::class); } public function onFixerFileProcessed(FixerFileProcessedEvent $event): void { $status = self::$eventStatusMap[$event->getStatus()]; - $this->output->write($this->output->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']); + $this->getOutput()->write($this->getOutput()->isDecorated() ? \sprintf($status['format'], $status['symbol']) : $status['symbol']); ++$this->processedFiles; $symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine; - $isLast = $this->processedFiles === $this->files; + $isLast = $this->processedFiles === $this->context->getFilesCount(); if (0 === $symbolsOnCurrentLine || $isLast) { - $this->output->write(sprintf( - '%s %'.\strlen((string) $this->files).'d / %d (%3d%%)', + $this->getOutput()->write(\sprintf( + '%s %'.\strlen((string) $this->context->getFilesCount()).'d / %d (%3d%%)', $isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '', $this->processedFiles, - $this->files, - round($this->processedFiles / $this->files * 100) + $this->context->getFilesCount(), + round($this->processedFiles / $this->context->getFilesCount() * 100) )); if (!$isLast) { - $this->output->writeln(''); + $this->getOutput()->writeln(''); } } } @@ -124,9 +114,14 @@ public function printLegend(): void continue; } - $symbols[$symbol] = sprintf('%s-%s', $this->output->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']); + $symbols[$symbol] = \sprintf('%s-%s', $this->getOutput()->isDecorated() ? \sprintf($status['format'], $symbol) : $symbol, $status['description']); } - $this->output->write(sprintf("\nLegend: %s\n", implode(', ', $symbols))); + $this->getOutput()->write(\sprintf("\nLegend: %s\n", implode(', ', $symbols))); + } + + private function getOutput(): OutputInterface + { + return $this->context->getOutput(); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/NullOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/NullOutput.php new file mode 100644 index 0000000000..3af7ee4435 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/NullOutput.php @@ -0,0 +1,27 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\FixerFileProcessedEvent; + +/** + * @internal + */ +final class NullOutput implements ProgressOutputInterface +{ + public function printLegend(): void {} + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/PercentageBarOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/PercentageBarOutput.php new file mode 100644 index 0000000000..369bed1d41 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/PercentageBarOutput.php @@ -0,0 +1,76 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\Console\Output\OutputContext; +use PhpCsFixer\FixerFileProcessedEvent; +use Symfony\Component\Console\Helper\ProgressBar; + +/** + * Output writer to show the progress of a FixCommand using progress bar (percentage). + * + * @internal + */ +final class PercentageBarOutput implements ProgressOutputInterface +{ + /** @readonly */ + private OutputContext $context; + + private ProgressBar $progressBar; + + public function __construct(OutputContext $context) + { + $this->context = $context; + + $this->progressBar = new ProgressBar($context->getOutput(), $this->context->getFilesCount()); + $this->progressBar->setBarCharacter('▓'); // dark shade character \u2593 + $this->progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $this->progressBar->setProgressCharacter(''); + $this->progressBar->setFormat('normal'); + + $this->progressBar->start(); + } + + /** + * This class is not intended to be serialized, + * and cannot be deserialized (see __wakeup method). + */ + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.self::class); + } + + /** + * Disable the deserialization of the class to prevent attacker executing + * code by leveraging the __destruct method. + * + * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection + */ + public function __wakeup(): void + { + throw new \BadMethodCallException('Cannot unserialize '.self::class); + } + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void + { + $this->progressBar->advance(1); + + if ($this->progressBar->getProgress() === $this->progressBar->getMaxSteps()) { + $this->context->getOutput()->write("\n\n"); + } + } + + public function printLegend(): void {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputFactory.php new file mode 100644 index 0000000000..71027d833a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputFactory.php @@ -0,0 +1,55 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\Console\Output\OutputContext; + +/** + * @internal + */ +final class ProgressOutputFactory +{ + /** + * @var array> + */ + private static array $outputTypeMap = [ + ProgressOutputType::NONE => NullOutput::class, + ProgressOutputType::DOTS => DotsOutput::class, + ProgressOutputType::BAR => PercentageBarOutput::class, + ]; + + public function create(string $outputType, OutputContext $context): ProgressOutputInterface + { + if (null === $context->getOutput()) { + $outputType = ProgressOutputType::NONE; + } + + if (!$this->isBuiltInType($outputType)) { + throw new \InvalidArgumentException( + \sprintf( + 'Something went wrong, "%s" output type is not supported', + $outputType + ) + ); + } + + return new self::$outputTypeMap[$outputType]($context); + } + + private function isBuiltInType(string $outputType): bool + { + return \in_array($outputType, ProgressOutputType::all(), true); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputInterface.php new file mode 100644 index 0000000000..b5ab951730 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputInterface.php @@ -0,0 +1,27 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +use PhpCsFixer\FixerFileProcessedEvent; + +/** + * @internal + */ +interface ProgressOutputInterface +{ + public function printLegend(): void; + + public function onFixerFileProcessed(FixerFileProcessedEvent $event): void; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputType.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputType.php new file mode 100644 index 0000000000..6f427a46d9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Output/Progress/ProgressOutputType.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Output\Progress; + +/** + * @internal + */ +final class ProgressOutputType +{ + public const NONE = 'none'; + public const DOTS = 'dots'; + public const BAR = 'bar'; + + /** + * @return list + */ + public static function all(): array + { + return [ + self::BAR, + self::DOTS, + self::NONE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php index c283b23820..48daf7b2fa 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/CheckstyleReporter.php @@ -14,6 +14,7 @@ namespace PhpCsFixer\Console\Report\FixReport; +use PhpCsFixer\Console\Application; use Symfony\Component\Console\Formatter\OutputFormatter; /** @@ -23,17 +24,11 @@ */ final class CheckstyleReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'checkstyle'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { if (!\extension_loaded('dom')) { @@ -41,7 +36,10 @@ public function generate(ReportSummary $reportSummary): string } $dom = new \DOMDocument('1.0', 'UTF-8'); + + /** @var \DOMElement $checkstyles */ $checkstyles = $dom->appendChild($dom->createElement('checkstyle')); + $checkstyles->setAttribute('version', Application::getAbout()); foreach ($reportSummary->getChanged() as $filePath => $fixResult) { /** @var \DOMElement $file */ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php index 974d66d119..23aad2e838 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/GitlabReporter.php @@ -14,6 +14,10 @@ namespace PhpCsFixer\Console\Report\FixReport; +use PhpCsFixer\Console\Application; +use SebastianBergmann\Diff\Chunk; +use SebastianBergmann\Diff\Diff; +use SebastianBergmann\Diff\Parser; use Symfony\Component\Console\Formatter\OutputFormatter; /** @@ -27,6 +31,13 @@ */ final class GitlabReporter implements ReporterInterface { + private Parser $diffParser; + + public function __construct() + { + $this->diffParser = new Parser(); + } + public function getFormat(): string { return 'gitlab'; @@ -37,25 +48,47 @@ public function getFormat(): string */ public function generate(ReportSummary $reportSummary): string { + $about = Application::getAbout(); + $report = []; foreach ($reportSummary->getChanged() as $fileName => $change) { foreach ($change['appliedFixers'] as $fixerName) { $report[] = [ - 'description' => $fixerName, + 'check_name' => 'PHP-CS-Fixer.'.$fixerName, + 'description' => 'PHP-CS-Fixer.'.$fixerName.' by '.$about, + 'categories' => ['Style'], 'fingerprint' => md5($fileName.$fixerName), 'severity' => 'minor', 'location' => [ 'path' => $fileName, - 'lines' => [ - 'begin' => 0, // line numbers are required in the format, but not available to reports - ], + 'lines' => self::getLines($this->diffParser->parse($change['diff'])), ], ]; } } - $jsonString = json_encode($report); + $jsonString = json_encode($report, JSON_THROW_ON_ERROR); return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($jsonString) : $jsonString; } + + /** + * @param list $diffs + * + * @return array{begin: int, end: int} + */ + private static function getLines(array $diffs): array + { + if (isset($diffs[0])) { + $firstDiff = $diffs[0]; + + $firstChunk = \Closure::bind(static fn (Diff $diff) => array_shift($diff->chunks), null, $firstDiff)($firstDiff); + + if ($firstChunk instanceof Chunk) { + return \Closure::bind(static fn (Chunk $chunk): array => ['begin' => $chunk->start, 'end' => $chunk->startRange], null, $firstChunk)($firstChunk); + } + } + + return ['begin' => 0, 'end' => 0]; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php index 6bb05120c0..0dc01dba4d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JsonReporter.php @@ -14,6 +14,7 @@ namespace PhpCsFixer\Console\Report\FixReport; +use PhpCsFixer\Console\Application; use Symfony\Component\Console\Formatter\OutputFormatter; /** @@ -23,17 +24,11 @@ */ final class JsonReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'json'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { $jsonFiles = []; @@ -45,7 +40,7 @@ public function generate(ReportSummary $reportSummary): string $jsonFile['appliedFixers'] = $fixResult['appliedFixers']; } - if (!empty($fixResult['diff'])) { + if ('' !== $fixResult['diff']) { $jsonFile['diff'] = $fixResult['diff']; } @@ -53,14 +48,15 @@ public function generate(ReportSummary $reportSummary): string } $json = [ + 'about' => Application::getAbout(), 'files' => $jsonFiles, 'time' => [ - 'total' => round($reportSummary->getTime() / 1000, 3), + 'total' => round($reportSummary->getTime() / 1_000, 3), ], - 'memory' => round($reportSummary->getMemory() / 1024 / 1024, 3), + 'memory' => round($reportSummary->getMemory() / 1_024 / 1_024, 3), ]; - $json = json_encode($json); + $json = json_encode($json, JSON_THROW_ON_ERROR); return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($json) : $json; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php index 1614f37ca9..ca2817fd87 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/JunitReporter.php @@ -14,6 +14,7 @@ namespace PhpCsFixer\Console\Report\FixReport; +use PhpCsFixer\Console\Application; use PhpCsFixer\Preg; use Symfony\Component\Console\Formatter\OutputFormatter; @@ -24,17 +25,11 @@ */ final class JunitReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'junit'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { if (!\extension_loaded('dom')) { @@ -44,10 +39,17 @@ public function generate(ReportSummary $reportSummary): string $dom = new \DOMDocument('1.0', 'UTF-8'); $testsuites = $dom->appendChild($dom->createElement('testsuites')); - /** @var \DomElement $testsuite */ + /** @var \DOMElement $testsuite */ $testsuite = $testsuites->appendChild($dom->createElement('testsuite')); $testsuite->setAttribute('name', 'PHP CS Fixer'); + $properties = $dom->createElement('properties'); + $property = $dom->createElement('property'); + $property->setAttribute('name', 'about'); + $property->setAttribute('value', Application::getAbout()); + $properties->appendChild($property); + $testsuite->appendChild($properties); + if (\count($reportSummary->getChanged()) > 0) { $this->createFailedTestCases($dom, $testsuite, $reportSummary); } else { @@ -57,9 +59,9 @@ public function generate(ReportSummary $reportSummary): string if ($reportSummary->getTime() > 0) { $testsuite->setAttribute( 'time', - sprintf( + \sprintf( '%.3f', - $reportSummary->getTime() / 1000 + $reportSummary->getTime() / 1_000 ) ); } @@ -102,6 +104,9 @@ private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite $testsuite->setAttribute('errors', '0'); } + /** + * @param array{appliedFixers: list, diff: string} $fixResult + */ private function createFailedTestCase(\DOMDocument $dom, string $file, array $fixResult, bool $shouldAddAppliedFixers): \DOMElement { $appliedFixersCount = \count($fixResult['appliedFixers']); @@ -127,7 +132,7 @@ private function createFailedTestCase(\DOMDocument $dom, string $file, array $fi $failureContent = "Wrong code style\n"; } - if (!empty($fixResult['diff'])) { + if ('' !== $fixResult['diff']) { $failureContent .= "\nDiff:\n---------------\n\n".$fixResult['diff']; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php index 3c48a0628e..ccdfefb322 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReportSummary.php @@ -21,8 +21,13 @@ */ final class ReportSummary { + /** + * @var array, diff: string}> + */ private array $changed; + private int $filesCount; + private int $time; private int $memory; @@ -34,11 +39,13 @@ final class ReportSummary private bool $isDecoratedOutput; /** - * @param int $time duration in milliseconds - * @param int $memory memory usage in bytes + * @param array, diff: string}> $changed + * @param int $time duration in milliseconds + * @param int $memory memory usage in bytes */ public function __construct( array $changed, + int $filesCount, int $time, int $memory, bool $addAppliedFixers, @@ -46,6 +53,7 @@ public function __construct( bool $isDecoratedOutput ) { $this->changed = $changed; + $this->filesCount = $filesCount; $this->time = $time; $this->memory = $memory; $this->addAppliedFixers = $addAppliedFixers; @@ -63,6 +71,9 @@ public function isDryRun(): bool return $this->isDryRun; } + /** + * @return array, diff: string}> + */ public function getChanged(): array { return $this->changed; @@ -78,6 +89,11 @@ public function getTime(): int return $this->time; } + public function getFilesCount(): int + { + return $this->filesCount; + } + public function shouldAddAppliedFixers(): bool { return $this->addAppliedFixers; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php index 5ffcb5ad04..1b2d81229b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/ReporterFactory.php @@ -15,7 +15,6 @@ namespace PhpCsFixer\Console\Report\FixReport; use Symfony\Component\Finder\Finder as SymfonyFinder; -use Symfony\Component\Finder\SplFileInfo; /** * @author Boris Gorbylev @@ -24,26 +23,23 @@ */ final class ReporterFactory { - /** - * @var ReporterInterface[] - */ + /** @var array */ private array $reporters = []; public function registerBuiltInReporters(): self { - /** @var null|string[] $builtInReporters */ + /** @var null|list $builtInReporters */ static $builtInReporters; if (null === $builtInReporters) { $builtInReporters = []; - /** @var SplFileInfo $file */ foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) { $relativeNamespace = $file->getRelativePath(); - $builtInReporters[] = sprintf( - '%s\\%s%s', + $builtInReporters[] = \sprintf( + '%s\%s%s', __NAMESPACE__, - $relativeNamespace ? $relativeNamespace.'\\' : '', + '' !== $relativeNamespace ? $relativeNamespace.'\\' : '', $file->getBasename('.php') ); } @@ -64,7 +60,7 @@ public function registerReporter(ReporterInterface $reporter): self $format = $reporter->getFormat(); if (isset($this->reporters[$format])) { - throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); + throw new \UnexpectedValueException(\sprintf('Reporter for format "%s" is already registered.', $format)); } $this->reporters[$format] = $reporter; @@ -73,7 +69,7 @@ public function registerReporter(ReporterInterface $reporter): self } /** - * @return string[] + * @return list */ public function getFormats(): array { @@ -86,7 +82,7 @@ public function getFormats(): array public function getReporter(string $format): ReporterInterface { if (!isset($this->reporters[$format])) { - throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); + throw new \UnexpectedValueException(\sprintf('Reporter for format "%s" is not registered.', $format)); } return $this->reporters[$format]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php index 19525bc931..79a548e9eb 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/TextReporter.php @@ -23,71 +23,80 @@ */ final class TextReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'txt'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { $output = ''; - $i = 0; + $identifiedFiles = 0; foreach ($reportSummary->getChanged() as $file => $fixResult) { - ++$i; - $output .= sprintf('%4d) %s', $i, $file); + ++$identifiedFiles; + $output .= \sprintf('%4d) %s', $identifiedFiles, $file); if ($reportSummary->shouldAddAppliedFixers()) { - $output .= $this->getAppliedFixers($reportSummary->isDecoratedOutput(), $fixResult); + $output .= $this->getAppliedFixers( + $reportSummary->isDecoratedOutput(), + $fixResult['appliedFixers'], + ); } - $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult); + $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult['diff']); $output .= PHP_EOL; } - return $output.$this->getFooter($reportSummary->getTime(), $reportSummary->getMemory(), $reportSummary->isDryRun()); + return $output.$this->getFooter( + $reportSummary->getTime(), + $identifiedFiles, + $reportSummary->getFilesCount(), + $reportSummary->getMemory(), + $reportSummary->isDryRun() + ); } - private function getAppliedFixers(bool $isDecoratedOutput, array $fixResult): string + /** + * @param list $appliedFixers + */ + private function getAppliedFixers(bool $isDecoratedOutput, array $appliedFixers): string { - return sprintf( + return \sprintf( $isDecoratedOutput ? ' (%s)' : ' (%s)', - implode(', ', $fixResult['appliedFixers']) + implode(', ', $appliedFixers) ); } - private function getDiff(bool $isDecoratedOutput, array $fixResult): string + private function getDiff(bool $isDecoratedOutput, string $diff): string { - if (empty($fixResult['diff'])) { + if ('' === $diff) { return ''; } - $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf( + $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, \sprintf( ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------', PHP_EOL, PHP_EOL )); - return PHP_EOL.$diffFormatter->format($fixResult['diff']).PHP_EOL; + return PHP_EOL.$diffFormatter->format($diff).PHP_EOL; } - private function getFooter(int $time, int $memory, bool $isDryRun): string + private function getFooter(int $time, int $identifiedFiles, int $files, int $memory, bool $isDryRun): string { if (0 === $time || 0 === $memory) { return ''; } - return PHP_EOL.sprintf( - '%s all files in %.3f seconds, %.3f MB memory used'.PHP_EOL, - $isDryRun ? 'Checked' : 'Fixed', - $time / 1000, - $memory / 1024 / 1024 + return PHP_EOL.\sprintf( + '%s %d of %d %s in %.3f seconds, %.2f MB memory used'.PHP_EOL, + $isDryRun ? 'Found' : 'Fixed', + $identifiedFiles, + $files, + $isDryRun ? 'files that can be fixed' : 'files', + $time / 1_000, + $memory / 1_024 / 1_024 ); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php index 16edd37900..78209ba928 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/FixReport/XmlReporter.php @@ -14,6 +14,7 @@ namespace PhpCsFixer\Console\Report\FixReport; +use PhpCsFixer\Console\Application; use Symfony\Component\Console\Formatter\OutputFormatter; /** @@ -23,17 +24,11 @@ */ final class XmlReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'xml'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { if (!\extension_loaded('dom')) { @@ -45,6 +40,8 @@ public function generate(ReportSummary $reportSummary): string $root = $dom->createElement('report'); $dom->appendChild($root); + $root->appendChild($this->createAboutElement($dom, Application::getAbout())); + $filesXML = $dom->createElement('files'); $root->appendChild($filesXML); @@ -56,11 +53,13 @@ public function generate(ReportSummary $reportSummary): string $filesXML->appendChild($fileXML); if ($reportSummary->shouldAddAppliedFixers()) { - $fileXML->appendChild($this->createAppliedFixersElement($dom, $fixResult)); + $fileXML->appendChild( + $this->createAppliedFixersElement($dom, $fixResult['appliedFixers']), + ); } - if (!empty($fixResult['diff'])) { - $fileXML->appendChild($this->createDiffElement($dom, $fixResult)); + if ('' !== $fixResult['diff']) { + $fileXML->appendChild($this->createDiffElement($dom, $fixResult['diff'])); } } @@ -77,11 +76,14 @@ public function generate(ReportSummary $reportSummary): string return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML(); } - private function createAppliedFixersElement(\DOMDocument $dom, array $fixResult): \DOMElement + /** + * @param list $appliedFixers + */ + private function createAppliedFixersElement(\DOMDocument $dom, array $appliedFixers): \DOMElement { $appliedFixersXML = $dom->createElement('applied_fixers'); - foreach ($fixResult['appliedFixers'] as $appliedFixer) { + foreach ($appliedFixers as $appliedFixer) { $appliedFixerXML = $dom->createElement('applied_fixer'); $appliedFixerXML->setAttribute('name', $appliedFixer); $appliedFixersXML->appendChild($appliedFixerXML); @@ -90,17 +92,17 @@ private function createAppliedFixersElement(\DOMDocument $dom, array $fixResult) return $appliedFixersXML; } - private function createDiffElement(\DOMDocument $dom, array $fixResult): \DOMElement + private function createDiffElement(\DOMDocument $dom, string $diff): \DOMElement { $diffXML = $dom->createElement('diff'); - $diffXML->appendChild($dom->createCDATASection($fixResult['diff'])); + $diffXML->appendChild($dom->createCDATASection($diff)); return $diffXML; } private function createTimeElement(float $time, \DOMDocument $dom): \DOMElement { - $time = round($time / 1000, 3); + $time = round($time / 1_000, 3); $timeXML = $dom->createElement('time'); $timeXML->setAttribute('unit', 's'); @@ -113,7 +115,7 @@ private function createTimeElement(float $time, \DOMDocument $dom): \DOMElement private function createMemoryElement(float $memory, \DOMDocument $dom): \DOMElement { - $memory = round($memory / 1024 / 1024, 3); + $memory = round($memory / 1_024 / 1_024, 3); $memoryXML = $dom->createElement('memory'); $memoryXML->setAttribute('value', (string) $memory); @@ -121,4 +123,12 @@ private function createMemoryElement(float $memory, \DOMDocument $dom): \DOMElem return $memoryXML; } + + private function createAboutElement(\DOMDocument $dom, string $about): \DOMElement + { + $xml = $dom->createElement('about'); + $xml->setAttribute('value', $about); + + return $xml; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php index 2046939717..ceebd89e33 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/JsonReporter.php @@ -23,24 +23,16 @@ */ final class JsonReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'json'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { $sets = $reportSummary->getSets(); - usort($sets, static function (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int { - return strcmp($a->getName(), $b->getName()); - }); + usort($sets, static fn (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int => $a->getName() <=> $b->getName()); $json = ['sets' => []]; @@ -53,6 +45,6 @@ public function generate(ReportSummary $reportSummary): string ]; } - return json_encode($json, JSON_PRETTY_PRINT); + return json_encode($json, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php index 6ca98d9a41..c7d66e7151 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReportSummary.php @@ -24,12 +24,12 @@ final class ReportSummary { /** - * @var RuleSetDescriptionInterface[] + * @var list */ private array $sets; /** - * @param RuleSetDescriptionInterface[] $sets + * @param list $sets */ public function __construct(array $sets) { @@ -37,7 +37,7 @@ public function __construct(array $sets) } /** - * @return RuleSetDescriptionInterface[] + * @return list */ public function getSets(): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php index abf211e954..ad963f6d46 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/ReporterFactory.php @@ -15,7 +15,6 @@ namespace PhpCsFixer\Console\Report\ListSetsReport; use Symfony\Component\Finder\Finder as SymfonyFinder; -use Symfony\Component\Finder\SplFileInfo; /** * @author Boris Gorbylev @@ -25,25 +24,24 @@ final class ReporterFactory { /** - * @var ReporterInterface[] + * @var array */ private array $reporters = []; public function registerBuiltInReporters(): self { - /** @var null|string[] $builtInReporters */ + /** @var null|list $builtInReporters */ static $builtInReporters; if (null === $builtInReporters) { $builtInReporters = []; - /** @var SplFileInfo $file */ foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) { $relativeNamespace = $file->getRelativePath(); - $builtInReporters[] = sprintf( - '%s\\%s%s', + $builtInReporters[] = \sprintf( + '%s\%s%s', __NAMESPACE__, - $relativeNamespace ? $relativeNamespace.'\\' : '', + '' !== $relativeNamespace ? $relativeNamespace.'\\' : '', $file->getBasename('.php') ); } @@ -61,7 +59,7 @@ public function registerReporter(ReporterInterface $reporter): self $format = $reporter->getFormat(); if (isset($this->reporters[$format])) { - throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); + throw new \UnexpectedValueException(\sprintf('Reporter for format "%s" is already registered.', $format)); } $this->reporters[$format] = $reporter; @@ -70,7 +68,7 @@ public function registerReporter(ReporterInterface $reporter): self } /** - * @return string[] + * @return list */ public function getFormats(): array { @@ -83,7 +81,7 @@ public function getFormats(): array public function getReporter(string $format): ReporterInterface { if (!isset($this->reporters[$format])) { - throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); + throw new \UnexpectedValueException(\sprintf('Reporter for format "%s" is not registered.', $format)); } return $this->reporters[$format]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php index 0caddef57f..65c4156eab 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/Report/ListSetsReport/TextReporter.php @@ -23,29 +23,21 @@ */ final class TextReporter implements ReporterInterface { - /** - * {@inheritdoc} - */ public function getFormat(): string { return 'txt'; } - /** - * {@inheritdoc} - */ public function generate(ReportSummary $reportSummary): string { $sets = $reportSummary->getSets(); - usort($sets, static function (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int { - return strcmp($a->getName(), $b->getName()); - }); + usort($sets, static fn (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b): int => $a->getName() <=> $b->getName()); $output = ''; foreach ($sets as $i => $set) { - $output .= sprintf('%2d) %s', $i + 1, $set->getName()).PHP_EOL.' '.$set->getDescription().PHP_EOL; + $output .= \sprintf('%2d) %s', $i + 1, $set->getName()).PHP_EOL.' '.$set->getDescription().PHP_EOL; if ($set->isRisky()) { $output .= ' Set contains risky rules.'.PHP_EOL; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php index 3da780fe4e..b32113049f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClient.php @@ -19,36 +19,44 @@ */ final class GithubClient implements GithubClientInterface { - /** - * {@inheritdoc} - */ + private string $url = 'https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/tags'; + public function getTags(): array { - $url = 'https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/tags'; - $result = @file_get_contents( - $url, + $this->url, false, stream_context_create([ 'http' => [ - 'header' => 'User-Agent: FriendsOfPHP/PHP-CS-Fixer', + 'header' => 'User-Agent: PHP-CS-Fixer/PHP-CS-Fixer', ], ]) ); if (false === $result) { - throw new \RuntimeException(sprintf('Failed to load tags at "%s".', $url)); + throw new \RuntimeException(\sprintf('Failed to load tags at "%s".', $this->url)); } + /** + * @var list + */ $result = json_decode($result, true); if (JSON_ERROR_NONE !== json_last_error()) { - throw new \RuntimeException(sprintf( + throw new \RuntimeException(\sprintf( 'Failed to read response from "%s" as JSON: %s.', - $url, + $this->url, json_last_error_msg() )); } - return $result; + return array_map( + static fn (array $tagData): string => $tagData['name'], + $result + ); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php index 25e02a6cbb..27728327fc 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/GithubClientInterface.php @@ -19,5 +19,8 @@ */ interface GithubClientInterface { + /** + * @return list + */ public function getTags(): array; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php index 54ec34ddbe..fc28677cfe 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/SelfUpdate/NewVersionChecker.php @@ -28,7 +28,7 @@ final class NewVersionChecker implements NewVersionCheckerInterface private VersionParser $versionParser; /** - * @var null|string[] + * @var null|list */ private $availableVersions; @@ -38,9 +38,6 @@ public function __construct(GithubClientInterface $githubClient) $this->versionParser = new VersionParser(); } - /** - * {@inheritdoc} - */ public function getLatestVersion(): string { $this->retrieveAvailableVersions(); @@ -48,9 +45,6 @@ public function getLatestVersion(): string return $this->availableVersions[0]; } - /** - * {@inheritdoc} - */ public function getLatestVersionOfMajor(int $majorVersion): ?string { $this->retrieveAvailableVersions(); @@ -66,9 +60,6 @@ public function getLatestVersionOfMajor(int $majorVersion): ?string return null; } - /** - * {@inheritdoc} - */ public function compareVersions(string $versionA, string $versionB): int { $versionA = $this->versionParser->normalize($versionA); @@ -91,9 +82,7 @@ private function retrieveAvailableVersions(): void return; } - foreach ($this->githubClient->getTags() as $tag) { - $version = $tag['name']; - + foreach ($this->githubClient->getTags() as $version) { try { $this->versionParser->normalize($version); @@ -105,6 +94,9 @@ private function retrieveAvailableVersions(): void } } - $this->availableVersions = Semver::rsort($this->availableVersions); + $versions = Semver::rsort($this->availableVersions); + \assert(array_is_list($versions)); // Semver::rsort provides soft `array` type, let's validate and ensure proper type for SCA + + $this->availableVersions = $versions; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php b/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php index 85ac917934..7465c2a477 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Console/WarningsDetector.php @@ -27,7 +27,7 @@ final class WarningsDetector private ToolInfoInterface $toolInfo; /** - * @var string[] + * @var list */ private array $warnings = []; @@ -42,7 +42,7 @@ public function detectOldMajor(): void // $currentMajorVersion = \intval(explode('.', Application::VERSION)[0], 10); // $nextMajorVersion = $currentMajorVersion + 1; // $this->warnings[] = "You are running PHP CS Fixer v{$currentMajorVersion}, which is not maintained anymore. Please update to v{$nextMajorVersion}."; - // $this->warnings[] = "You may find an UPGRADE guide at https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/v{$nextMajorVersion}.0.0/UPGRADE-v{$nextMajorVersion}.md ."; + // $this->warnings[] = "You may find an UPGRADE guide at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v{$nextMajorVersion}.0.0/UPGRADE-v{$nextMajorVersion}.md ."; } public function detectOldVendor(): void @@ -50,7 +50,7 @@ public function detectOldVendor(): void if ($this->toolInfo->isInstalledByComposer()) { $details = $this->toolInfo->getComposerInstallationDetails(); if (ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME === $details['name']) { - $this->warnings[] = sprintf( + $this->warnings[] = \sprintf( 'You are running PHP CS Fixer installed with old vendor `%s`. Please update to `%s`.', ToolInfo::COMPOSER_LEGACY_PACKAGE_NAME, ToolInfo::COMPOSER_PACKAGE_NAME @@ -60,7 +60,7 @@ public function detectOldVendor(): void } /** - * @return string[] + * @return list */ public function getWarnings(): array { @@ -68,9 +68,9 @@ public function getWarnings(): array return []; } - return array_unique(array_merge( + return array_values(array_unique(array_merge( $this->warnings, - ['If you need help while solving warnings, ask at https://gitter.im/PHP-CS-Fixer, we will help you!'] - )); + ['If you need help while solving warnings, ask at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/, we will help you!'] + ))); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php index f52ed44a74..97e8f747f9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/DiffConsoleFormatter.php @@ -40,10 +40,9 @@ public function format(string $diff, string $lineTemplate = '%s'): string $template = $isDecorated ? $this->template - : Preg::replace('/<[^<>]+>/', '', $this->template) - ; + : Preg::replace('/<[^<>]+>/', '', $this->template); - return sprintf( + return \sprintf( $template, implode( PHP_EOL, @@ -62,7 +61,7 @@ static function (array $matches): string { $colour = 'cyan'; } - return sprintf('%s', $colour, OutputFormatter::escape($matches[0]), $colour); + return \sprintf('%s', $colour, OutputFormatter::escape($matches[0]), $colour); }, $line, 1, @@ -74,7 +73,7 @@ static function (array $matches): string { } } - return sprintf($lineTemplate, $line); + return \sprintf($lineTemplate, $line); }, Preg::split('#\R#u', $diff) ) diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php index ee5d738fe4..a927ede40d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/FullDiffer.php @@ -14,8 +14,8 @@ namespace PhpCsFixer\Differ; -use PhpCsFixer\Diff\Differ; -use PhpCsFixer\Diff\Output\StrictUnifiedDiffOutputBuilder; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder; /** * @author Dariusz Rumiński @@ -37,9 +37,6 @@ public function __construct() ])); } - /** - * {@inheritdoc} - */ public function diff(string $old, string $new, ?\SplFileInfo $file = null): string { return $this->differ->diff($old, $new); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php index 8ef968ee68..cf07f9e744 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/NullDiffer.php @@ -19,9 +19,6 @@ */ final class NullDiffer implements DifferInterface { - /** - * {@inheritdoc} - */ public function diff(string $old, string $new, ?\SplFileInfo $file = null): string { return ''; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php b/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php index fa2233b313..36663aa19f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Differ/UnifiedDiffer.php @@ -14,15 +14,12 @@ namespace PhpCsFixer\Differ; -use PhpCsFixer\Diff\Differ; -use PhpCsFixer\Diff\Output\StrictUnifiedDiffOutputBuilder; use PhpCsFixer\Preg; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder; final class UnifiedDiffer implements DifferInterface { - /** - * {@inheritdoc} - */ public function diff(string $old, string $new, ?\SplFileInfo $file = null): string { if (null === $file) { @@ -33,7 +30,7 @@ public function diff(string $old, string $new, ?\SplFileInfo $file = null): stri } else { $filePath = $file->getRealPath(); - if (1 === Preg::match('/\s/', $filePath)) { + if (Preg::match('/\s/', $filePath)) { $filePath = '"'.$filePath.'"'; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php index aae73e3c45..d3f09830f2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Annotation.php @@ -29,7 +29,7 @@ final class Annotation /** * All the annotation tag names with types. * - * @var string[] + * @var list */ private static array $tags = [ 'method', @@ -46,9 +46,9 @@ final class Annotation /** * The lines that make up the annotation. * - * @var Line[] + * @var array */ - private $lines; + private array $lines; /** * The position of the first line of the annotation in the docblock. @@ -81,7 +81,7 @@ final class Annotation /** * The cached types. * - * @var null|string[] + * @var null|list */ private $types; @@ -91,16 +91,16 @@ final class Annotation private $namespace; /** - * @var NamespaceUseAnalysis[] + * @var list */ private array $namespaceUses; /** * Create a new line instance. * - * @param Line[] $lines - * @param null|NamespaceAnalysis $namespace - * @param NamespaceUseAnalysis[] $namespaceUses + * @param array $lines + * @param null|NamespaceAnalysis $namespace + * @param list $namespaceUses */ public function __construct(array $lines, $namespace = null, array $namespaceUses = []) { @@ -108,10 +108,8 @@ public function __construct(array $lines, $namespace = null, array $namespaceUse $this->namespace = $namespace; $this->namespaceUses = $namespaceUses; - $keys = array_keys($lines); - - $this->start = $keys[0]; - $this->end = end($keys); + $this->start = array_key_first($lines); + $this->end = array_key_last($lines); } /** @@ -125,7 +123,7 @@ public function __toString(): string /** * Get all the annotation tag names with types. * - * @return string[] + * @return list */ public static function getTagsWithTypes(): array { @@ -163,20 +161,27 @@ public function getTag(): Tag /** * @internal */ - public function getTypeExpression(): TypeExpression + public function getTypeExpression(): ?TypeExpression { - return new TypeExpression($this->getTypesContent(), $this->namespace, $this->namespaceUses); + $typesContent = $this->getTypesContent(); + + return null === $typesContent + ? null + : new TypeExpression($typesContent, $this->namespace, $this->namespaceUses); } /** - * @return null|string - * * @internal */ - public function getVariableName() + public function getVariableName(): ?string { - $type = preg_quote($this->getTypesContent(), '/'); - $regex = "/@{$this->tag->getName()}\\s+({$type}\\s*)?(&\\s*)?(\\.{3}\\s*)?(?\\$.+?)(?:[\\s*]|$)/"; + $type = preg_quote($this->getTypesContent() ?? '', '/'); + $regex = \sprintf( + '/@%s\s+(%s\s*)?(&\s*)?(\.{3}\s*)?(?\$%s)(?:.*|$)/', + $this->tag->getName(), + $type, + TypeExpression::REGEX_IDENTIFIER + ); if (Preg::match($regex, $this->lines[0]->getContent(), $matches)) { return $matches['variable']; @@ -188,12 +193,15 @@ public function getVariableName() /** * Get the types associated with this annotation. * - * @return string[] + * @return list */ public function getTypes(): array { if (null === $this->types) { - $this->types = $this->getTypeExpression()->getTypes(); + $typeExpression = $this->getTypeExpression(); + $this->types = null === $typeExpression + ? [] + : $typeExpression->getTypes(); } return $this->types; @@ -202,7 +210,7 @@ public function getTypes(): array /** * Set the types associated with this annotation. * - * @param string[] $types + * @param list $types */ public function setTypes(array $types): void { @@ -216,13 +224,11 @@ public function setTypes(array $types): void /** * Get the normalized types associated with this annotation, so they can easily be compared. * - * @return string[] + * @return list */ public function getNormalizedTypes(): array { - $normalized = array_map(static function (string $type): string { - return strtolower($type); - }, $this->getTypes()); + $normalized = array_map(static fn (string $type): string => strtolower($type), $this->getTypes()); sort($normalized); @@ -275,7 +281,7 @@ public function supportTypes(): bool * * Be careful modifying the underlying line as that won't flush the cache. */ - private function getTypesContent(): string + private function getTypesContent(): ?string { if (null === $this->typesContent) { $name = $this->getTag()->getName(); @@ -285,14 +291,14 @@ private function getTypesContent(): string } $matchingResult = Preg::match( - '{^(?:\s*\*|/\*\*)\s*@'.$name.'\s+'.TypeExpression::REGEX_TYPES.'(?:(?:[*\h\v]|\&[\.\$]).*)?\r?$}isx', + '{^(?:\h*\*|/\*\*)[\h*]*@'.$name.'\h+'.TypeExpression::REGEX_TYPES.'(?:(?:[*\h\v]|\&?[\.\$]).*)?\r?$}is', $this->lines[0]->getContent(), $matches ); - $this->typesContent = 1 === $matchingResult + $this->typesContent = $matchingResult ? $matches['types'] - : ''; + : null; } return $this->typesContent; diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php index efdcff32da..b4ae53e222 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/DocBlock.php @@ -28,29 +28,25 @@ final class DocBlock { /** - * The array of lines. - * - * @var Line[] + * @var list */ private array $lines = []; /** - * The array of annotations. - * - * @var null|Annotation[] + * @var null|list */ - private $annotations; + private ?array $annotations = null; - /** - * @var null|NamespaceAnalysis - */ - private $namespace; + private ?NamespaceAnalysis $namespace; /** - * @var NamespaceUseAnalysis[] + * @var list */ private array $namespaceUses; + /** + * @param list $namespaceUses + */ public function __construct(string $content, ?NamespaceAnalysis $namespace = null, array $namespaceUses = []) { foreach (Preg::split('/([^\n\r]+\R*)/', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $line) { @@ -61,9 +57,6 @@ public function __construct(string $content, ?NamespaceAnalysis $namespace = nul $this->namespaceUses = $namespaceUses; } - /** - * Get the string representation of object. - */ public function __toString(): string { return $this->getContent(); @@ -72,7 +65,7 @@ public function __toString(): string /** * Get this docblock's lines. * - * @return Line[] + * @return list */ public function getLines(): array { @@ -90,7 +83,7 @@ public function getLine(int $pos): ?Line /** * Get this docblock's annotations. * - * @return Annotation[] + * @return list */ public function getAnnotations(): array { @@ -159,9 +152,7 @@ public function makeSingleLine(): void $usefulLines = array_filter( $this->lines, - static function (Line $line): bool { - return $line->containsUsefulContent(); - } + static fn (Line $line): bool => $line->containsUsefulContent() ); if (1 < \count($usefulLines)) { @@ -186,23 +177,20 @@ public function getAnnotation(int $pos): ?Annotation /** * Get specific types of annotations only. * - * If none exist, we're returning an empty array. + * @param list|string $types * - * @param string|string[] $types - * - * @return Annotation[] + * @return list */ public function getAnnotationsOfType($types): array { + $typesToSearchFor = (array) $types; + $annotations = []; - $types = (array) $types; foreach ($this->getAnnotations() as $annotation) { - $tag = $annotation->getTag()->getName(); - foreach ($types as $type) { - if ($type === $tag) { - $annotations[] = $annotation; - } + $tagName = $annotation->getTag()->getName(); + if (\in_array($tagName, $typesToSearchFor, true)) { + $annotations[] = $annotation; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php index 0db50e8284..9ef4a08740 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Line.php @@ -59,7 +59,7 @@ public function getContent(): string */ public function containsUsefulContent(): bool { - return 0 !== Preg::match('/\\*\s*\S+/', $this->content) && '' !== trim(str_replace(['/', '*'], ' ', $this->content)); + return Preg::match('/\*\s*\S+/', $this->content) && '' !== trim(str_replace(['/', '*'], ' ', $this->content)); } /** @@ -69,7 +69,7 @@ public function containsUsefulContent(): bool */ public function containsATag(): bool { - return 0 !== Preg::match('/\\*\s*@/', $this->content); + return Preg::match('/\*\s*@/', $this->content); } /** @@ -119,7 +119,7 @@ public function addBlank(): void { $matched = Preg::match('/^(\h*\*)[^\r\n]*(\r?\n)$/', $this->content, $matches); - if (1 !== $matched) { + if (!$matched) { return; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php index 988c19b713..6206718dc5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/Tag.php @@ -20,15 +20,14 @@ * This represents a tag, as defined by the proposed PSR PHPDoc standard. * * @author Graham Campbell + * @author Jakub Kwaśniewski */ final class Tag { /** * All the tags defined by the proposed PSR PHPDoc standard. - * - * @var string[] */ - private static array $tags = [ + public const PSR_STANDARD_TAGS = [ 'api', 'author', 'category', 'copyright', 'deprecated', 'example', 'global', 'internal', 'license', 'link', 'method', 'package', 'param', 'property', 'property-read', 'property-write', 'return', 'see', @@ -42,10 +41,8 @@ final class Tag /** * The cached tag name. - * - * @var null|string */ - private $name; + private ?string $name = null; /** * Create a new tag instance. @@ -100,6 +97,6 @@ public function setName(string $name): void */ public function valid(): bool { - return \in_array($this->getName(), self::$tags, true); + return \in_array($this->getName(), self::PSR_STANDARD_TAGS, true); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php index 2f41451981..c5a3a89631 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TagComparator.php @@ -19,13 +19,20 @@ * together, or kept apart. * * @author Graham Campbell + * @author Jakub Kwaśniewski + * + * @deprecated */ final class TagComparator { /** * Groups of tags that should be allowed to immediately follow each other. + * + * @var list> + * + * @internal */ - private static array $groups = [ + public const DEFAULT_GROUPS = [ ['deprecated', 'link', 'see', 'since'], ['author', 'copyright', 'license'], ['category', 'package', 'subpackage'], @@ -34,9 +41,13 @@ final class TagComparator /** * Should the given tags be kept together, or kept apart? + * + * @param list> $groups */ - public static function shouldBeTogether(Tag $first, Tag $second): bool + public static function shouldBeTogether(Tag $first, Tag $second, array $groups = self::DEFAULT_GROUPS): bool { + @trigger_error('Method '.__METHOD__.' is deprecated and will be removed in version 4.0.', E_USER_DEPRECATED); + $firstName = $first->getName(); $secondName = $second->getName(); @@ -44,7 +55,7 @@ public static function shouldBeTogether(Tag $first, Tag $second): bool return true; } - foreach (self::$groups as $group) { + foreach ($groups as $group) { if (\in_array($firstName, $group, true) && \in_array($secondName, $group, true)) { return true; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php index 02e1ffba78..4836fbca75 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php +++ b/vendor/friendsofphp/php-cs-fixer/src/DocBlock/TypeExpression.php @@ -20,117 +20,212 @@ use PhpCsFixer\Utils; /** + * @author Michael Vorisek + * * @internal */ final class TypeExpression { /** - * Regex to match any types, shall be used with `x` modifier. + * Regex to match any PHP identifier. + * + * @internal + */ + public const REGEX_IDENTIFIER = '(?:(?!(? # several types separated by `|` or `&` - (? # single type - (?\??) + public const REGEX_TYPES = '(?(?x) # one or several types separated by `|` or `&` +'.self::REGEX_TYPE.' + (?: + \h*(?[|&])\h* + (?&type) + )*+ + )'; + + /** + * Based on: + * - https://github.com/phpstan/phpdoc-parser/blob/1.26.0/doc/grammars/type.abnf fuzzing grammar + * - and https://github.com/phpstan/phpdoc-parser/blob/1.26.0/src/Parser/PhpDocParser.php parser impl. + */ + private const REGEX_TYPE = '(?(?x) # single type + (?\??\h*) (?: - (? - (?array\h*\{) - (? - (? - \h*[^?:\h]+\h*\??\h*:\h*(?&types) - ) - (?:\h*,(?&object_like_array_key))* + (? + (?(?i)(?:array|list|object)(?-i)\h*\{\h*) + (? + (? + (?(?:(?&constant)|(?&identifier)|(?&name))\h*\??\h*:\h*|) + (?(?&types_inner)) ) + (?: + \h*,\h* + (?&array_shape_inner) + )*+ + (?:\h*,\h*)? + |) \h*\} ) | - (? # callable syntax, e.g. `callable(string): bool` - (?(?:callable|Closure)\h*\(\h*) - (? - (?&types) + (? # callable syntax, e.g. `callable(string, int...): bool`, `\Closure(T, int): T` + (?(?&name)) + (? + (?\h*<\h*) + (? + (? + (? + (?&identifier) + ) + (? # template bound + \h+(?i)(?of|as)(?-i)\h+ + (?(?&types_inner)) + |) + (? # template default + \h*=\h* + (?(?&types_inner)) + |) + ) (?: \h*,\h* - (?&types) - )* - )? + (?&callable_template_inner) + )*+ + ) + \h*> + (?=\h*\() + |) + (?\h*\(\h*) + (? + (? + (?(?&types_inner)) + (?\h*&|) + (?\h*\.\.\.|) + (?\h*\$(?&identifier)|) + (?\h*=|) + ) + (?: + \h*,\h* + (?&callable_argument) + )*+ + (?:\h*,\h*)? + |) \h*\) (?: \h*\:\h* - (?(?&types)) + (?(?&type)) )? ) | (? # generic syntax, e.g.: `array` - (? - (?&name)+ - \h*<\h* + (?(?&name)\h*<\h*) + (? + (?&types_inner) + (?: + \h*,\h* + (?&types_inner) + )*+ + (?:\h*,\h*)? ) - (? - (?&types) - (?: - \h*,\h* - (?&types) - )* - ) \h*> ) | (? # class constants with optional wildcard, e.g.: `Foo::*`, `Foo::CONST_A`, `FOO::CONST_*` - (?&name)::(\*|\w+\*?) + (?&name)::\*?(?:(?&identifier)\*?)* ) | - (? # array expression, e.g.: `string[]`, `string[][]` - (?&name)(\[\])+ + (? # single constant value (case insensitive), e.g.: 1, -1.8E+6, `\'a\'` + (?i) + # all sorts of numbers: with or without sign, supports literal separator and several numeric systems, + # e.g.: 1, +1.1, 1., .1, -1, 123E+8, 123_456_789, 0x7Fb4, 0b0110, 0o777 + [+-]?(?: + (?:0b[01]++(?:_[01]++)*+) + | (?:0o[0-7]++(?:_[0-7]++)*+) + | (?:0x[\da-f]++(?:_[\da-f]++)*+) + | (?:(?\d++(?:_\d++)*+)|(?=\.\d)) + (?:\.(?&constant_digits)|(?<=\d)\.)?+ + (?:e[+-]?(?&constant_digits))?+ + ) + | \'(?:[^\'\\\]|\\\.)*+\' + | "(?:[^"\\\]|\\\.)*+" + (?-i) ) | - (? # single constant value (case insensitive), e.g.: 1, `\'a\'` + (? # self reference, e.g.: $this, $self, @static (?i) - null | true | false - | [\d.]+ - | \'[^\']+?\' | "[^"]+?" - | [@$]?(?:this | self | static) + [@$](?:this | self | static) (?-i) ) | - (? # single type, e.g.: `null`, `int`, `\Foo\Bar` - [\\\\\w-]++ + (? # full name, e.g.: `int`, `\DateTime`, `\Foo\Bar`, `positive-int` + \\\?+ + (?'.self::REGEX_IDENTIFIER.') + (?:[\\\\\-](?&identifier))*+ + ) + | + (? # parenthesized type, e.g.: `(int)`, `(int|\stdClass)` + (? + \(\h* + ) + (?: + (? + (?&types_inner) + ) + | + (? # conditional type, e.g.: `$foo is \Throwable ? false : $foo` + (? + (?:\$(?&identifier)) + | + (?(?&types_inner)) + ) + (? + \h+(?i)is(?:\h+not)?(?-i)\h+ + ) + (?(?&types_inner)) + (?\h*\?\h*) + (?(?&types_inner)) + (?\h*:\h*) + (?(?&types_inner)) + ) + ) + \h*\) ) ) - ) - (?: - \h*(?[|&])\h* - (?&type) - )* - ) - '; + (? # array, e.g.: `string[]`, `array[][]` + (\h*\[\h*\])* + ) + (?:(?=1)0 + (?(?> + (?&type) + (?: + \h*[|&]\h* + (?&type) + )*+ + )) + |) + )'; - /** - * @var string - */ - private $value; + private string $value; private bool $isUnionType = false; + private string $typesGlue = '|'; + /** * @var list */ private array $innerTypeExpressions = []; - private string $typesGlue = '|'; - - /** - * @var null|NamespaceAnalysis - */ - private $namespace; + private ?NamespaceAnalysis $namespace; /** - * @var NamespaceUseAnalysis[] + * @var list */ - private $namespaceUses; + private array $namespaceUses; /** - * @param NamespaceUseAnalysis[] $namespaceUses + * @param list $namespaceUses */ public function __construct(string $value, ?NamespaceAnalysis $namespace, array $namespaceUses) { @@ -147,55 +242,84 @@ public function toString(): string } /** - * @return string[] + * @return list */ public function getTypes(): array { if ($this->isUnionType) { return array_map( - static function (array $type) { return $type['expression']->toString(); }, - $this->innerTypeExpressions + static fn (array $type) => $type['expression']->toString(), + $this->innerTypeExpressions, ); } return [$this->value]; } + public function isUnionType(): bool + { + return $this->isUnionType; + } + + public function getTypesGlue(): string + { + return $this->typesGlue; + } + /** - * @param callable(self $a, self $b): int $compareCallback + * @param \Closure(self): void $callback */ - public function sortTypes(callable $compareCallback): void + public function walkTypes(\Closure $callback): void { - foreach (array_reverse($this->innerTypeExpressions) as [ - 'start_index' => $startIndex, + $innerValueOrig = $this->value; + + $startIndexOffset = 0; + + foreach ($this->innerTypeExpressions as [ + 'start_index' => $startIndexOrig, 'expression' => $inner, ]) { - $initialValueLength = \strlen($inner->toString()); + $innerLengthOrig = \strlen($inner->toString()); - $inner->sortTypes($compareCallback); + $inner->walkTypes($callback); $this->value = substr_replace( $this->value, $inner->toString(), - $startIndex, - $initialValueLength + $startIndexOrig + $startIndexOffset, + $innerLengthOrig ); + + $startIndexOffset += \strlen($inner->toString()) - $innerLengthOrig; } - if ($this->isUnionType) { - $this->innerTypeExpressions = Utils::stableSort( - $this->innerTypeExpressions, - static function (array $type): self { return $type['expression']; }, - $compareCallback - ); + $callback($this); + + if ($this->value !== $innerValueOrig) { + $this->isUnionType = false; + $this->typesGlue = '|'; + $this->innerTypeExpressions = []; - $this->value = implode($this->getTypesGlue(), $this->getTypes()); + $this->parse(); } } - public function getTypesGlue(): string + /** + * @param \Closure(self, self): (-1|0|1) $compareCallback + */ + public function sortTypes(\Closure $compareCallback): void { - return $this->typesGlue; + $this->walkTypes(static function (self $type) use ($compareCallback): void { + if ($type->isUnionType) { + $type->innerTypeExpressions = Utils::stableSort( + $type->innerTypeExpressions, + static fn (array $type): self => $type['expression'], + $compareCallback, + ); + + $type->value = implode($type->getTypesGlue(), $type->getTypes()); + } + }); } public function getCommonType(): ?string @@ -209,14 +333,20 @@ public function getCommonType(): ?string continue; } - if (isset($aliases[$type])) { - $type = $aliases[$type]; - } elseif (1 === Preg::match('/\[\]$/', $type)) { + if (str_starts_with($type, '?')) { + $type = substr($type, 1); + } + + if (Preg::match('/\[\h*\]$/', $type)) { $type = 'array'; - } elseif (1 === Preg::match('/^(.+?)getTypes() as $type) { - if (\in_array($type, ['null', 'mixed'], true)) { + if (\in_array($type, ['null', 'mixed'], true) || str_starts_with($type, '?')) { return true; } } @@ -246,135 +376,243 @@ public function allowsNull(): bool private function parse(): void { - $value = $this->value; + $index = 0; + while (true) { + Preg::match( + '{\G'.self::REGEX_TYPE.'(?:\h*(?[|&])\h*|$)}', + $this->value, + $matches, + PREG_OFFSET_CAPTURE, + $index + ); - Preg::match( - '{^'.self::REGEX_TYPES.'$}x', - $value, - $matches - ); + if ([] === $matches) { + throw new \Exception('Unable to parse phpdoc type '.var_export($this->value, true)); + } + + if (!$this->isUnionType) { + if (($matches['glue'][0] ?? '') === '') { + break; + } + + $this->isUnionType = true; + $this->typesGlue = $matches['glue'][0]; + } - if ([] === $matches) { - return; + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['type'][0]), + ]; + + $consumedValueLength = \strlen($matches[0][0]); + $index += $consumedValueLength; + + if (\strlen($this->value) <= $index) { + \assert(\strlen($this->value) === $index); + + return; + } } - $this->typesGlue = $matches['glue'] ?? $this->typesGlue; + $nullableLength = \strlen($matches['nullable'][0]); + $index = $nullableLength; - $index = '' !== $matches['nullable'] ? 1 : 0; + if ('' !== ($matches['generic'][0] ?? '') && $matches['generic'][1] === $nullableLength) { + $this->parseCommaSeparatedInnerTypes( + $index + \strlen($matches['generic_start'][0]), + $matches['generic_types'][0] + ); + } elseif ('' !== ($matches['callable'][0] ?? '') && $matches['callable'][1] === $nullableLength) { + $this->parseCallableTemplateInnerTypes( + $index + \strlen($matches['callable_name'][0]) + + \strlen($matches['callable_template_start'][0]), + $matches['callable_template_inners'][0] + ); - if ($matches['type'] !== $matches['types']) { - $this->isUnionType = true; + $this->parseCallableArgumentTypes( + $index + \strlen($matches['callable_name'][0]) + + \strlen($matches['callable_template'][0]) + + \strlen($matches['callable_start'][0]), + $matches['callable_arguments'][0] + ); - while (true) { - $innerType = $matches['type']; + if ('' !== ($matches['callable_return'][0] ?? '')) { + $this->innerTypeExpressions[] = [ + 'start_index' => \strlen($this->value) - \strlen($matches['callable_return'][0]), + 'expression' => $this->inner($matches['callable_return'][0]), + ]; + } + } elseif ('' !== ($matches['array_shape'][0] ?? '') && $matches['array_shape'][1] === $nullableLength) { + $this->parseArrayShapeInnerTypes( + $index + \strlen($matches['array_shape_start'][0]), + $matches['array_shape_inners'][0] + ); + } elseif ('' !== ($matches['parenthesized'][0] ?? '') && $matches['parenthesized'][1] === $nullableLength) { + $index += \strlen($matches['parenthesized_start'][0]); + + if ('' !== ($matches['conditional'][0] ?? '')) { + if ('' !== ($matches['conditional_cond_left_types'][0] ?? '')) { + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_cond_left_types'][0]), + ]; + } - $newValue = Preg::replace( - '/^'.preg_quote($innerType, '/').'(\h*[|&]\h*)?/', - '', - $value - ); + $index += \strlen($matches['conditional_cond_left'][0]) + \strlen($matches['conditional_cond_middle'][0]); $this->innerTypeExpressions[] = [ 'start_index' => $index, - 'expression' => $this->inner($innerType), + 'expression' => $this->inner($matches['conditional_cond_right_types'][0]), ]; - if ('' === $newValue) { - return; - } + $index += \strlen($matches['conditional_cond_right_types'][0]) + \strlen($matches['conditional_true_start'][0]); + + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_true_types'][0]), + ]; - $index += \strlen($value) - \strlen($newValue); - $value = $newValue; + $index += \strlen($matches['conditional_true_types'][0]) + \strlen($matches['conditional_false_start'][0]); - Preg::match( - '{^'.self::REGEX_TYPES.'$}x', - $value, - $matches - ); + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['conditional_false_types'][0]), + ]; + } else { + $this->innerTypeExpressions[] = [ + 'start_index' => $index, + 'expression' => $this->inner($matches['parenthesized_types'][0]), + ]; } } + } - if ('' !== ($matches['generic'] ?? '')) { - $this->parseCommaSeparatedInnerTypes( - $index + \strlen($matches['generic_start']), - $matches['generic_types'] + private function parseCommaSeparatedInnerTypes(int $startIndex, string $value): void + { + $index = 0; + while (\strlen($value) !== $index) { + Preg::match( + '{\G'.self::REGEX_TYPES.'(?:\h*,\h*|$)}', + $value, + $matches, + 0, + $index ); - return; + $this->innerTypeExpressions[] = [ + 'start_index' => $startIndex + $index, + 'expression' => $this->inner($matches['types']), + ]; + + $index += \strlen($matches[0]); } + } - if ('' !== ($matches['callable'] ?? '')) { - $this->parseCommaSeparatedInnerTypes( - $index + \strlen($matches['callable_start']), - $matches['callable_arguments'] ?? '' + private function parseCallableTemplateInnerTypes(int $startIndex, string $value): void + { + $index = 0; + while (\strlen($value) !== $index) { + Preg::match( + '{\G(?:(?=1)0'.self::REGEX_TYPES.'|(?<_callable_template_inner>(?&callable_template_inner))(?:\h*,\h*|$))}', + $value, + $prematches, + 0, + $index + ); + $consumedValue = $prematches['_callable_template_inner']; + $consumedValueLength = \strlen($consumedValue); + $consumedCommaLength = \strlen($prematches[0]) - $consumedValueLength; + + $addedPrefix = 'Closure<'; + Preg::match( + '{^'.self::REGEX_TYPES.'$}', + $addedPrefix.$consumedValue.'>(): void', + $matches, + PREG_OFFSET_CAPTURE ); - $return = $matches['callable_return'] ?? null; - if (null !== $return) { + if ('' !== $matches['callable_template_inner_b'][0]) { $this->innerTypeExpressions[] = [ - 'start_index' => \strlen($this->value) - \strlen($matches['callable_return']), - 'expression' => $this->inner($matches['callable_return']), + 'start_index' => $startIndex + $index + $matches['callable_template_inner_b_types'][1] + - \strlen($addedPrefix), + 'expression' => $this->inner($matches['callable_template_inner_b_types'][0]), ]; } - return; - } + if ('' !== $matches['callable_template_inner_d'][0]) { + $this->innerTypeExpressions[] = [ + 'start_index' => $startIndex + $index + $matches['callable_template_inner_d_types'][1] + - \strlen($addedPrefix), + 'expression' => $this->inner($matches['callable_template_inner_d_types'][0]), + ]; + } - if ('' !== ($matches['object_like_array'] ?? '')) { - $this->parseObjectLikeArrayKeys( - $index + \strlen($matches['object_like_array_start']), - $matches['object_like_array_keys'] - ); + $index += $consumedValueLength + $consumedCommaLength; } } - private function parseCommaSeparatedInnerTypes(int $startIndex, string $value): void + private function parseCallableArgumentTypes(int $startIndex, string $value): void { - while ('' !== $value) { + $index = 0; + while (\strlen($value) !== $index) { Preg::match( - '{^'.self::REGEX_TYPES.'\h*(?:,|$)}x', + '{\G(?:(?=1)0'.self::REGEX_TYPES.'|(?<_callable_argument>(?&callable_argument))(?:\h*,\h*|$))}', $value, - $matches + $prematches, + 0, + $index + ); + $consumedValue = $prematches['_callable_argument']; + $consumedValueLength = \strlen($consumedValue); + $consumedCommaLength = \strlen($prematches[0]) - $consumedValueLength; + + $addedPrefix = 'Closure('; + Preg::match( + '{^'.self::REGEX_TYPES.'$}', + $addedPrefix.$consumedValue.'): void', + $matches, + PREG_OFFSET_CAPTURE ); $this->innerTypeExpressions[] = [ - 'start_index' => $startIndex, - 'expression' => $this->inner($matches['types']), + 'start_index' => $startIndex + $index, + 'expression' => $this->inner($matches['callable_argument_type'][0]), ]; - $newValue = Preg::replace( - '/^'.preg_quote($matches['types'], '/').'(\h*\,\h*)?/', - '', - $value - ); - - $startIndex += \strlen($value) - \strlen($newValue); - $value = $newValue; + $index += $consumedValueLength + $consumedCommaLength; } } - private function parseObjectLikeArrayKeys(int $startIndex, string $value): void + private function parseArrayShapeInnerTypes(int $startIndex, string $value): void { - while ('' !== $value) { + $index = 0; + while (\strlen($value) !== $index) { Preg::match( - '{(?<_start>^.+?:\h*)'.self::REGEX_TYPES.'\h*(?:,|$)}x', + '{\G(?:(?=1)0'.self::REGEX_TYPES.'|(?<_array_shape_inner>(?&array_shape_inner))(?:\h*,\h*|$))}', $value, - $matches + $prematches, + 0, + $index + ); + $consumedValue = $prematches['_array_shape_inner']; + $consumedValueLength = \strlen($consumedValue); + $consumedCommaLength = \strlen($prematches[0]) - $consumedValueLength; + + $addedPrefix = 'array{'; + Preg::match( + '{^'.self::REGEX_TYPES.'$}', + $addedPrefix.$consumedValue.'}', + $matches, + PREG_OFFSET_CAPTURE ); $this->innerTypeExpressions[] = [ - 'start_index' => $startIndex + \strlen($matches['_start']), - 'expression' => $this->inner($matches['types']), + 'start_index' => $startIndex + $index + $matches['array_shape_inner_value'][1] + - \strlen($addedPrefix), + 'expression' => $this->inner($matches['array_shape_inner_value'][0]), ]; - $newValue = Preg::replace( - '/^.+?:\h*'.preg_quote($matches['types'], '/').'(\h*\,\h*)?/', - '', - $value - ); - - $startIndex += \strlen($value) - \strlen($newValue); - $value = $newValue; + $index += $consumedValueLength + $consumedCommaLength; } } @@ -414,6 +652,7 @@ private function normalize(string $type): string 'array', 'bool', 'callable', + 'false', 'float', 'int', 'iterable', @@ -423,16 +662,17 @@ private function normalize(string $type): string 'object', 'resource', 'string', + 'true', 'void', ], true)) { return $type; } - if (1 === Preg::match('/\[\]$/', $type)) { + if (Preg::match('/\[\]$/', $type)) { return 'array'; } - if (1 === Preg::match('/^(.+?) + * @return array */ private function getAliases(): array { @@ -464,6 +704,7 @@ private function getAliases(): array 'double' => 'float', 'false' => 'bool', 'integer' => 'int', + 'list' => 'array', 'real' => 'float', 'true' => 'bool', ]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/DocLexer.php b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/DocLexer.php new file mode 100644 index 0000000000..2335efb04f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/DocLexer.php @@ -0,0 +1,173 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Doctrine\Annotation; + +use PhpCsFixer\Preg; + +/** + * Copyright (c) 2006-2013 Doctrine Project. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * @internal + */ +final class DocLexer +{ + public const T_NONE = 1; + public const T_INTEGER = 2; + public const T_STRING = 3; + public const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + public const T_IDENTIFIER = 100; + public const T_AT = 101; + public const T_CLOSE_CURLY_BRACES = 102; + public const T_CLOSE_PARENTHESIS = 103; + public const T_COMMA = 104; + public const T_EQUALS = 105; + public const T_FALSE = 106; + public const T_NAMESPACE_SEPARATOR = 107; + public const T_OPEN_CURLY_BRACES = 108; + public const T_OPEN_PARENTHESIS = 109; + public const T_TRUE = 110; + public const T_NULL = 111; + public const T_COLON = 112; + public const T_MINUS = 113; + + /** @var array */ + private array $noCase = [ + '@' => self::T_AT, + ',' => self::T_COMMA, + '(' => self::T_OPEN_PARENTHESIS, + ')' => self::T_CLOSE_PARENTHESIS, + '{' => self::T_OPEN_CURLY_BRACES, + '}' => self::T_CLOSE_CURLY_BRACES, + '=' => self::T_EQUALS, + ':' => self::T_COLON, + '-' => self::T_MINUS, + '\\' => self::T_NAMESPACE_SEPARATOR, + ]; + + /** @var list */ + private array $tokens = []; + + private int $position = 0; + + private int $peek = 0; + + private ?string $regex = null; + + public function setInput(string $input): void + { + $this->tokens = []; + $this->reset(); + $this->scan($input); + } + + public function reset(): void + { + $this->peek = 0; + $this->position = 0; + } + + public function peek(): ?Token + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } + + return null; + } + + /** + * @return list + */ + private function getCatchablePatterns(): array + { + return [ + '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:""|[^"])*+"', + ]; + } + + /** + * @return list + */ + private function getNonCatchablePatterns(): array + { + return ['\s+', '\*+', '(.)']; + } + + /** + * @return self::T_* + */ + private function getType(string &$value): int + { + $type = self::T_NONE; + + if ('"' === $value[0]) { + $value = str_replace('""', '"', substr($value, 1, \strlen($value) - 2)); + + return self::T_STRING; + } + + if (isset($this->noCase[$value])) { + return $this->noCase[$value]; + } + + if ('_' === $value[0] || '\\' === $value[0] || !Preg::match('/[^A-Za-z]/', $value[0])) { + return self::T_IDENTIFIER; + } + + if (is_numeric($value)) { + return str_contains($value, '.') || false !== stripos($value, 'e') + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } + + private function scan(string $input): void + { + if (!isset($this->regex)) { + $this->regex = \sprintf( + '/(%s)|%s/%s', + implode(')|(', $this->getCatchablePatterns()), + implode('|', $this->getNonCatchablePatterns()), + 'iu' + ); + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = Preg::split($this->regex, $input, -1, $flags); + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $firstMatch = $match[0]; + $type = $this->getType($firstMatch); + + $this->tokens[] = new Token($type, $firstMatch, (int) $match[1]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php index 4e5a4a5db8..56cb83e47c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Token.php @@ -14,8 +14,6 @@ namespace PhpCsFixer\Doctrine\Annotation; -use Doctrine\Common\Annotations\DocLexer; - /** * A Doctrine annotation token. * @@ -27,14 +25,17 @@ final class Token private string $content; + private int $position; + /** * @param int $type The type * @param string $content The content */ - public function __construct(int $type = DocLexer::T_NONE, string $content = '') + public function __construct(int $type = DocLexer::T_NONE, string $content = '', int $position = 0) { $this->type = $type; $this->content = $content; + $this->position = $position; } public function getType(): int @@ -57,10 +58,15 @@ public function setContent(string $content): void $this->content = $content; } + public function getPosition(): int + { + return $this->position; + } + /** * Returns whether the token type is one of the given types. * - * @param int|int[] $types + * @param int|list $types */ public function isType($types): bool { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php index 3f7a47789f..b73d14a4d7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Doctrine/Annotation/Tokens.php @@ -14,7 +14,6 @@ namespace PhpCsFixer\Doctrine\Annotation; -use Doctrine\Common\Annotations\DocLexer; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token as PhpToken; @@ -28,7 +27,7 @@ final class Tokens extends \SplFixedArray { /** - * @param string[] $ignoredTags + * @param list $ignoredTags * * @throws \InvalidArgumentException */ @@ -59,27 +58,27 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags $nbScannedTokensToUse = 0; $nbScopes = 0; while (null !== $token = $lexer->peek()) { - if (0 === $index && DocLexer::T_AT !== $token['type']) { + if (0 === $index && !$token->isType(DocLexer::T_AT)) { break; } if (1 === $index) { - if (DocLexer::T_IDENTIFIER !== $token['type'] || \in_array($token['value'], $ignoredTags, true)) { + if (!$token->isType(DocLexer::T_IDENTIFIER) || \in_array($token->getContent(), $ignoredTags, true)) { break; } $nbScannedTokensToUse = 2; } - if ($index >= 2 && 0 === $nbScopes && !\in_array($token['type'], [DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS], true)) { + if ($index >= 2 && 0 === $nbScopes && !$token->isType([DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS])) { break; } $scannedTokens[] = $token; - if (DocLexer::T_OPEN_PARENTHESIS === $token['type']) { + if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) { ++$nbScopes; - } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token['type']) { + } elseif ($token->isType(DocLexer::T_CLOSE_PARENTHESIS)) { if (0 === --$nbScopes) { $nbScannedTokensToUse = \count($scannedTokens); @@ -101,12 +100,16 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags } $lastTokenEndIndex = 0; - foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) { - if (DocLexer::T_STRING === $token['type']) { - $token['value'] = '"'.str_replace('"', '""', $token['value']).'"'; - } - - $missingTextLength = $token['position'] - $lastTokenEndIndex; + foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $scannedToken) { + $token = $scannedToken->isType(DocLexer::T_STRING) + ? new Token( + $scannedToken->getType(), + '"'.str_replace('"', '""', $scannedToken->getContent()).'"', + $scannedToken->getPosition() + ) + : $scannedToken; + + $missingTextLength = $token->getPosition() - $lastTokenEndIndex; if ($missingTextLength > 0) { $tokens[] = new Token(DocLexer::T_NONE, substr( $content, @@ -115,11 +118,11 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags )); } - $tokens[] = new Token($token['type'], $token['value']); - $lastTokenEndIndex = $token['position'] + \strlen($token['value']); + $tokens[] = new Token($token->getType(), $token->getContent()); + $lastTokenEndIndex = $token->getPosition() + \strlen($token->getContent()); } - $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + \strlen($token['value']); + $currentPosition = $ignoredTextPosition = $nextAtPosition + $token->getPosition() + \strlen($token->getContent()); } else { $currentPosition = $nextAtPosition + 1; } @@ -135,8 +138,8 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags /** * Create token collection from array. * - * @param Token[] $array the array to import - * @param ?bool $saveIndices save the numeric indices used in the original array, default is yes + * @param array $array the array to import + * @param ?bool $saveIndices save the numeric indices used in the original array, default is yes */ public static function fromArray($array, $saveIndices = null): self { @@ -242,9 +245,8 @@ public function insertAt(int $index, Token $token): void public function offsetSet($index, $token): void { - // @phpstan-ignore-next-line as we type checking here if (null === $token) { - throw new \InvalidArgumentException('Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, "null" given.'); + throw new \InvalidArgumentException('Token must be an instance of PhpCsFixer\Doctrine\Annotation\Token, "null" given.'); } if (!$token instanceof Token) { @@ -254,21 +256,21 @@ public function offsetSet($index, $token): void $type = \get_class($token); } - throw new \InvalidArgumentException(sprintf('Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, "%s" given.', $type)); + throw new \InvalidArgumentException(\sprintf('Token must be an instance of PhpCsFixer\Doctrine\Annotation\Token, "%s" given.', $type)); } parent::offsetSet($index, $token); } /** - * {@inheritdoc} + * @param mixed $index * * @throws \OutOfBoundsException */ public function offsetUnset($index): void { if (!isset($this[$index])) { - throw new \OutOfBoundsException(sprintf('Index "%s" is invalid or does not exist.', $index)); + throw new \OutOfBoundsException(\sprintf('Index "%s" is invalid or does not exist.', $index)); } $max = \count($this) - 1; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php b/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php index 6b9f436839..3769c4fdea 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Documentation/DocumentationLocator.php @@ -23,10 +23,7 @@ */ final class DocumentationLocator { - /** - * @var string - */ - private $path; + private string $path; public function __construct() { @@ -46,10 +43,8 @@ public function getFixersDocumentationIndexFilePath(): string public function getFixerDocumentationFilePath(FixerInterface $fixer): string { return $this->getFixersDocumentationDirectoryPath().'/'.Preg::replaceCallback( - '/^.*\\\\(.+)\\\\(.+)Fixer$/', - static function (array $matches): string { - return Utils::camelCaseToUnderscore($matches[1]).'/'.Utils::camelCaseToUnderscore($matches[2]); - }, + '/^.*\\\(.+)\\\(.+)Fixer$/', + static fn (array $matches): string => Utils::camelCaseToUnderscore($matches[1]).'/'.Utils::camelCaseToUnderscore($matches[2]), \get_class($fixer) ).'.rst'; } @@ -78,8 +73,8 @@ public function getRuleSetsDocumentationFilePath(string $name): string return $this->getRuleSetsDocumentationDirectoryPath().'/'.str_replace(':risky', 'Risky', ucfirst(substr($name, 1))).'.rst'; } - public function getListingFilePath(): string + public function getUsageFilePath(): string { - return $this->path.'/list.rst'; + return $this->path.'/usage.rst'; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php b/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php index 4de98f4bba..19ef36a937 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Documentation/FixerDocumentGenerator.php @@ -18,6 +18,7 @@ use PhpCsFixer\Differ\FullDiffer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; use PhpCsFixer\Fixer\FixerInterface; use PhpCsFixer\FixerConfiguration\AliasedFixerOption; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; @@ -64,11 +65,11 @@ public function generateFixerDocumentation(FixerInterface $fixer): string $doc .= <<getSuccessorsNames(); if (0 !== \count($alternatives)) { - $deprecationDescription .= RstUtils::toRst(sprintf( + $deprecationDescription .= RstUtils::toRst(\sprintf( "\n\nYou should use %s instead.", Utils::naturalLanguageJoinWithBackticks($alternatives) ), 0); } } + $experimentalDescription = ''; + + if ($fixer instanceof ExperimentalFixerInterface) { + $experimentalDescriptionRaw = RstUtils::toRst('Rule is not covered with backward compatibility promise, use it at your own risk. Rule\'s behaviour may be changed at any point, including rule\'s name; its options\' names, availability and allowed values; its default configuration. Rule may be even removed without prior notice. Feel free to provide feedback and help with determining final state of the rule.', 0); + $experimentalDescription = <<getRiskyDescription(); @@ -96,38 +110,40 @@ public function generateFixerDocumentation(FixerInterface $fixer): string $riskyDescriptionRaw = RstUtils::toRst($riskyDescriptionRaw, 0); $riskyDescription = << '' !== $text + )); } if ($fixer instanceof ConfigurableFixerInterface) { $doc .= <<<'RST' -Configuration -------------- -RST; + Configuration + ------------- + RST; $configurationDefinition = $fixer->getConfigurationDefinition(); @@ -137,39 +153,35 @@ public function generateFixerDocumentation(FixerInterface $fixer): string if ($option instanceof DeprecatedFixerOptionInterface) { $deprecationMessage = RstUtils::toRst($option->getDeprecationMessage()); - $optionInfo .= "\n\n.. warning:: This option is deprecated and will be removed on next major version. {$deprecationMessage}"; + $optionInfo .= "\n\n.. warning:: This option is deprecated and will be removed in the next major version. {$deprecationMessage}"; } $optionInfo .= "\n\n".RstUtils::toRst($option->getDescription()); if ($option instanceof AliasedFixerOption) { - $optionInfo .= "\n\n.. note:: The previous name of this option was ``{$option->getAlias()}`` but it is now deprecated and will be removed on next major version."; + $optionInfo .= "\n\n.. note:: The previous name of this option was ``{$option->getAlias()}`` but it is now deprecated and will be removed in the next major version."; } $allowed = HelpCommand::getDisplayableAllowedValues($option); if (null === $allowed) { $allowedKind = 'Allowed types'; - $allowed = array_map(static function ($value): string { - return '``'.$value.'``'; - }, $option->getAllowedTypes()); + $allowed = array_map( + static fn (string $value): string => '``'.Utils::convertArrayTypeToList($value).'``', + $option->getAllowedTypes(), + ); } else { $allowedKind = 'Allowed values'; - - foreach ($allowed as &$value) { - if ($value instanceof AllowedValueSubset) { - $value = 'a subset of ``'.HelpCommand::toString($value->getAllowedValues()).'``'; - } else { - $value = '``'.HelpCommand::toString($value).'``'; - } - } + $allowed = array_map(static fn ($value): string => $value instanceof AllowedValueSubset + ? 'a subset of ``'.Utils::toString($value->getAllowedValues()).'``' + : '``'.Utils::toString($value).'``', $allowed); } - $allowed = implode(', ', $allowed); + $allowed = Utils::naturalLanguageJoin($allowed, ''); $optionInfo .= "\n\n{$allowedKind}: {$allowed}"; if ($option->hasDefault()) { - $default = HelpCommand::toString($option->getDefault()); + $default = Utils::toString($option->getDefault()); $optionInfo .= "\n\nDefault value: ``{$default}``"; } else { $optionInfo .= "\n\nThis option is required."; @@ -185,12 +197,12 @@ public function generateFixerDocumentation(FixerInterface $fixer): string $doc .= <<<'RST' -Examples --------- -RST; + Examples + -------- + RST; foreach ($samples as $index => $sample) { - $title = sprintf('Example #%d', $index + 1); + $title = \sprintf('Example #%d', $index + 1); $titleLine = str_repeat('~', \strlen($title)); $doc .= "\n\n{$title}\n{$titleLine}"; @@ -198,9 +210,9 @@ public function generateFixerDocumentation(FixerInterface $fixer): string if (null === $sample->getConfiguration()) { $doc .= "\n\n*Default* configuration."; } else { - $doc .= sprintf( + $doc .= \sprintf( "\n\nWith configuration: ``%s``.", - HelpCommand::toString($sample->getConfiguration()) + Utils::toString($sample->getConfiguration()) ); } } @@ -209,53 +221,82 @@ public function generateFixerDocumentation(FixerInterface $fixer): string } } - $ruleSetConfigs = []; - - foreach (RuleSets::getSetDefinitionNames() as $set) { - $ruleSet = new RuleSet([$set => true]); - - if ($ruleSet->hasRule($name)) { - $ruleSetConfigs[$set] = $ruleSet->getRuleConfiguration($name); - } - } + $ruleSetConfigs = self::getSetsOfRule($name); if ([] !== $ruleSetConfigs) { $plural = 1 !== \count($ruleSetConfigs) ? 's' : ''; $doc .= << $config) { $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($set); $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/')); + $configInfo = (null !== $config) + ? " with config:\n\n ``".Utils::toString($config)."``\n" + : ''; + $doc .= <<`_{$configInfo}\n + RST; + } + } + $reflectionObject = new \ReflectionObject($fixer); + $className = str_replace('\\', '\\\\', $reflectionObject->getName()); + $fileName = $reflectionObject->getFileName(); + $fileName = str_replace('\\', '/', $fileName); + $fileName = substr($fileName, strrpos($fileName, '/src/Fixer/') + 1); + $fileName = "`{$className} <./../../../{$fileName}>`_"; -{$set} - Using the `{$set} <./../../ruleSets{$ruleSetPath}>`_ rule set will enable the ``{$name}`` rule -RST; + $testFileName = Preg::replace('~.*\K/src/(?=Fixer/)~', '/tests/', $fileName); + $testFileName = Preg::replace('~PhpCsFixer\\\\\\\\\K(?=Fixer\\\\\\\)~', 'Tests\\\\\\\\', $testFileName); + $testFileName = Preg::replace('~(?= <|\.php>)~', 'Test', $testFileName); - if (null !== $config) { - $doc .= " with the config below:\n\n ``".HelpCommand::toString($config).'``'; - } elseif ($fixer instanceof ConfigurableFixerInterface) { - $doc .= ' with the default config.'; - } else { - $doc .= '.'; - } + $doc .= <<', $doc); + + return "{$doc}\n"; + } + + /** + * @internal + * + * @return array> + */ + public static function getSetsOfRule(string $ruleName): array + { + $ruleSetConfigs = []; + + foreach (RuleSets::getSetDefinitionNames() as $set) { + $ruleSet = new RuleSet([$set => true]); + + if ($ruleSet->hasRule($ruleName)) { + $ruleSetConfigs[$set] = $ruleSet->getRuleConfiguration($ruleName); } } - return "{$doc}\n"; + return $ruleSetConfigs; } /** - * @param FixerInterface[] $fixers + * @param list $fixers */ public function generateFixersDocumentationIndex(array $fixers): string { @@ -265,20 +306,18 @@ public function generateFixersDocumentationIndex(array $fixers): string 'Phpdoc' => 'PHPDoc', ]; - usort($fixers, static function (FixerInterface $a, FixerInterface $b): int { - return strcmp(\get_class($a), \get_class($b)); - }); + usort($fixers, static fn (FixerInterface $a, FixerInterface $b): int => \get_class($a) <=> \get_class($b)); $documentation = <<<'RST' -======================= -List of Available Rules -======================= -RST; + ======================= + List of Available Rules + ======================= + RST; $currentGroup = null; foreach ($fixers as $fixer) { - $namespace = Preg::replace('/^.*\\\\(.+)\\\\.+Fixer$/', '$1', \get_class($fixer)); + $namespace = Preg::replace('/^.*\\\(.+)\\\.+Fixer$/', '$1', \get_class($fixer)); $group = $overrideGroups[$namespace] ?? Preg::replace('/(?<=[[:lower:]])(?=[[:upper:]])/', ' ', $namespace); if ($group !== $currentGroup) { @@ -296,23 +335,26 @@ public function generateFixersDocumentationIndex(array $fixers): string $attributes[] = 'deprecated'; } + if ($fixer instanceof ExperimentalFixerInterface) { + $attributes[] = 'experimental'; + } + if ($fixer->isRisky()) { $attributes[] = 'risky'; } $attributes = 0 === \count($attributes) ? '' - : ' *('.implode(', ', $attributes).')*' - ; + : ' *('.implode(', ', $attributes).')*'; $summary = str_replace('`', '``', $fixer->getDefinition()->getSummary()); $documentation .= <<getName()} <{$path}>`_{$attributes} + - `{$fixer->getName()} <{$path}>`_{$attributes} - {$summary} -RST; + {$summary} + RST; } return "{$documentation}\n"; @@ -333,12 +375,12 @@ private function generateSampleDiff(FixerInterface $fixer, CodeSampleInterface $ $error = <<getCode(); @@ -346,8 +388,7 @@ private function generateSampleDiff(FixerInterface $fixer, CodeSampleInterface $ $tokens = Tokens::fromCode($old); $file = $sample instanceof FileSpecificCodeSampleInterface ? $sample->getSplFileInfo() - : new StdinFileInfo() - ; + : new StdinFileInfo(); if ($fixer instanceof ConfigurableFixerInterface) { $fixer->configure($sample->getConfiguration() ?? []); @@ -364,9 +405,9 @@ private function generateSampleDiff(FixerInterface $fixer, CodeSampleInterface $ return << - * Dariusz Rumiński - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace PhpCsFixer\Documentation; - -use PhpCsFixer\Console\Command\HelpCommand; -use PhpCsFixer\Fixer\ConfigurableFixerInterface; -use PhpCsFixer\Fixer\DeprecatedFixerInterface; -use PhpCsFixer\Fixer\FixerInterface; -use PhpCsFixer\FixerConfiguration\AliasedFixerOption; -use PhpCsFixer\FixerConfiguration\AllowedValueSubset; -use PhpCsFixer\FixerConfiguration\DeprecatedFixerOptionInterface; -use PhpCsFixer\RuleSet\RuleSet; -use PhpCsFixer\RuleSet\RuleSets; -use PhpCsFixer\Utils; - -/** - * @internal - */ -final class ListDocumentGenerator -{ - private DocumentationLocator $locator; - - public function __construct(DocumentationLocator $locator) - { - $this->locator = $locator; - } - - /** - * @param FixerInterface[] $fixers - */ - public function generateListingDocumentation(array $fixers): string - { - usort( - $fixers, - static function (FixerInterface $fixer1, FixerInterface $fixer2): int { - return strnatcasecmp($fixer1->getName(), $fixer2->getName()); - } - ); - - $documentation = <<<'RST' -======================= -List of Available Rules -======================= - -RST; - foreach ($fixers as $fixer) { - $name = $fixer->getName(); - $definition = $fixer->getDefinition(); - $path = './rules/'.$this->locator->getFixerDocumentationFileRelativePath($fixer); - - $documentation .= "\n- `{$name} <{$path}>`_\n"; - $documentation .= "\n ".str_replace('`', '``', $definition->getSummary())."\n"; - - $description = $definition->getDescription(); - - if (null !== $description) { - $documentation .= "\n ".RstUtils::toRst($description, 3)."\n"; - } - - if ($fixer instanceof DeprecatedFixerInterface) { - $documentation .= "\n *warning deprecated*"; - $alternatives = $fixer->getSuccessorsNames(); - - if (0 !== \count($alternatives)) { - $documentation .= RstUtils::toRst(sprintf( - ' Use %s instead.', - Utils::naturalLanguageJoinWithBackticks($alternatives) - ), 3); - } - - $documentation .= "\n"; - } - - if ($fixer->isRisky()) { - $documentation .= "\n *warning risky* ".RstUtils::toRst($definition->getRiskyDescription(), 3)."\n"; - } - - if ($fixer instanceof ConfigurableFixerInterface) { - $documentation .= "\n Configuration options:\n"; - $configurationDefinition = $fixer->getConfigurationDefinition(); - - foreach ($configurationDefinition->getOptions() as $option) { - $documentation .= "\n - | ``{$option->getName()}``"; - $documentation .= "\n | {$option->getDescription()}"; - - if ($option instanceof DeprecatedFixerOptionInterface) { - $deprecationMessage = RstUtils::toRst($option->getDeprecationMessage(), 3); - $documentation .= "\n | warning:: This option is deprecated and will be removed on next major version. {$deprecationMessage}"; - } - - if ($option instanceof AliasedFixerOption) { - $documentation .= "\n | note:: The previous name of this option was ``{$option->getAlias()}`` but it is now deprecated and will be removed on next major version."; - } - - $allowed = HelpCommand::getDisplayableAllowedValues($option); - - if (null === $allowed) { - $allowedKind = 'Allowed types'; - $allowed = array_map(static function ($value): string { - return '``'.$value.'``'; - }, $option->getAllowedTypes()); - } else { - $allowedKind = 'Allowed values'; - - foreach ($allowed as &$value) { - if ($value instanceof AllowedValueSubset) { - $value = 'a subset of ``'.HelpCommand::toString($value->getAllowedValues()).'``'; - } else { - $value = '``'.HelpCommand::toString($value).'``'; - } - } - } - - $allowed = implode(', ', $allowed); - $documentation .= "\n | {$allowedKind}: {$allowed}"; - - if ($option->hasDefault()) { - $default = HelpCommand::toString($option->getDefault()); - $documentation .= "\n | Default value: ``{$default}``"; - } else { - $documentation .= "\n | This option is required."; - } - } - - $documentation .= "\n\n"; - } - - $ruleSetConfigs = []; - - foreach (RuleSets::getSetDefinitionNames() as $set) { - $ruleSet = new RuleSet([$set => true]); - - if ($ruleSet->hasRule($name)) { - $ruleSetConfigs[$set] = $ruleSet->getRuleConfiguration($name); - } - } - - if ([] !== $ruleSetConfigs) { - $plural = 1 !== \count($ruleSetConfigs) ? 's' : ''; - - $documentation .= "\n Part of rule set{$plural} "; - - foreach ($ruleSetConfigs as $set => $config) { - $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($set); - $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/')); - - $documentation .= "`{$set} <./ruleSets{$ruleSetPath}>`_ "; - } - - $documentation = rtrim($documentation)."\n"; - } - - $reflectionObject = new \ReflectionObject($fixer); - $className = str_replace('\\', '\\\\', $reflectionObject->getName()); - $fileName = $reflectionObject->getFileName(); - $fileName = str_replace('\\', '/', $fileName); - $fileName = substr($fileName, strrpos($fileName, '/src/Fixer/') + 1); - $fileName = "`Source {$className} <./../{$fileName}>`_"; - $documentation .= "\n ".$fileName; - } - - return $documentation."\n"; - } -} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Documentation/RstUtils.php b/vendor/friendsofphp/php-cs-fixer/src/Documentation/RstUtils.php index b7b5c51c58..129bc5d0ee 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Documentation/RstUtils.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Documentation/RstUtils.php @@ -28,11 +28,16 @@ private function __construct() public static function toRst(string $string, int $indent = 0): string { - $string = wordwrap(Preg::replace('/(? $fixers */ public function generateRuleSetsDocumentation(RuleSetDescriptionInterface $definition, array $fixers): string { @@ -46,38 +47,93 @@ public function generateRuleSetsDocumentation(RuleSetDescriptionInterface $defin $titleLine = str_repeat('=', \strlen($title)); $doc = "{$titleLine}\n{$title}\n{$titleLine}\n\n".$definition->getDescription(); + $warnings = []; + if ($definition instanceof DeprecatedRuleSetDescriptionInterface) { + $deprecationDescription = <<<'RST' + + This rule set is deprecated and will be removed in the next major version + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RST; + $alternatives = $definition->getSuccessorsNames(); + + if (0 !== \count($alternatives)) { + $deprecationDescription .= RstUtils::toRst( + \sprintf( + "\n\nYou should use %s instead.", + Utils::naturalLanguageJoinWithBackticks($alternatives) + ), + 0 + ); + } else { + $deprecationDescription .= 'No replacement available.'; + } + + $warnings[] = $deprecationDescription; + } + if ($definition->isRisky()) { - $doc .= ' This set contains rules that are risky.'; + $warnings[] = <<<'RST' + + This set contains rules that are risky + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Using this rule set may lead to changes in your code's logic and behaviour. Use it with caution and review changes before incorporating them into your code base. + RST; } - $doc .= "\n\n"; + if ([] !== $warnings) { + $warningsHeader = 1 === \count($warnings) ? 'Warning' : 'Warnings'; + + $warningsHeaderLine = str_repeat('-', \strlen($warningsHeader)); + $doc .= "\n\n".implode( + "\n", + [ + $warningsHeader, + $warningsHeaderLine, + ...$warnings, + ] + ); + } $rules = $definition->getRules(); - if (\count($rules) < 1) { - $doc .= 'This is an empty set.'; + if ([] === $rules) { + $doc .= "\n\nThis is an empty set."; } else { - $doc .= "Rules\n-----\n"; - - foreach ($rules as $rule => $config) { - if (str_starts_with($rule, '@')) { - $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($rule); - $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/')); - - $doc .= "\n- `{$rule} <.{$ruleSetPath}>`_"; - } else { - $path = Preg::replace( - '#^'.preg_quote($this->locator->getFixersDocumentationDirectoryPath(), '#').'/#', - './../rules/', - $this->locator->getFixerDocumentationFilePath($fixerNames[$rule]) - ); - - $doc .= "\n- `{$rule} <{$path}>`_"; + $enabledRules = array_filter($rules, static fn ($config) => false !== $config); + $disabledRules = array_filter($rules, static fn ($config) => false === $config); + + $listRules = function (array $rules) use (&$doc, $fixerNames): void { + foreach ($rules as $rule => $config) { + if (str_starts_with($rule, '@')) { + $ruleSetPath = $this->locator->getRuleSetsDocumentationFilePath($rule); + $ruleSetPath = substr($ruleSetPath, strrpos($ruleSetPath, '/')); + + $doc .= "\n- `{$rule} <.{$ruleSetPath}>`_"; + } else { + $path = Preg::replace( + '#^'.preg_quote($this->locator->getFixersDocumentationDirectoryPath(), '#').'/#', + './../rules/', + $this->locator->getFixerDocumentationFilePath($fixerNames[$rule]) + ); + + $doc .= "\n- `{$rule} <{$path}>`_"; + } + + if (!\is_bool($config)) { + $doc .= " with config:\n\n ``".Utils::toString($config)."``\n"; + } } + }; - if (!\is_bool($config)) { - $doc .= "\n config:\n ``".HelpCommand::toString($config).'``'; - } + if ([] !== $enabledRules) { + $doc .= "\n\nRules\n-----\n"; + $listRules($enabledRules); + } + + if ([] !== $disabledRules) { + $doc .= "\n\nDisabled rules\n--------------\n"; + $listRules($disabledRules); } } @@ -85,18 +141,30 @@ public function generateRuleSetsDocumentation(RuleSetDescriptionInterface $defin } /** - * @param array $setDefinitions + * @param array $setDefinitions */ public function generateRuleSetsDocumentationIndex(array $setDefinitions): string { $documentation = <<<'RST' -=========================== -List of Available Rule sets -=========================== -RST; - foreach ($setDefinitions as $name => $path) { + =========================== + List of Available Rule sets + =========================== + RST; + + foreach ($setDefinitions as $path => $definition) { $path = substr($path, strrpos($path, '/')); - $documentation .= "\n- `{$name} <.{$path}>`_"; + + $attributes = []; + + if ($definition instanceof DeprecatedRuleSetDescriptionInterface) { + $attributes[] = 'deprecated'; + } + + $attributes = 0 === \count($attributes) + ? '' + : ' *('.implode(', ', $attributes).')*'; + + $documentation .= "\n- `{$definition->getName()} <.{$path}>`_{$attributes}"; } return $documentation."\n"; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php b/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php index b2ad97dfc4..d0c5ac7ea1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Error/Error.php @@ -21,7 +21,7 @@ * * @internal */ -final class Error +final class Error implements \JsonSerializable { /** * Error which has occurred in linting phase, before applying any fixers. @@ -38,22 +38,24 @@ final class Error */ public const TYPE_LINT = 3; + /** @var self::TYPE_* */ private int $type; private string $filePath; + private ?\Throwable $source; + /** - * @var null|\Throwable + * @var list */ - private $source; - private array $appliedFixers; + private ?string $diff; + /** - * @var null|string + * @param self::TYPE_* $type + * @param list $appliedFixers */ - private $diff; - public function __construct(int $type, string $filePath, ?\Throwable $source = null, array $appliedFixers = [], ?string $diff = null) { $this->type = $type; @@ -78,6 +80,9 @@ public function getType(): int return $this->type; } + /** + * @return list + */ public function getAppliedFixers(): array { return $this->appliedFixers; @@ -87,4 +92,32 @@ public function getDiff(): ?string { return $this->diff; } + + /** + * @return array{ + * type: self::TYPE_*, + * filePath: string, + * source: null|array{class: class-string, message: string, code: int, file: string, line: int}, + * appliedFixers: list, + * diff: null|string + * } + */ + public function jsonSerialize(): array + { + return [ + 'type' => $this->type, + 'filePath' => $this->filePath, + 'source' => null !== $this->source + ? [ + 'class' => \get_class($this->source), + 'message' => $this->source->getMessage(), + 'code' => $this->source->getCode(), + 'file' => $this->source->getFile(), + 'line' => $this->source->getLine(), + ] + : null, + 'appliedFixers' => $this->appliedFixers, + 'diff' => $this->diff, + ]; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php b/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php index 01006673a8..e6333db80f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Error/ErrorsManager.php @@ -24,44 +24,48 @@ final class ErrorsManager { /** - * @var Error[] + * @var list */ private array $errors = []; /** * Returns errors reported during linting before fixing. * - * @return Error[] + * @return list */ public function getInvalidErrors(): array { - return array_filter($this->errors, static function (Error $error): bool { - return Error::TYPE_INVALID === $error->getType(); - }); + return array_filter($this->errors, static fn (Error $error): bool => Error::TYPE_INVALID === $error->getType()); } /** * Returns errors reported during fixing. * - * @return Error[] + * @return list */ public function getExceptionErrors(): array { - return array_filter($this->errors, static function (Error $error): bool { - return Error::TYPE_EXCEPTION === $error->getType(); - }); + return array_filter($this->errors, static fn (Error $error): bool => Error::TYPE_EXCEPTION === $error->getType()); } /** * Returns errors reported during linting after fixing. * - * @return Error[] + * @return list */ public function getLintErrors(): array { - return array_filter($this->errors, static function (Error $error): bool { - return Error::TYPE_LINT === $error->getType(); - }); + return array_filter($this->errors, static fn (Error $error): bool => Error::TYPE_LINT === $error->getType()); + } + + /** + * Returns errors reported for specified path. + * + * @return list + */ + public function forPath(string $path): array + { + return array_values(array_filter($this->errors, static fn (Error $error): bool => $path === $error->getFilePath())); } /** @@ -69,7 +73,7 @@ public function getLintErrors(): array */ public function isEmpty(): bool { - return empty($this->errors); + return [] === $this->errors; } public function report(Error $error): void diff --git a/vendor/friendsofphp/php-cs-fixer/src/Error/SourceExceptionFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Error/SourceExceptionFactory.php new file mode 100644 index 0000000000..5180b08e51 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Error/SourceExceptionFactory.php @@ -0,0 +1,60 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Error; + +/** + * @internal + */ +final class SourceExceptionFactory +{ + /** + * @param array{class: class-string<\Throwable>, message: string, code: int, file: string, line: int} $error + */ + public static function fromArray(array $error): \Throwable + { + $exceptionClass = $error['class']; + + try { + $exception = new $exceptionClass($error['message'], $error['code']); + + if ( + $exception->getMessage() !== $error['message'] + || $exception->getCode() !== $error['code'] + ) { + throw new \RuntimeException('Failed to create exception from array. Message and code are not the same.'); + } + } catch (\Throwable $e) { + $exception = new \RuntimeException( + \sprintf('[%s] %s', $exceptionClass, $error['message']), + $error['code'] + ); + } + + try { + $exceptionReflection = new \ReflectionClass($exception); + foreach (['file', 'line'] as $property) { + $propertyReflection = $exceptionReflection->getProperty($property); + $propertyReflection->setAccessible(true); + $propertyReflection->setValue($exception, $error[$property]); + $propertyReflection->setAccessible(false); + } + } catch (\Throwable $reflectionException) { + // Ignore if we were not able to set file/line properties. In most cases it should be fine, + // we just need to make sure nothing is broken when we recreate errors from raw data passed from worker. + } + + return $exception; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandler.php b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandler.php new file mode 100644 index 0000000000..003398e49c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandler.php @@ -0,0 +1,58 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ExecutorWithoutErrorHandler +{ + private function __construct() {} + + /** + * @template T + * + * @param callable(): T $callback + * + * @return T + * + * @throws ExecutorWithoutErrorHandlerException + */ + public static function execute(callable $callback) + { + /** @var ?string */ + $error = null; + + set_error_handler(static function (int $errorNumber, string $errorString, string $errorFile, int $errorLine) use (&$error): bool { + $error = $errorString; + + return true; + }); + + try { + $result = $callback(); + } finally { + restore_error_handler(); + } + + if (null !== $error) { + throw new ExecutorWithoutErrorHandlerException($error); + } + + return $result; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandlerException.php b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandlerException.php new file mode 100644 index 0000000000..750352a265 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ExecutorWithoutErrorHandlerException.php @@ -0,0 +1,22 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ExecutorWithoutErrorHandlerException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FileReader.php b/vendor/friendsofphp/php-cs-fixer/src/FileReader.php index d71f5f76e0..ec36e3a6b2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FileReader.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FileReader.php @@ -61,10 +61,10 @@ private function readRaw(string $realPath): string if (false === $content) { $error = error_get_last(); - throw new \RuntimeException(sprintf( + throw new \RuntimeException(\sprintf( 'Failed to read content from "%s".%s', $realPath, - $error ? ' '.$error['message'] : '' + null !== $error ? ' '.$error['message'] : '' )); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php b/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php index f3fd7f8ee9..148a4c5e14 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FileRemoval.php @@ -26,6 +26,8 @@ final class FileRemoval { /** * List of observed files to be removed. + * + * @var array */ private array $files = []; @@ -45,7 +47,7 @@ public function __destruct() */ public function __sleep(): array { - throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + throw new \BadMethodCallException('Cannot serialize '.self::class); } /** @@ -56,7 +58,7 @@ public function __sleep(): array */ public function __wakeup(): void { - throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + throw new \BadMethodCallException('Cannot unserialize '.self::class); } /** diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php index 42a92a7ef9..b6a8660c7a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractPhpUnitFixer.php @@ -15,7 +15,15 @@ namespace PhpCsFixer\Fixer; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** @@ -23,10 +31,7 @@ */ abstract class AbstractPhpUnitFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ - final public function isCandidate(Tokens $tokens): bool + public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_CLASS, T_STRING]); } @@ -44,15 +49,229 @@ abstract protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex final protected function getDocBlockIndex(Tokens $tokens, int $index): int { + $modifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT]; + + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $modifiers[] = T_ATTRIBUTE; + } + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required + $modifiers[] = T_READONLY; + } + do { $index = $tokens->getPrevNonWhitespace($index); - } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]); + } + } while ($tokens[$index]->isGivenKind($modifiers)); return $index; } + /** + * @param list $preventingAnnotations + * @param list $preventingAttributes + */ + final protected function ensureIsDocBlockWithAnnotation( + Tokens $tokens, + int $index, + string $annotation, + array $preventingAnnotations, + array $preventingAttributes + ): void { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + if (self::isPreventedByAttribute($tokens, $index, $preventingAttributes)) { + return; + } + + if ($this->isPHPDoc($tokens, $docBlockIndex)) { + $this->updateDocBlockIfNeeded($tokens, $docBlockIndex, $annotation, $preventingAnnotations); + } else { + $this->createDocBlock($tokens, $docBlockIndex, $annotation); + } + } + final protected function isPHPDoc(Tokens $tokens, int $index): bool { return $tokens[$index]->isGivenKind(T_DOC_COMMENT); } + + /** + * @return iterable + */ + protected function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $endIndex): iterable + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + for ($index = $endIndex; $index > $startIndex; --$index) { + $index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]); + + if (null === $index) { + return; + } + + // test if "assert" something call + $loweredContent = strtolower($tokens[$index]->getContent()); + + if (!str_starts_with($loweredContent, 'assert')) { + continue; + } + + // test candidate for simple calls like: ([\]+'some fixable call'(...)) + $openBraceIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$openBraceIndex]->equals('(')) { + continue; + } + + if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) { + continue; + } + + yield [ + 'index' => $index, + 'loweredName' => $loweredContent, + 'openBraceIndex' => $openBraceIndex, + 'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex), + ]; + } + } + + private function createDocBlock(Tokens $tokens, int $docBlockIndex, string $annotation): void + { + $lineEnd = $this->whitespacesConfig->getLineEnding(); + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $toInsert = [ + new Token([T_DOC_COMMENT, "/**{$lineEnd}{$originalIndent} * @{$annotation}{$lineEnd}{$originalIndent} */"]), + new Token([T_WHITESPACE, $lineEnd.$originalIndent]), + ]; + $index = $tokens->getNextMeaningfulToken($docBlockIndex); + $tokens->insertAt($index, $toInsert); + + if (!$tokens[$index - 1]->isGivenKind(T_WHITESPACE)) { + $extraNewLines = $this->whitespacesConfig->getLineEnding(); + + if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { + $extraNewLines .= $this->whitespacesConfig->getLineEnding(); + } + + $tokens->insertAt($index, [ + new Token([T_WHITESPACE, $extraNewLines.WhitespacesAnalyzer::detectIndent($tokens, $index)]), + ]); + } + } + + /** + * @param list $preventingAnnotations + */ + private function updateDocBlockIfNeeded( + Tokens $tokens, + int $docBlockIndex, + string $annotation, + array $preventingAnnotations + ): void { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + foreach ($preventingAnnotations as $preventingAnnotation) { + if ([] !== $doc->getAnnotationsOfType($preventingAnnotation)) { + return; + } + } + $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex, $annotation); + + $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation); + $lines = implode('', $lines); + + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + + /** + * @param list $preventingAttributes + */ + private static function isPreventedByAttribute(Tokens $tokens, int $index, array $preventingAttributes): bool + { + if ([] === $preventingAttributes) { + return false; + } + + $modifiers = [T_FINAL]; + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.2+ is required + $modifiers[] = T_READONLY; + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind($modifiers)); + if (!$tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + return false; + } + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + + foreach (AttributeAnalyzer::collect($tokens, $index) as $attributeAnalysis) { + foreach ($attributeAnalysis->getAttributes() as $attribute) { + if (\in_array(ltrim(self::getFullyQualifiedName($tokens, $attribute['name']), '\\'), $preventingAttributes, true)) { + return true; + } + } + } + + return false; + } + + private static function getFullyQualifiedName(Tokens $tokens, string $name): string + { + $name = strtolower($name); + + $names = []; + foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) { + $names[strtolower($namespaceUseAnalysis->getShortName())] = strtolower($namespaceUseAnalysis->getFullName()); + } + + foreach ($names as $shortName => $fullName) { + if ($name === $shortName) { + return $fullName; + } + + if (!str_starts_with($name, $shortName.'\\')) { + continue; + } + + return $fullName.substr($name, \strlen($shortName)); + } + + return $name; + } + + /** + * @return list + */ + private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation): array + { + $lines = $docBlock->getLines(); + $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); + $lineEnd = $this->whitespacesConfig->getLineEnding(); + array_splice($lines, -1, 0, $originalIndent.' * @'.$annotation.$lineEnd); + + return $lines; + } + + private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock + { + $lines = $doc->getLines(); + if (1 === \count($lines) && [] === $doc->getAnnotationsOfType($annotation)) { + $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); + $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding()); + + return $doc; + } + + return $doc; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractShortOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractShortOperatorFixer.php new file mode 100644 index 0000000000..1897e0424f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AbstractShortOperatorFixer.php @@ -0,0 +1,264 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +abstract class AbstractShortOperatorFixer extends AbstractFixer +{ + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); + + for ($index = \count($tokens) - 1; $index > 3; --$index) { + if (!$this->isOperatorTokenCandidate($tokens, $index)) { + continue; + } + + // get what is before the operator + + $beforeRange = $this->getBeforeOperatorRange($tokens, $index); + $equalsIndex = $tokens->getPrevMeaningfulToken($beforeRange['start']); + + // make sure that before that is '=' + + if (!$tokens[$equalsIndex]->equals('=')) { + continue; + } + + // get and check what is before '=' + + $assignRange = $this->getBeforeOperatorRange($tokens, $equalsIndex); + $beforeAssignmentIndex = $tokens->getPrevMeaningfulToken($assignRange['start']); + + if ($tokens[$beforeAssignmentIndex]->equals(':')) { + if (!$this->belongsToSwitchOrAlternativeSyntax($alternativeSyntaxAnalyzer, $tokens, $beforeAssignmentIndex)) { + continue; + } + } elseif (!$tokens[$beforeAssignmentIndex]->equalsAny([';', '{', '}', '(', ')', ',', [T_OPEN_TAG], [T_RETURN]])) { + continue; + } + + // check if "assign" and "before" the operator are (functionally) the same + + if (RangeAnalyzer::rangeEqualsRange($tokens, $assignRange, $beforeRange)) { + $this->shortenOperation($tokens, $equalsIndex, $index, $assignRange, $beforeRange); + + continue; + } + + if (!$this->isOperatorCommutative($tokens[$index])) { + continue; + } + + $afterRange = $this->getAfterOperatorRange($tokens, $index); + + // check if "assign" and "after" the operator are (functionally) the same + if (!RangeAnalyzer::rangeEqualsRange($tokens, $assignRange, $afterRange)) { + continue; + } + + $this->shortenOperation($tokens, $equalsIndex, $index, $assignRange, $afterRange); + } + } + + abstract protected function getReplacementToken(Token $token): Token; + + abstract protected function isOperatorTokenCandidate(Tokens $tokens, int $index): bool; + + /** + * @param array{start: int, end: int} $assignRange + * @param array{start: int, end: int} $operatorRange + */ + private function shortenOperation( + Tokens $tokens, + int $equalsIndex, + int $operatorIndex, + array $assignRange, + array $operatorRange + ): void { + $tokens[$equalsIndex] = $this->getReplacementToken($tokens[$operatorIndex]); + $tokens->clearTokenAndMergeSurroundingWhitespace($operatorIndex); + $this->clearMeaningfulFromRange($tokens, $operatorRange); + + foreach ([$equalsIndex, $assignRange['end']] as $i) { + $i = $tokens->getNonEmptySibling($i, 1); + + if ($tokens[$i]->isWhitespace(" \t")) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } elseif (!$tokens[$i]->isWhitespace()) { + $tokens->insertAt($i, new Token([T_WHITESPACE, ' '])); + } + } + } + + /** + * @return array{start: int, end: int} + */ + private function getAfterOperatorRange(Tokens $tokens, int $index): array + { + $index = $tokens->getNextMeaningfulToken($index); + $range = ['start' => $index]; + + while (true) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (null === $nextIndex || $tokens[$nextIndex]->equalsAny([';', ',', [T_CLOSE_TAG]])) { + break; + } + + $blockType = Tokens::detectBlockType($tokens[$nextIndex]); + + if (null === $blockType) { + $index = $nextIndex; + + continue; + } + + if (false === $blockType['isStart']) { + break; + } + + $index = $tokens->findBlockEnd($blockType['type'], $nextIndex); + } + + $range['end'] = $index; + + return $range; + } + + /** + * @return array{start: int, end: int} + */ + private function getBeforeOperatorRange(Tokens $tokens, int $index): array + { + static $blockOpenTypes; + + if (null === $blockOpenTypes) { + $blockOpenTypes = [',']; // not a true "block type", but speeds up things + + foreach (Tokens::getBlockEdgeDefinitions() as $definition) { + $blockOpenTypes[] = $definition['start']; + } + } + + $controlStructureWithoutBracesTypes = [T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE]; + + $previousIndex = $tokens->getPrevMeaningfulToken($index); + $previousToken = $tokens[$previousIndex]; + + if ($tokens[$previousIndex]->equalsAny($blockOpenTypes)) { + return ['start' => $index, 'end' => $index]; + } + + $range = ['end' => $previousIndex]; + $index = $previousIndex; + + while ($previousToken->equalsAny([ + '$', + ']', + ')', + [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], + [CT::T_DYNAMIC_PROP_BRACE_CLOSE], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [T_NS_SEPARATOR], + [T_STRING], + [T_VARIABLE], + ])) { + $blockType = Tokens::detectBlockType($previousToken); + + if (null !== $blockType) { + $blockStart = $tokens->findBlockStart($blockType['type'], $previousIndex); + + if ($tokens[$previousIndex]->equals(')') && $tokens[$tokens->getPrevMeaningfulToken($blockStart)]->isGivenKind($controlStructureWithoutBracesTypes)) { + break; // we went too far back + } + + $previousIndex = $blockStart; + } + + $index = $previousIndex; + $previousIndex = $tokens->getPrevMeaningfulToken($previousIndex); + $previousToken = $tokens[$previousIndex]; + } + + if ($previousToken->isGivenKind(T_OBJECT_OPERATOR)) { + $index = $this->getBeforeOperatorRange($tokens, $previousIndex)['start']; + } elseif ($previousToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + $index = $this->getBeforeOperatorRange($tokens, $tokens->getPrevMeaningfulToken($previousIndex))['start']; + } + + $range['start'] = $index; + + return $range; + } + + /** + * @param array{start: int, end: int} $range + */ + private function clearMeaningfulFromRange(Tokens $tokens, array $range): void + { + // $range['end'] must be meaningful! + for ($i = $range['end']; $i >= $range['start']; $i = $tokens->getPrevMeaningfulToken($i)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + } + } + + private function isOperatorCommutative(Token $operatorToken): bool + { + static $commutativeKinds = ['*', '|', '&', '^']; // note that for arrays in PHP `+` is not commutative + static $nonCommutativeKinds = ['-', '/', '.', '%', '+']; + + if ($operatorToken->isGivenKind(T_COALESCE)) { + return false; + } + + if ($operatorToken->equalsAny($commutativeKinds)) { + return true; + } + + if ($operatorToken->equalsAny($nonCommutativeKinds)) { + return false; + } + + throw new \InvalidArgumentException(\sprintf('Not supported operator "%s".', $operatorToken->toJson())); + } + + private function belongsToSwitchOrAlternativeSyntax(AlternativeSyntaxAnalyzer $alternativeSyntaxAnalyzer, Tokens $tokens, int $index): bool + { + $candidate = $index; + $index = $tokens->getPrevMeaningfulToken($candidate); + + if ($tokens[$index]->isGivenKind(T_DEFAULT)) { + return true; + } + + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_CASE)) { + return true; + } + + return $alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $candidate); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php index 43f4ae4f3a..9023bbd37d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ArrayPushFixer.php @@ -25,9 +25,6 @@ final class ArrayPushFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -38,17 +35,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING) && $tokens->count() > 7; } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -197,8 +188,6 @@ private function getSecondArgumentEnd(Tokens $tokens, int $index, int $endIndex) return null; } - $index = $tokens->getNextMeaningfulToken($index); - for (; $index <= $endIndex; ++$index) { $blockType = Tokens::detectBlockType($tokens[$index]); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php index c778c084d6..f2197705a7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/BacktickToShellExecFixer.php @@ -27,17 +27,11 @@ */ final class BacktickToShellExecFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound('`'); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,11 +39,11 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( <<<'EOT' -call()}`; + call()}`; -EOT + EOT ), ], 'Conversion is done only when it is non risky, so when special chars like single-quotes, double-quotes and backticks are not used inside the command.' @@ -59,16 +53,13 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before EscapeImplicitBackslashesFixer, ExplicitStringVariableFixer, NativeFunctionInvocationFixer, SingleQuoteFixer. + * Must run before EscapeImplicitBackslashesFixer, ExplicitStringVariableFixer, NativeFunctionInvocationFixer, SingleQuoteFixer, StringImplicitBackslashesFixer. */ public function getPriority(): int { - return 2; + return 17; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $backtickStarted = false; @@ -104,9 +95,8 @@ private function fixBackticks(Tokens $tokens, array $backtickTokens): void { // Track indices for final override ksort($backtickTokens); - $openingBacktickIndex = key($backtickTokens); - end($backtickTokens); - $closingBacktickIndex = key($backtickTokens); + $openingBacktickIndex = array_key_first($backtickTokens); + $closingBacktickIndex = array_key_last($backtickTokens); // Strip enclosing backticks array_shift($backtickTokens); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php index a0ca7197df..396658c389 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/EregToPregFixer.php @@ -30,8 +30,8 @@ final class EregToPregFixer extends AbstractFixer { /** - * @var array the list of the ext/ereg function names, their preg equivalent and the preg modifier(s), if any - * all condensed in an array of arrays + * @var list> the list of the ext/ereg function names, their preg equivalent and the preg modifier(s), if any + * all condensed in an array of arrays */ private static array $functions = [ ['ereg', 'preg_match', ''], @@ -43,13 +43,10 @@ final class EregToPregFixer extends AbstractFixer ]; /** - * @var array the list of preg delimiters, in order of preference + * @var list the list of preg delimiters, in order of preference */ private static array $delimiters = ['/', '#', '!']; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -62,23 +59,24 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} + * + * Must run after NoUselessConcatOperatorFixer. */ + public function getPriority(): int + { + return 0; + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $end = $tokens->count() - 1; @@ -99,7 +97,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // findSequence also returns the tokens, but we're only interested in the indices, i.e.: // 0 => function name, - // 1 => bracket "(" + // 1 => parenthesis "(" // 2 => quoted string passed as 1st parameter $match = array_keys($match); @@ -119,8 +117,17 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // convert to PCRE $regexTokenContent = $tokens[$match[2]]->getContent(); - $string = substr($regexTokenContent, 1, -1); - $quote = $regexTokenContent[0]; + + if ('b' === $regexTokenContent[0] || 'B' === $regexTokenContent[0]) { + $quote = $regexTokenContent[1]; + $prefix = $regexTokenContent[0]; + $string = substr($regexTokenContent, 2, -1); + } else { + $quote = $regexTokenContent[0]; + $prefix = ''; + $string = substr($regexTokenContent, 1, -1); + } + $delim = $this->getBestDelimiter($string); $preg = $delim.addcslashes($string, $delim).$delim.'D'.$map[2]; @@ -131,7 +138,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // modify function and argument $tokens[$match[0]] = new Token([T_STRING, $map[1]]); - $tokens[$match[2]] = new Token([T_CONSTANT_ENCAPSED_STRING, $quote.$preg.$quote]); + $tokens[$match[2]] = new Token([T_CONSTANT_ENCAPSED_STRING, $prefix.$quote.$preg.$quote]); } } } @@ -181,6 +188,6 @@ private function getBestDelimiter(string $pattern): string return $a[0] <=> $b[0]; }); - return key($delimiters); + return array_key_first($delimiters); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php index d496975559..1cbbd87428 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/MbStrFunctionsFixer.php @@ -28,7 +28,15 @@ final class MbStrFunctionsFixer extends AbstractFunctionReferenceFixer { /** - * @var array the list of the string-related function names and their mb_ equivalent + * list of the string-related function names and their mb_ equivalent. + * + * @var array< + * string, + * array{ + * alternativeName: string, + * argumentCount: list, + * }, + * > */ private static array $functionsMap = [ 'str_split' => ['alternativeName' => 'mb_str_split', 'argumentCount' => [1, 2, 3]], @@ -47,25 +55,36 @@ final class MbStrFunctionsFixer extends AbstractFunctionReferenceFixer ]; /** - * @var array + * @var array< + * string, + * array{ + * alternativeName: string, + * argumentCount: list, + * }, + * > */ - private $functions; + private array $functions; public function __construct() { parent::__construct(); + if (\PHP_VERSION_ID >= 8_03_00) { + self::$functionsMap['str_pad'] = ['alternativeName' => 'mb_str_pad', 'argumentCount' => [1, 2, 3, 4]]; + } + + if (\PHP_VERSION_ID >= 8_04_00) { + self::$functionsMap['trim'] = ['alternativeName' => 'mb_trim', 'argumentCount' => [1, 2]]; + self::$functionsMap['ltrim'] = ['alternativeName' => 'mb_ltrim', 'argumentCount' => [1, 2]]; + self::$functionsMap['rtrim'] = ['alternativeName' => 'mb_rtrim', 'argumentCount' => [1, 2]]; + } + $this->functions = array_filter( self::$functionsMap, - static function (array $mapping): bool { - return (new \ReflectionFunction($mapping['alternativeName']))->isInternal(); - } + static fn (array $mapping): bool => (new \ReflectionFunction($mapping['alternativeName']))->isInternal() ); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -93,9 +112,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php index d0f2cd4c3e..b060b4e63c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/ModernizeStrposFixer.php @@ -77,7 +77,8 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BinaryOperatorSpacesFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NotOperatorWithSpaceFixer, NotOperatorWithSuccessorSpaceFixer, PhpUnitDedicateAssertFixer, SingleSpaceAfterConstructFixer. + * Must run before BinaryOperatorSpacesFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NotOperatorWithSpaceFixer, NotOperatorWithSuccessorSpaceFixer, PhpUnitDedicateAssertFixer, SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, SpacesInsideParenthesesFixer. + * Must run after StrictComparisonFixer. */ public function getPriority(): int { @@ -127,6 +128,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + /** + * @param array{operator_index: int, operand_index: int} $operatorIndices + */ private function fixCall(Tokens $tokens, int $functionIndex, array $operatorIndices): void { foreach (self::REPLACEMENTS as $replacement) { @@ -160,11 +164,20 @@ private function fixCall(Tokens $tokens, int $functionIndex, array $operatorIndi } } + /** + * @param -1|1 $direction + * + * @return null|array{operator_index: int, operand_index: int} + */ private function getCompareTokens(Tokens $tokens, int $offsetIndex, int $direction): ?array { $operatorIndex = $tokens->getMeaningfulTokenSibling($offsetIndex, $direction); - if (null === $operatorIndex) { + if (null !== $operatorIndex && $tokens[$operatorIndex]->isGivenKind(T_NS_SEPARATOR)) { + $operatorIndex = $tokens->getMeaningfulTokenSibling($operatorIndex, $direction); + } + + if (null === $operatorIndex || !$tokens[$operatorIndex]->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL])) { return null; } @@ -180,10 +193,6 @@ private function getCompareTokens(Tokens $tokens, int $offsetIndex, int $directi return null; } - if (!$tokens[$operatorIndex]->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL])) { - return null; - } - $precedenceTokenIndex = $tokens->getMeaningfulTokenSibling($operandIndex, $direction); if (null !== $precedenceTokenIndex && $this->isOfHigherPrecedence($tokens[$precedenceTokenIndex])) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php index bf6fb579f4..efd042cf38 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasFunctionsFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -31,9 +32,21 @@ /** * @author Vladimir Reznichenko * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * sets?: list<'@all'|'@exif'|'@ftp'|'@IMAP'|'@internal'|'@ldap'|'@mbreg'|'@mysqli'|'@oci'|'@odbc'|'@openssl'|'@pcntl'|'@pg'|'@posix'|'@snmp'|'@sodium'|'@time'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * sets: list<'@all'|'@exif'|'@ftp'|'@IMAP'|'@internal'|'@ldap'|'@mbreg'|'@mysqli'|'@oci'|'@odbc'|'@openssl'|'@pcntl'|'@pg'|'@posix'|'@snmp'|'@sodium'|'@time'> + * } */ final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + private const SETS = [ '@internal' => [ 'diskfreespace' => 'disk_free_space', @@ -156,30 +169,10 @@ final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableF ]; /** - * @var array|string> stores alias (key) - master (value) functions mapping + * @var array stores alias (key) - master (value) functions mapping */ private array $aliases = []; - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->aliases = []; - - foreach ($this->configuration['sets'] as $set) { - if ('@all' === $set) { - $this->aliases = array_merge(...array_values(self::SETS)); - - break; - } - - $this->aliases = array_merge($this->aliases, self::SETS[$set]); - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -233,25 +226,35 @@ public function getPriority(): int return 40; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->aliases = []; + + foreach ($this->configuration['sets'] as $set) { + if ('@all' === $set) { + $this->aliases = array_merge(...array_values(self::SETS)); + + break; + } + + if (!isset(self::SETS[$set])) { + throw new \LogicException(\sprintf('Set %s passed option validation, but not part of ::SETS.', $set)); + } + + $this->aliases = array_merge($this->aliases, self::SETS[$set]); + } + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -293,9 +296,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $sets = [ @@ -321,12 +321,14 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn $list = "List of sets to fix. Defined sets are:\n\n"; foreach ($sets as $set => $description) { - $list .= sprintf("* `%s` (%s)\n", $set, $description); + $list .= \sprintf("* `%s` (%s);\n", $set, $description); } + $list = rtrim($list, ";\n").'.'; + return new FixerConfigurationResolver([ (new FixerOptionBuilder('sets', $list)) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset(array_keys($sets))]) ->setDefault(['@internal', '@IMAP', '@pg']) ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php index 357fa22830..4df764ab83 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoAliasLanguageConstructCallFixer.php @@ -23,9 +23,6 @@ final class NoAliasLanguageConstructCallFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -40,17 +37,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_EXIT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php index 4678241537..7a0292fefa 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/NoMixedEchoPrintFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,38 +29,26 @@ /** * @author Sullivan Senechal + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * use?: 'echo'|'print' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * use: 'echo'|'print' + * } */ final class NoMixedEchoPrintFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * @var string - */ - private $callBack; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; /** - * @var int T_ECHO or T_PRINT + * @var T_ECHO|T_PRINT */ - private $candidateTokenType; + private int $candidateTokenType; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - if ('echo' === $this->configuration['use']) { - $this->candidateTokenType = T_PRINT; - $this->callBack = 'fixPrintToEcho'; - } else { - $this->candidateTokenType = T_ECHO; - $this->callBack = 'fixEchoToPrint'; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -81,30 +70,29 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound($this->candidateTokenType); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->candidateTokenType = 'echo' === $this->configuration['use'] ? T_PRINT : T_ECHO; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $callBack = $this->callBack; foreach ($tokens as $index => $token) { if ($token->isGivenKind($this->candidateTokenType)) { - $this->{$callBack}($tokens, $index); + if (T_PRINT === $this->candidateTokenType) { + $this->fixPrintToEcho($tokens, $index); + } else { + $this->fixEchoToPrint($tokens, $index); + } } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php index 30386770a8..84e6809061 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/PowToExponentiationFixer.php @@ -25,18 +25,12 @@ final class PowToExponentiationFixer extends AbstractFunctionReferenceFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { // minimal candidate to fix is seven tokens: pow(x,y); return $tokens->count() > 7 && $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -54,16 +48,13 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer. + * Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. */ public function getPriority(): int { return 32; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $candidates = $this->findPowCalls($tokens); @@ -106,7 +97,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @return array + * @return list */ private function findPowCalls(Tokens $tokens): array { @@ -214,17 +205,15 @@ private function isParenthesisNeeded(Tokens $tokens, int $argumentStartIndex, in } /** - * @return int[] + * @return list */ private function getAllowedKinds(): array { - return array_merge( - [ - T_DNUMBER, T_LNUMBER, T_VARIABLE, T_STRING, T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_CAST, - T_INT_CAST, T_INC, T_DEC, T_NS_SEPARATOR, T_WHITESPACE, T_DOUBLE_COLON, T_LINE, T_COMMENT, T_DOC_COMMENT, - CT::T_NAMESPACE_OPERATOR, - ], - Token::getObjectOperatorKinds() - ); + return [ + T_DNUMBER, T_LNUMBER, T_VARIABLE, T_STRING, T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_CAST, + T_INT_CAST, T_INC, T_DEC, T_NS_SEPARATOR, T_WHITESPACE, T_DOUBLE_COLON, T_LINE, T_COMMENT, T_DOC_COMMENT, + CT::T_NAMESPACE_OPERATOR, + ...Token::getObjectOperatorKinds(), + ]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php index 8b722819cc..9909eb7a10 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/RandomApiMigrationFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFunctionReferenceFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,9 +30,24 @@ /** * @author Vladimir Reznichenko + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * replacements?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * replacements: array + * } */ final class RandomApiMigrationFixer extends AbstractFunctionReferenceFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** + * @var array> + */ private static array $argumentCounts = [ 'getrandmax' => [0], 'mt_rand' => [1, 2], @@ -40,24 +56,6 @@ final class RandomApiMigrationFixer extends AbstractFunctionReferenceFixer imple 'random_int' => [0, 2], ]; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - foreach ($this->configuration['replacements'] as $functionName => $replacement) { - $this->configuration['replacements'][$functionName] = [ - 'alternativeName' => $replacement, - 'argumentCount' => self::$argumentCounts[$functionName], - ]; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -78,15 +76,12 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach ($this->configuration['replacements'] as $functionIdentity => $functionReplacement) { - if ($functionIdentity === $functionReplacement['alternativeName']) { + if ($functionIdentity === $functionReplacement) { continue; } @@ -104,15 +99,15 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void [$functionName, $openParenthesis, $closeParenthesis] = $boundaries; $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); - if (!\in_array($count, $functionReplacement['argumentCount'], true)) { + if (!\in_array($count, self::$argumentCounts[$functionIdentity], true)) { continue 2; } // analysing cursor shift, so nested calls could be processed $currIndex = $openParenthesis; - $tokens[$functionName] = new Token([T_STRING, $functionReplacement['alternativeName']]); + $tokens[$functionName] = new Token([T_STRING, $functionReplacement]); - if (0 === $count && 'random_int' === $functionReplacement['alternativeName']) { + if (0 === $count && 'random_int' === $functionReplacement) { $tokens->insertAt($currIndex + 1, [ new Token([T_LNUMBER, '0']), new Token(','), @@ -128,30 +123,19 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('replacements', 'Mapping between replaced functions with the new ones.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $value): bool { foreach ($value as $functionName => $replacement) { if (!\array_key_exists($functionName, self::$argumentCounts)) { - throw new InvalidOptionsException(sprintf( + throw new InvalidOptionsException(\sprintf( 'Function "%s" is not handled by the fixer.', $functionName )); } - - if (!\is_string($replacement)) { - throw new InvalidOptionsException(sprintf( - 'Replacement for function "%s" must be a string, "%s" given.', - $functionName, - get_debug_type($replacement) - )); - } } return true; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php index 5ecc2bd291..daf0e2652f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Alias/SetTypeToCastFixer.php @@ -24,9 +24,6 @@ final class SetTypeToCastFixer extends AbstractFunctionReferenceFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -47,15 +44,19 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} + * + * Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer. */ + public function getPriority(): int + { + return 0; + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_STRING, T_VARIABLE]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $map = [ @@ -125,7 +126,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // --- Test type ------------------------------ - $type = strtolower(trim($tokens[$secondArgumentStart]->getContent(), '"\'"')); + $type = strtolower(trim($tokens[$secondArgumentStart]->getContent(), '"\'')); if ('null' !== $type && !isset($map[$type])) { continue; // we don't know how to map @@ -146,13 +147,17 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void ); if ('null' === $type) { - $this->findSettypeNullCall($tokens, $functionNameIndex, $argumentToken); + $this->fixSettypeNullCall($tokens, $functionNameIndex, $argumentToken); } else { + \assert(isset($map[$type])); $this->fixSettypeCall($tokens, $functionNameIndex, $argumentToken, new Token($map[$type])); } } } + /** + * @return list> + */ private function findSettypeCalls(Tokens $tokens): array { $candidates = []; @@ -215,7 +220,7 @@ private function fixSettypeCall( $tokens->removeTrailingWhitespace($functionNameIndex + 6); // 6 = number of inserted tokens -1 for offset correction } - private function findSettypeNullCall( + private function fixSettypeNullCall( Tokens $tokens, int $functionNameIndex, Token $argumentToken diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php index c878a023e9..2eab59c6c9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ArraySyntaxFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -30,33 +31,26 @@ * @author Gregor Harlan * @author Sebastiaan Stok * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * syntax?: 'long'|'short' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * syntax: 'long'|'short' + * } */ final class ArraySyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * @var null|int - */ - private $candidateTokenKind; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; /** - * @var null|string - */ - private $fixCallback; - - /** - * {@inheritdoc} + * @var CT::T_ARRAY_SQUARE_BRACE_OPEN|T_ARRAY */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->resolveCandidateTokenKind(); - $this->resolveFixCallback(); - } + private $candidateTokenKind; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -76,38 +70,36 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BinaryOperatorSpacesFixer, TernaryOperatorSpacesFixer. + * Must run before BinaryOperatorSpacesFixer, SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, TernaryOperatorSpacesFixer. */ public function getPriority(): int { - return 1; + return 37; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound($this->candidateTokenKind); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function configurePostNormalisation(): void { - $callback = $this->fixCallback; + $this->resolveCandidateTokenKind(); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { for ($index = $tokens->count() - 1; 0 <= $index; --$index) { if ($tokens[$index]->isGivenKind($this->candidateTokenKind)) { - $this->{$callback}($tokens, $index); + if ('short' === $this->configuration['syntax']) { + $this->fixToShortArraySyntax($tokens, $index); + } else { + $this->fixToLongArraySyntax($tokens, $index); + } } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -139,11 +131,6 @@ private function fixToShortArraySyntax(Tokens $tokens, int $index): void $tokens->clearTokenAndMergeSurroundingWhitespace($index); } - private function resolveFixCallback(): void - { - $this->fixCallback = sprintf('fixTo%sArraySyntax', ucfirst($this->configuration['syntax'])); - } - private function resolveCandidateTokenKind(): void { $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_ARRAY_SQUARE_BRACE_OPEN : T_ARRAY; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php index d16a6ad938..b7a888f009 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoMultilineWhitespaceAroundDoubleArrowFixer.php @@ -28,9 +28,6 @@ */ final class NoMultilineWhitespaceAroundDoubleArrowFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -42,24 +39,18 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer, TrailingCommaInMultilineFixer. + * Must run before BinaryOperatorSpacesFixer, MethodArgumentSpaceFixer. */ public function getPriority(): int { return 31; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOUBLE_ARROW); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -67,7 +58,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - $this->fixWhitespace($tokens, $index - 1); + if (!$tokens[$index - 2]->isComment() || str_starts_with($tokens[$index - 2]->getContent(), '/*')) { + $this->fixWhitespace($tokens, $index - 1); + } + // do not move anything about if there is a comment following the whitespace if (!$tokens[$index + 2]->isComment()) { $this->fixWhitespace($tokens, $index + 1); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php index 3db62645e9..6d133841a9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoTrailingCommaInSinglelineArrayFixer.php @@ -14,23 +14,21 @@ namespace PhpCsFixer\Fixer\ArrayNotation; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Tokens; -use PhpCsFixer\Tokenizer\TokensAnalyzer; /** + * @deprecated + * * @author Dariusz Rumiński * @author Sebastiaan Stok */ -final class NoTrailingCommaInSinglelineArrayFixer extends AbstractFixer +final class NoTrailingCommaInSinglelineArrayFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -39,51 +37,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); - } - - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void - { - $tokensAnalyzer = new TokensAnalyzer($tokens); - - for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { - if ($tokensAnalyzer->isArray($index)) { - $this->fixArray($tokens, $index); - } - } + return array_keys($this->proxyFixers); } - private function fixArray(Tokens $tokens, int $index): void + protected function createProxyFixers(): array { - $tokensAnalyzer = new TokensAnalyzer($tokens); - - if ($tokensAnalyzer->isArrayMultiLine($index)) { - return; - } - - $startIndex = $index; - - if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { - $startIndex = $tokens->getNextTokenOfKind($startIndex, ['(']); - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); - } else { - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); - } - - $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); - $beforeEndToken = $tokens[$beforeEndIndex]; + $fixer = new NoTrailingCommaInSinglelineFixer(); + $fixer->configure(['elements' => ['array']]); - if ($beforeEndToken->equals(',')) { - $tokens->removeTrailingWhitespace($beforeEndIndex); - $tokens->clearAt($beforeEndIndex); - } + return [$fixer]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php index c3eb4fd1ac..e5e7548945 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NoWhitespaceBeforeCommaInArrayFixer.php @@ -16,82 +16,75 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Tokens; -use Symfony\Component\OptionsResolver\Options; /** * @author Adam Marczuk + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * after_heredoc?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * after_heredoc: bool + * } */ final class NoWhitespaceBeforeCommaInArrayFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'In array declaration, there MUST NOT be a whitespace before each comma.', [ new CodeSample(" true] ), ] ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - for ($index = $tokens->count() - 1; $index >= 0; --$index) { + for ($index = $tokens->count() - 1; $index > 0; --$index) { if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $this->fixSpacing($index, $tokens); } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.')) ->setAllowedTypes(['bool']) ->setDefault(false) - ->setNormalizer(static function (Options $options, $value) { - return $value; - }) ->getOption(), ]); } @@ -125,8 +118,6 @@ private function fixSpacing(int $index, Tokens $tokens): void /** * Method to move index over the non-array elements like function calls or function declarations. - * - * @return int New index */ private function skipNonArrayElements(int $index, Tokens $tokens): int { @@ -142,6 +133,21 @@ private function skipNonArrayElements(int $index, Tokens $tokens): int } } + if ($tokens[$index]->equals(',') && $this->commaIsPartOfImplementsList($index, $tokens)) { + --$index; + } + return $index; } + + private function commaIsPartOfImplementsList(int $index, Tokens $tokens): bool + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + + $current = $tokens[$index]; + } while ($current->isGivenKind(T_STRING) || $current->equals(',')); + + return $current->isGivenKind(T_IMPLEMENTS); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php index f4d7652559..3d5f561609 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/NormalizeIndexBraceFixer.php @@ -15,9 +15,10 @@ namespace PhpCsFixer\Fixer\ArrayNotation; use PhpCsFixer\AbstractFixer; -use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -27,28 +28,22 @@ */ final class NormalizeIndexBraceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Array index should always be written by using square braces.', - [new CodeSample("isTokenKindFound(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ReturnToYieldFromFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ReturnToYieldFromFixer.php new file mode 100644 index 0000000000..4b130a2782 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/ReturnToYieldFromFixer.php @@ -0,0 +1,105 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class ReturnToYieldFromFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'If the function explicitly returns an array, and has the return type `iterable`, then `yield from` must be used instead of `return`.', + [new CodeSample('isAllTokenKindsFound([T_FUNCTION, T_RETURN]) && $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + } + + /** + * {@inheritdoc} + * + * Must run before YieldFromArrayToYieldsFixer. + * Must run after PhpUnitDataProviderReturnTypeFixer, PhpdocToReturnTypeFixer. + */ + public function getPriority(): int + { + return 1; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens->findGivenKind(T_RETURN) as $index => $token) { + if (!$this->shouldBeFixed($tokens, $index)) { + continue; + } + + $tokens[$index] = new Token([T_YIELD_FROM, 'yield from']); + } + } + + private function shouldBeFixed(Tokens $tokens, int $returnIndex): bool + { + $arrayStartIndex = $tokens->getNextMeaningfulToken($returnIndex); + if (!$tokens[$arrayStartIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + return false; + } + + if ($tokens[$arrayStartIndex]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $arrayEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $arrayStartIndex); + } else { + $arrayOpenParenthesisIndex = $tokens->getNextTokenOfKind($arrayStartIndex, ['(']); + $arrayEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $arrayOpenParenthesisIndex); + } + + $functionEndIndex = $arrayEndIndex; + do { + $functionEndIndex = $tokens->getNextMeaningfulToken($functionEndIndex); + } while (null !== $functionEndIndex && $tokens[$functionEndIndex]->equals(';')); + if (null === $functionEndIndex || !$tokens[$functionEndIndex]->equals('}')) { + return false; + } + + $functionStartIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $functionEndIndex); + + $returnTypeIndex = $tokens->getPrevMeaningfulToken($functionStartIndex); + if (!$tokens[$returnTypeIndex]->isGivenKind(T_STRING)) { + return false; + } + + if ('iterable' !== strtolower($tokens[$returnTypeIndex]->getContent())) { + return false; + } + + $beforeReturnTypeIndex = $tokens->getPrevMeaningfulToken($returnTypeIndex); + + return $tokens[$beforeReturnTypeIndex]->isGivenKind(CT::T_TYPE_COLON); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php index d2e19bfb7a..775e912591 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/TrimArraySpacesFixer.php @@ -26,9 +26,6 @@ */ final class TrimArraySpacesFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,21 +34,15 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = 0, $c = $tokens->count(); $index < $c; ++$index) { - if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN])) { self::fixArray($tokens, $index); } } @@ -67,6 +58,8 @@ private static function fixArray(Tokens $tokens, int $index): void if ($tokens[$startIndex]->isGivenKind(T_ARRAY)) { $startIndex = $tokens->getNextMeaningfulToken($startIndex); $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } elseif ($tokens[$startIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE, $startIndex); } else { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php index 8fed4a16b2..88cd0724d3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/WhitespaceAfterCommaInArrayFixer.php @@ -15,40 +15,62 @@ namespace PhpCsFixer\Fixer\ArrayNotation; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Adam Marczuk + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * ensure_single_space?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * ensure_single_space: bool + * } */ -final class WhitespaceAfterCommaInArrayFixer extends AbstractFixer +final class WhitespaceAfterCommaInArrayFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'In array declaration, there MUST be a whitespace after each comma.', - [new CodeSample(" true]), + ] ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); } - /** - * {@inheritdoc} - */ + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('ensure_single_space', 'If there are only horizontal whitespaces after the comma then ensure it is a single space.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensToInsert = []; @@ -68,8 +90,18 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void for ($i = $endIndex - 1; $i > $startIndex; --$i) { $i = $this->skipNonArrayElements($i, $tokens); - if ($tokens[$i]->equals(',') && !$tokens[$i + 1]->isWhitespace()) { + if (!$tokens[$i]->equals(',')) { + continue; + } + if (!$tokens[$i + 1]->isWhitespace()) { $tokensToInsert[$i + 1] = new Token([T_WHITESPACE, ' ']); + } elseif ( + true === $this->configuration['ensure_single_space'] + && ' ' !== $tokens[$i + 1]->getContent() + && Preg::match('/^\h+$/', $tokens[$i + 1]->getContent()) + && (!$tokens[$i + 2]->isComment() || Preg::match('/^\h+$/', $tokens[$i + 3]->getContent())) + ) { + $tokens[$i + 1] = new Token([T_WHITESPACE, ' ']); } } } @@ -98,6 +130,21 @@ private function skipNonArrayElements(int $index, Tokens $tokens): int } } + if ($tokens[$index]->equals(',') && $this->commaIsPartOfImplementsList($index, $tokens)) { + --$index; + } + return $index; } + + private function commaIsPartOfImplementsList(int $index, Tokens $tokens): bool + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + + $current = $tokens[$index]; + } while ($current->isGivenKind(T_STRING) || $current->equals(',')); + + return $current->isGivenKind(T_IMPLEMENTS); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/YieldFromArrayToYieldsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/YieldFromArrayToYieldsFixer.php new file mode 100644 index 0000000000..021d1cd161 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ArrayNotation/YieldFromArrayToYieldsFixer.php @@ -0,0 +1,189 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ArrayNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class YieldFromArrayToYieldsFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Yield from array must be unpacked to series of yields.', + [ + new CodeSample('isTokenKindFound(T_YIELD_FROM); + } + + /** + * {@inheritdoc} + * + * Must run before BlankLineBeforeStatementFixer, NoExtraBlankLinesFixer, NoMultipleStatementsPerLineFixer, NoWhitespaceInBlankLineFixer, StatementIndentationFixer. + * Must run after ReturnToYieldFromFixer. + */ + public function getPriority(): int + { + return 0; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + /** + * @var array $inserts + */ + $inserts = []; + + foreach ($this->getYieldsFromToUnpack($tokens) as $index => [$startIndex, $endIndex]) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + if ($tokens[$startIndex]->equals('(')) { + $prevStartIndex = $tokens->getPrevMeaningfulToken($startIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($prevStartIndex); // clear `array` from `array(` + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($startIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($endIndex); + + $arrayHasTrailingComma = false; + + $startIndex = $tokens->getNextMeaningfulToken($startIndex); + + $inserts[$startIndex] = [new Token([T_YIELD, 'yield']), new Token([T_WHITESPACE, ' '])]; + + foreach ($this->findArrayItemCommaIndex( + $tokens, + $startIndex, + $tokens->getPrevMeaningfulToken($endIndex), + ) as $commaIndex) { + $nextItemIndex = $tokens->getNextMeaningfulToken($commaIndex); + + if ($nextItemIndex < $endIndex) { + $inserts[$nextItemIndex] = [new Token([T_YIELD, 'yield']), new Token([T_WHITESPACE, ' '])]; + $tokens[$commaIndex] = new Token(';'); + } else { + $arrayHasTrailingComma = true; + // array has trailing comma - we replace it with `;` (as it's best fit to put it) + $tokens[$commaIndex] = new Token(';'); + } + } + + // there was a trailing comma, so we do not need original `;` after initial array structure + if (true === $arrayHasTrailingComma) { + $tokens->clearTokenAndMergeSurroundingWhitespace($tokens->getNextMeaningfulToken($endIndex)); + } + } + + $tokens->insertSlices($inserts); + } + + /** + * @return iterable + */ + private function getYieldsFromToUnpack(Tokens $tokens): iterable + { + $tokensCount = $tokens->count(); + $index = 0; + while (++$index < $tokensCount) { + if (!$tokens[$index]->isGivenKind(T_YIELD_FROM)) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->equalsAny([';', '{', '}', [T_OPEN_TAG]])) { + continue; + } + + $arrayStartIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$arrayStartIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + continue; + } + + if ($tokens[$arrayStartIndex]->isGivenKind(T_ARRAY)) { + $startIndex = $tokens->getNextTokenOfKind($arrayStartIndex, ['(']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); + } else { + $startIndex = $arrayStartIndex; + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex); + } + + // is there empty "yield from []" ? + if ($endIndex === $tokens->getNextMeaningfulToken($startIndex)) { + continue; + } + + // is there any nested "yield from"? + if ([] !== $tokens->findGivenKind(T_YIELD_FROM, $startIndex, $endIndex)) { + continue; + } + + yield $index => [$startIndex, $endIndex]; + } + } + + /** + * @return iterable + */ + private function findArrayItemCommaIndex(Tokens $tokens, int $startIndex, int $endIndex): iterable + { + for ($index = $startIndex; $index <= $endIndex; ++$index) { + $token = $tokens[$index]; + + // skip nested (), [], {} constructs + $blockDefinitionProbe = Tokens::detectBlockType($token); + + if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) { + $index = $tokens->findBlockEnd($blockDefinitionProbe['type'], $index); + + continue; + } + + if (!$tokens[$index]->equals(',')) { + continue; + } + + yield $index; + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/AttributeEmptyParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/AttributeEmptyParenthesesFixer.php new file mode 100644 index 0000000000..8166a0f29d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/AttributeEmptyParenthesesFixer.php @@ -0,0 +1,136 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\AttributeNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author HypeMC + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * use_parentheses?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * use_parentheses: bool + * } + */ +final class AttributeEmptyParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHP attributes declared without arguments must (not) be followed by empty parentheses.', + [ + new CodeSample(" true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \defined('T_ATTRIBUTE') && $tokens->isTokenKindFound(T_ATTRIBUTE); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('use_parentheses', 'Whether attributes should be followed by parentheses or not.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $index = 0; + + while (null !== $index = $tokens->getNextTokenOfKind($index, [[T_ATTRIBUTE]])) { + $nextIndex = $index; + + do { + $parenthesesIndex = $tokens->getNextTokenOfKind($nextIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]); + + if (true === $this->configuration['use_parentheses']) { + $this->ensureParenthesesAt($tokens, $parenthesesIndex); + } else { + $this->ensureNoParenthesesAt($tokens, $parenthesesIndex); + } + + $nextIndex = $tokens->getNextTokenOfKind($nextIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]); + + // Find closing parentheses, we need to do this in case there's a comma inside the parentheses + if ($tokens[$nextIndex]->equals('(')) { + $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); + $nextIndex = $tokens->getNextTokenOfKind($nextIndex, [',', [CT::T_ATTRIBUTE_CLOSE]]); + } + + // In case there's a comma right before T_ATTRIBUTE_CLOSE + if (!$tokens[$nextIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + } while (!$tokens[$nextIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)); + } + } + + private function ensureParenthesesAt(Tokens $tokens, int $index): void + { + if ($tokens[$index]->equals('(')) { + return; + } + + $tokens->insertAt( + $tokens->getPrevMeaningfulToken($index) + 1, + [new Token('('), new Token(')')] + ); + } + + private function ensureNoParenthesesAt(Tokens $tokens, int $index): void + { + if (!$tokens[$index]->equals('(')) { + return; + } + + $closingIndex = $tokens->getNextMeaningfulToken($index); + + // attribute has arguments - parentheses can not be removed + if (!$tokens[$closingIndex]->equals(')')) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/OrderedAttributesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/OrderedAttributesFixer.php new file mode 100644 index 0000000000..8cd58ebc6d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/AttributeNotation/OrderedAttributesFixer.php @@ -0,0 +1,325 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\AttributeNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\AttributeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author HypeMC + * + * @phpstan-import-type _AttributeItems from AttributeAnalysis + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * order?: list, + * sort_algorithm?: 'alpha'|'custom' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * order: array, + * sort_algorithm: 'alpha'|'custom' + * } + */ +final class OrderedAttributesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public const ORDER_ALPHA = 'alpha'; + public const ORDER_CUSTOM = 'custom'; + + private const SUPPORTED_SORT_ALGORITHMS = [ + self::ORDER_ALPHA, + self::ORDER_CUSTOM, + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Sorts attributes using the configured sort algorithm.', + [ + new VersionSpecificCodeSample( + <<<'EOL' + self::ORDER_CUSTOM, 'order' => ['A\B\Qux', 'A\B\Bar', 'A\B\Corge']], + ), + ], + ); + } + + /** + * {@inheritdoc} + * + * Must run after FullyQualifiedStrictTypesFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return \defined('T_ATTRIBUTE') && $tokens->isTokenKindFound(T_ATTRIBUTE); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $fixerName = $this->getName(); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sort_algorithm', 'How the attributes should be sorted.')) + ->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS) + ->setDefault(self::ORDER_ALPHA) + ->setNormalizer(static function (Options $options, string $value) use ($fixerName): string { + if (self::ORDER_CUSTOM === $value && [] === $options['order']) { + throw new InvalidFixerConfigurationException( + $fixerName, + 'The custom order strategy requires providing `order` option with a list of attributes\'s FQNs.' + ); + } + + return $value; + }) + ->getOption(), + (new FixerOptionBuilder('order', 'A list of FQCNs of attributes defining the desired order used when custom sorting algorithm is configured.')) + ->setAllowedTypes(['string[]']) + ->setDefault([]) + ->setNormalizer(static function (Options $options, array $value) use ($fixerName): array { + if ($value !== array_unique($value)) { + throw new InvalidFixerConfigurationException($fixerName, 'The list includes attributes that are not unique.'); + } + + return array_flip(array_values( + array_map(static fn (string $attribute): string => ltrim($attribute, '\\'), $value), + )); + }) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $index = 0; + + while (null !== $index = $tokens->getNextTokenOfKind($index, [[T_ATTRIBUTE]])) { + /** @var list $elements */ + $elements = array_map(function (AttributeAnalysis $attributeAnalysis) use ($tokens): array { + return [ + 'name' => $this->sortAttributes($tokens, $attributeAnalysis->getStartIndex(), $attributeAnalysis->getAttributes()), + 'start' => $attributeAnalysis->getStartIndex(), + 'end' => $attributeAnalysis->getEndIndex(), + ]; + }, AttributeAnalyzer::collect($tokens, $index)); + + $endIndex = end($elements)['end']; + + try { + if (1 === \count($elements)) { + continue; + } + + $sortedElements = $this->sortElements($elements); + + if ($elements === $sortedElements) { + continue; + } + + $this->sortTokens($tokens, $index, $endIndex, $sortedElements); + } finally { + $index = $endIndex; + } + } + } + + /** + * @param _AttributeItems $attributes + */ + private function sortAttributes(Tokens $tokens, int $index, array $attributes): string + { + if (1 === \count($attributes)) { + return $this->getAttributeName($tokens, $attributes[0]['name'], $attributes[0]['start']); + } + + foreach ($attributes as &$attribute) { + $attribute['name'] = $this->getAttributeName($tokens, $attribute['name'], $attribute['start']); + } + + $sortedElements = $this->sortElements($attributes); + + if ($attributes === $sortedElements) { + return $attributes[0]['name']; + } + + $this->sortTokens($tokens, $index + 1, end($attributes)['end'], $sortedElements, new Token(',')); + + return $sortedElements[0]['name']; + } + + private function getAttributeName(Tokens $tokens, string $name, int $index): string + { + if (self::ORDER_CUSTOM === $this->configuration['sort_algorithm']) { + $name = $this->determineAttributeFullyQualifiedName($tokens, $name, $index); + } + + return ltrim($name, '\\'); + } + + private function determineAttributeFullyQualifiedName(Tokens $tokens, string $name, int $index): string + { + if ('\\' === $name[0]) { + return $name; + } + + if (!$tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + $index = $tokens->getNextTokenOfKind($index, [[T_STRING], [T_NS_SEPARATOR]]); + } + + [$namespaceAnalysis, $namespaceUseAnalyses] = $this->collectNamespaceAnalysis($tokens, $index); + $namespace = $namespaceAnalysis->getFullName(); + $firstTokenOfName = $tokens[$index]->getContent(); + $namespaceUseAnalysis = $namespaceUseAnalyses[$firstTokenOfName] ?? false; + + if ($namespaceUseAnalysis instanceof NamespaceUseAnalysis) { + $namespace = $namespaceUseAnalysis->getFullName(); + + if ($name === $firstTokenOfName) { + return $namespace; + } + + $name = substr(strstr($name, '\\'), 1); + } + + return $namespace.'\\'.$name; + } + + /** + * @param list $elements + * + * @return list + */ + private function sortElements(array $elements): array + { + usort($elements, function (array $a, array $b): int { + $sortAlgorithm = $this->configuration['sort_algorithm']; + + if (self::ORDER_ALPHA === $sortAlgorithm) { + return $a['name'] <=> $b['name']; + } + + if (self::ORDER_CUSTOM === $sortAlgorithm) { + return + ($this->configuration['order'][$a['name']] ?? PHP_INT_MAX) + <=> + ($this->configuration['order'][$b['name']] ?? PHP_INT_MAX); + } + + throw new \InvalidArgumentException(\sprintf('Invalid sort algorithm "%s" provided.', $sortAlgorithm)); + }); + + return $elements; + } + + /** + * @param list $elements + */ + private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements, ?Token $delimiter = null): void + { + $replaceTokens = []; + + foreach ($elements as $pos => $element) { + for ($i = $element['start']; $i <= $element['end']; ++$i) { + $replaceTokens[] = clone $tokens[$i]; + } + if (null !== $delimiter && $pos !== \count($elements) - 1) { + $replaceTokens[] = clone $delimiter; + } + } + + $tokens->overrideRange($startIndex, $endIndex, $replaceTokens); + } + + /** + * @return array{NamespaceAnalysis, array} + */ + private function collectNamespaceAnalysis(Tokens $tokens, int $startIndex): array + { + $namespaceAnalysis = (new NamespacesAnalyzer())->getNamespaceAt($tokens, $startIndex); + $namespaceUseAnalyses = (new NamespaceUsesAnalyzer())->getDeclarationsInNamespace($tokens, $namespaceAnalysis); + + $uses = []; + foreach ($namespaceUseAnalyses as $use) { + if (!$use->isClass()) { + continue; + } + + $uses[$use->getShortName()] = $use; + } + + return [$namespaceAnalysis, $uses]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php index f0dd9824ca..cc1b14dc0e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesFixer.php @@ -16,8 +16,14 @@ use PhpCsFixer\AbstractProxyFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\ControlStructure\ControlStructureBracesFixer; use PhpCsFixer\Fixer\ControlStructure\ControlStructureContinuationPositionFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\Fixer\LanguageConstruct\DeclareParenthesesFixer; +use PhpCsFixer\Fixer\LanguageConstruct\SingleSpaceAroundConstructFixer; +use PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer; +use PhpCsFixer\Fixer\Whitespace\StatementIndentationFixer; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -25,20 +31,36 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; -use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; -use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * Fixer for rules defined in PSR2 ¶4.1, ¶4.4, ¶5. * * @author Dariusz Rumiński + * + * @deprecated + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * allow_single_line_anonymous_class_with_empty_body?: bool, + * allow_single_line_closure?: bool, + * position_after_anonymous_constructs?: 'next'|'same', + * position_after_control_structures?: 'next'|'same', + * position_after_functions_and_oop_constructs?: 'next'|'same' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * allow_single_line_anonymous_class_with_empty_body: bool, + * allow_single_line_closure: bool, + * position_after_anonymous_constructs: 'next'|'same', + * position_after_control_structures: 'next'|'same', + * position_after_functions_and_oop_constructs: 'next'|'same' + * } */ -final class BracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +final class BracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface, DeprecatedFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -50,13 +72,15 @@ final class BracesFixer extends AbstractProxyFixer implements ConfigurableFixerI public const LINE_SAME = 'same'; /** - * @var null|ControlStructureContinuationPositionFixer + * @var null|BracesPositionFixer */ - private $controlStructureContinuationPositionFixer; + private $bracesPositionFixer; /** - * {@inheritdoc} + * @var null|ControlStructureContinuationPositionFixer */ + private $controlStructureContinuationPositionFixer; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -136,17 +160,30 @@ public function bar($baz) /** * {@inheritdoc} * - * Must run before ArrayIndentationFixer, MethodArgumentSpaceFixer, MethodChainingIndentationFixer. - * Must run after ClassAttributesSeparationFixer, ClassDefinitionFixer, ElseifFixer, EmptyLoopBodyFixer, LineEndingFixer, NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUselessElseFixer, SingleLineThrowFixer, SingleSpaceAfterConstructFixer, SingleTraitInsertPerStatementFixer. + * Must run before HeredocIndentationFixer. + * Must run after ClassAttributesSeparationFixer, ClassDefinitionFixer, EmptyLoopBodyFixer, NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUselessElseFixer, SingleLineThrowFixer, SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, SingleTraitInsertPerStatementFixer. */ public function getPriority(): int { return 35; } - public function configure(array $configuration = null): void + public function getSuccessorsNames(): array { - parent::configure($configuration); + return array_keys($this->proxyFixers); + } + + protected function configurePostNormalisation(): void + { + $this->getBracesPositionFixer()->configure([ + 'control_structures_opening_brace' => $this->translatePositionOption($this->configuration['position_after_control_structures']), + 'functions_opening_brace' => $this->translatePositionOption($this->configuration['position_after_functions_and_oop_constructs']), + 'anonymous_functions_opening_brace' => $this->translatePositionOption($this->configuration['position_after_anonymous_constructs']), + 'classes_opening_brace' => $this->translatePositionOption($this->configuration['position_after_functions_and_oop_constructs']), + 'anonymous_classes_opening_brace' => $this->translatePositionOption($this->configuration['position_after_anonymous_constructs']), + 'allow_single_line_empty_anonymous_classes' => $this->configuration['allow_single_line_anonymous_class_with_empty_body'], + 'allow_single_line_anonymous_functions' => $this->configuration['allow_single_line_closure'], + ]); $this->getControlStructureContinuationPositionFixer()->configure([ 'position' => self::LINE_NEXT === $this->configuration['position_after_control_structures'] @@ -155,31 +192,6 @@ public function configure(array $configuration = null): void ]); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool - { - return true; - } - - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void - { - $this->fixCommentBeforeBrace($tokens); - $this->fixMissingControlBraces($tokens); - $this->fixIndents($tokens); - $this->fixSpaceAroundToken($tokens); - $this->fixDoWhile($tokens); - - parent::applyFix($file, $tokens); - } - - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -191,15 +203,15 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), - (new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).')) + (new FixerOptionBuilder('position_after_functions_and_oop_constructs', 'Whether the opening brace should be placed on "next" or "same" line after classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions).')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_NEXT) ->getOption(), - (new FixerOptionBuilder('position_after_control_structures', 'whether the opening brace should be placed on "next" or "same" line after control structures.')) + (new FixerOptionBuilder('position_after_control_structures', 'Whether the opening brace should be placed on "next" or "same" line after control structures.')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_SAME) ->getOption(), - (new FixerOptionBuilder('position_after_anonymous_constructs', 'whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).')) + (new FixerOptionBuilder('position_after_anonymous_constructs', 'Whether the opening brace should be placed on "next" or "same" line after anonymous constructs (anonymous classes and lambda functions).')) ->setAllowedValues([self::LINE_NEXT, self::LINE_SAME]) ->setDefault(self::LINE_SAME) ->getOption(), @@ -208,799 +220,55 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn protected function createProxyFixers(): array { + $singleSpaceAroundConstructFixer = new SingleSpaceAroundConstructFixer(); + $singleSpaceAroundConstructFixer->configure([ + 'constructs_contain_a_single_space' => [], + 'constructs_followed_by_a_single_space' => ['elseif', 'for', 'foreach', 'if', 'match', 'while', 'use_lambda'], + 'constructs_preceded_by_a_single_space' => ['use_lambda'], + ]); + + $noExtraBlankLinesFixer = new NoExtraBlankLinesFixer(); + $noExtraBlankLinesFixer->configure([ + 'tokens' => ['curly_brace_block'], + ]); + return [ + $singleSpaceAroundConstructFixer, + new ControlStructureBracesFixer(), + $noExtraBlankLinesFixer, + $this->getBracesPositionFixer(), $this->getControlStructureContinuationPositionFixer(), new DeclareParenthesesFixer(), + new NoMultipleStatementsPerLineFixer(), + new StatementIndentationFixer(true), ]; } - private function fixCommentBeforeBrace(Tokens $tokens): void + private function getBracesPositionFixer(): BracesPositionFixer { - $tokensAnalyzer = new TokensAnalyzer($tokens); - $controlTokens = $this->getControlTokens(); - - for ($index = $tokens->count() - 1; 0 <= $index; --$index) { - $token = $tokens[$index]; - - if ($token->isGivenKind($controlTokens)) { - $prevIndex = $this->findParenthesisEnd($tokens, $index); - } elseif ( - ($token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index)) - || ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) - ) { - $prevIndex = $tokens->getNextTokenOfKind($index, ['{']); - $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); - } else { - continue; - } - - $commentIndex = $tokens->getNextNonWhitespace($prevIndex); - $commentToken = $tokens[$commentIndex]; - - if (!$commentToken->isGivenKind(T_COMMENT) || str_starts_with($commentToken->getContent(), '/*')) { - continue; - } - - $braceIndex = $tokens->getNextMeaningfulToken($commentIndex); - $braceToken = $tokens[$braceIndex]; - - if (!$braceToken->equals('{')) { - continue; - } - - /** @var Token $tokenTmp */ - $tokenTmp = $tokens[$braceIndex]; - - $newBraceIndex = $prevIndex + 1; - for ($i = $braceIndex; $i > $newBraceIndex; --$i) { - // we might be moving one white space next to another, these have to be merged - /** @var Token $previousToken */ - $previousToken = $tokens[$i - 1]; - $tokens[$i] = $previousToken; - if ($tokens[$i]->isWhitespace() && $tokens[$i + 1]->isWhitespace()) { - $tokens[$i] = new Token([T_WHITESPACE, $tokens[$i]->getContent().$tokens[$i + 1]->getContent()]); - $tokens->clearAt($i + 1); - } - } - - $tokens[$newBraceIndex] = $tokenTmp; - $c = $tokens[$braceIndex]->getContent(); - if (substr_count($c, "\n") > 1) { - // left trim till last line break - $tokens[$braceIndex] = new Token([T_WHITESPACE, substr($c, strrpos($c, "\n"))]); - } + if (null === $this->bracesPositionFixer) { + $this->bracesPositionFixer = new BracesPositionFixer(); } - } - - private function fixDoWhile(Tokens $tokens): void - { - for ($index = \count($tokens) - 1; 0 <= $index; --$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind(T_DO)) { - continue; - } - - $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); - $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); - $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); - $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($endBraceIndex); - $nextNonWhitespaceToken = $tokens[$nextNonWhitespaceIndex]; - if (!$nextNonWhitespaceToken->isGivenKind(T_WHILE)) { - continue; - } - - $tokens->ensureWhitespaceAtIndex($nextNonWhitespaceIndex - 1, 1, ' '); - } + return $this->bracesPositionFixer; } - private function fixIndents(Tokens $tokens): void - { - $classyTokens = Token::getClassyTokenKinds(); - $classyAndFunctionTokens = array_merge([T_FUNCTION], $classyTokens); - $controlTokens = $this->getControlTokens(); - $indentTokens = array_filter( - array_merge($classyAndFunctionTokens, $controlTokens), - static function (int $item): bool { - return T_SWITCH !== $item; - } - ); - $tokensAnalyzer = new TokensAnalyzer($tokens); - - for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { - $token = $tokens[$index]; - - // if token is not a structure element - continue - if (!$token->isGivenKind($indentTokens)) { - continue; - } - - // do not change indent for `while` in `do ... while ...` - if ( - $token->isGivenKind(T_WHILE) - && $tokensAnalyzer->isWhilePartOfDoWhile($index) - ) { - continue; - } - - if ( - $this->configuration['allow_single_line_anonymous_class_with_empty_body'] - && $token->isGivenKind(T_CLASS) - ) { - $prevIndex = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prevIndex]->isGivenKind(T_NEW)) { - $braceStartIndex = $tokens->getNextTokenOfKind($index, ['{']); - $braceEndIndex = $tokens->getNextMeaningfulToken($braceStartIndex); - - if ('}' === $tokens[$braceEndIndex]->getContent() && !$this->isMultilined($tokens, $index, $braceEndIndex)) { - $index = $braceEndIndex; - - continue; - } - } - } - - if ( - $this->configuration['allow_single_line_closure'] - && $token->isGivenKind(T_FUNCTION) - && $tokensAnalyzer->isLambda($index) - ) { - $braceEndIndex = $tokens->findBlockEnd( - Tokens::BLOCK_TYPE_CURLY_BRACE, - $tokens->getNextTokenOfKind($index, ['{']) - ); - - if (!$this->isMultilined($tokens, $index, $braceEndIndex)) { - $index = $braceEndIndex; - - continue; - } - } - - if ($token->isGivenKind($classyAndFunctionTokens)) { - $startBraceIndex = $tokens->getNextTokenOfKind($index, [';', '{']); - $startBraceToken = $tokens[$startBraceIndex]; - } else { - $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); - $startBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex); - $startBraceToken = $tokens[$startBraceIndex]; - } - - // structure without braces block - nothing to do, e.g. do { } while (true); - if (!$startBraceToken->equals('{')) { - continue; - } - - $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($startBraceIndex, " \t"); - $nextNonWhitespace = $tokens[$nextNonWhitespaceIndex]; - - /* if CLOSE_TAG is after { on the same line, do not indent. e.g. */ - if ($nextNonWhitespace->isGivenKind(T_CLOSE_TAG)) { - continue; - } - - /* if CLOSE_TAG is after { on the next line and a comment on this line, do not indent. e.g. */ - if ($nextNonWhitespace->isComment() && $tokens[$tokens->getNextMeaningfulToken($nextNonWhitespaceIndex)]->isGivenKind(T_CLOSE_TAG)) { - continue; - } - - $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); - - $indent = WhitespacesAnalyzer::detectIndent($tokens, $index); - - // fix indent near closing brace - $tokens->ensureWhitespaceAtIndex($endBraceIndex - 1, 1, $this->whitespacesConfig->getLineEnding().$indent); - - // fix indent between braces - $lastCommaIndex = $tokens->getPrevTokenOfKind($endBraceIndex - 1, [';', '}']); - - $nestLevel = 1; - for ($nestIndex = $lastCommaIndex; $nestIndex >= $startBraceIndex; --$nestIndex) { - $nestToken = $tokens[$nestIndex]; - - if ($nestToken->equalsAny([')', [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]])) { - $nestIndex = $tokens->findBlockStart( - $nestToken->equals(')') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, - $nestIndex - ); - - continue; - } - - if (1 === $nestLevel) { - // Next token is the beginning of a line that can be indented when - // the current token is a `;`, a `}` or the opening `{` of current - // scope. Current token may also be a comment that follows `;` or - // `}`, in which case indentation will only be fixed if this - // comment is followed by a newline. - $nextLineCanBeIndented = false; - if ($nestToken->equalsAny([';', '}'])) { - $nextLineCanBeIndented = true; - } elseif ($this->isCommentWithFixableIndentation($tokens, $nestIndex)) { - for ($i = $nestIndex; $i > $startBraceIndex; --$i) { - if ($tokens[$i]->equalsAny([';', '}'])) { - $nextLineCanBeIndented = true; - - break; - } - - if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { - break; - } - } - - if ($nextLineCanBeIndented || $i === $startBraceIndex) { - $nextToken = $tokens[$nestIndex + 1]; - $nextLineCanBeIndented = $nextToken->isWhitespace() && 1 === Preg::match('/\R/', $nextToken->getContent()); - } - } - - if (!$nextLineCanBeIndented) { - continue; - } - - $nextNonWhitespaceNestIndex = $tokens->getNextNonWhitespace($nestIndex); - $nextNonWhitespaceNestToken = $tokens[$nextNonWhitespaceNestIndex]; - - if ( - // next Token is not a comment on its own line - !($nextNonWhitespaceNestToken->isComment() && ( - !$tokens[$nextNonWhitespaceNestIndex - 1]->isWhitespace() - || !Preg::match('/\R/', $tokens[$nextNonWhitespaceNestIndex - 1]->getContent()) - )) - // and it is not a `$foo = function () {};` situation - && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equalsAny([';', ',', ']', [CT::T_ARRAY_SQUARE_BRACE_CLOSE]])) - // and it is not a `Foo::{bar}()` situation - && !($nestToken->equals('}') && $nextNonWhitespaceNestToken->equals('(')) - // and it is not a `${"a"}->...` and `${"b{$foo}"}->...` situation - && !($nestToken->equals('}') && $tokens[$nestIndex - 1]->equalsAny(['"', "'", [T_CONSTANT_ENCAPSED_STRING], [T_VARIABLE]])) - // and next token is not a closing tag that would break heredoc/nowdoc syntax - && !($tokens[$nestIndex - 1]->isGivenKind(T_END_HEREDOC) && $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG)) - ) { - if ( - ( - self::LINE_NEXT !== $this->configuration['position_after_control_structures'] - && $nextNonWhitespaceNestToken->isGivenKind($this->getControlContinuationTokens()) - && !$tokens[$tokens->getPrevNonWhitespace($nextNonWhitespaceNestIndex)]->isComment() - ) - || $nextNonWhitespaceNestToken->isGivenKind(T_CLOSE_TAG) - || ( - self::LINE_NEXT !== $this->configuration['position_after_control_structures'] - && $nextNonWhitespaceNestToken->isGivenKind(T_WHILE) - && $tokensAnalyzer->isWhilePartOfDoWhile($nextNonWhitespaceNestIndex) - ) - ) { - $whitespace = ' '; - } else { - $nextToken = $tokens[$nestIndex + 1]; - $nextWhitespace = ''; - - if ($nextToken->isWhitespace()) { - $nextWhitespace = rtrim($nextToken->getContent(), " \t"); - - if ('' !== $nextWhitespace) { - $nextWhitespace = Preg::replace( - sprintf('/%s$/', $this->whitespacesConfig->getLineEnding()), - '', - $nextWhitespace, - 1 - ); - } - } - - $whitespace = $nextWhitespace.$this->whitespacesConfig->getLineEnding().$indent; - - if (!$nextNonWhitespaceNestToken->equals('}')) { - $determineIsIndentableBlockContent = static function (int $contentIndex) use ($tokens): bool { - if (!$tokens[$contentIndex]->isComment()) { - return true; - } - - if (!$tokens[$tokens->getPrevMeaningfulToken($contentIndex)]->equals(';')) { - return true; - } - - $nextIndex = $tokens->getNextMeaningfulToken($contentIndex); - - if (!$tokens[$nextIndex]->equals('}')) { - return true; - } - - $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); - - if (null === $nextNextIndex) { - return true; - } - - if ($tokens[$nextNextIndex]->equalsAny([ - [T_ELSE], - [T_ELSEIF], - ',', - ])) { - return false; - } - - return true; - }; - - // add extra indent only if current content is not a comment for content outside of current block - if ($determineIsIndentableBlockContent($nestIndex + 2)) { - $whitespace .= $this->whitespacesConfig->getIndent(); - } - } - } - - $this->ensureWhitespaceAtIndexAndIndentMultilineComment($tokens, $nestIndex + 1, $whitespace); - } - } - - if ($nestToken->equals('}')) { - ++$nestLevel; - - continue; - } - - if ($nestToken->equals('{')) { - --$nestLevel; - - continue; - } - } - - // fix indent near opening brace - if (isset($tokens[$startBraceIndex + 2]) && $tokens[$startBraceIndex + 2]->equals('}')) { - $tokens->ensureWhitespaceAtIndex($startBraceIndex + 1, 0, $this->whitespacesConfig->getLineEnding().$indent); - } else { - $nextToken = $tokens[$startBraceIndex + 1]; - $nextNonWhitespaceToken = $tokens[$tokens->getNextNonWhitespace($startBraceIndex)]; - - // set indent only if it is not a case, when comment is following { on same line - if ( - !$nextNonWhitespaceToken->isComment() - || ($nextToken->isWhitespace() && 1 === substr_count($nextToken->getContent(), "\n")) // preserve blank lines - ) { - $this->ensureWhitespaceAtIndexAndIndentMultilineComment( - $tokens, - $startBraceIndex + 1, - $this->whitespacesConfig->getLineEnding().$indent.$this->whitespacesConfig->getIndent() - ); - } - } - - if ($token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index)) { - if (self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment()) { - $ensuredWhitespace = ' '; - } else { - $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; - } - - $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); - } elseif ( - $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) - || ( - self::LINE_NEXT === $this->configuration['position_after_control_structures'] && $token->isGivenKind($controlTokens) - || ( - self::LINE_NEXT === $this->configuration['position_after_anonymous_constructs'] - && ( - $token->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($index) - || $token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index) - ) - ) - ) - ) { - $isAnonymousClass = $token->isGivenKind($classyTokens) && $tokensAnalyzer->isAnonymousClass($index); - - $closingParenthesisIndex = $tokens->getPrevTokenOfKind($startBraceIndex, [')']); - if (null === $closingParenthesisIndex && !$isAnonymousClass) { - continue; - } - - if ( - !$isAnonymousClass - && $tokens[$closingParenthesisIndex - 1]->isWhitespace() - && str_contains($tokens[$closingParenthesisIndex - 1]->getContent(), "\n") - ) { - if (!$tokens[$startBraceIndex - 2]->isComment()) { - $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); - } - } else { - if ( - self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] - && ( - $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) - || $token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index) - ) - && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment() - ) { - $ensuredWhitespace = ' '; - } else { - $ensuredWhitespace = $this->whitespacesConfig->getLineEnding().$indent; - } - - $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, $ensuredWhitespace); - } - } else { - $tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' '); - } - - // reset loop limit due to collection change - $limit = \count($tokens); - } - } - - private function fixMissingControlBraces(Tokens $tokens): void - { - $controlTokens = $this->getControlTokens(); - - for ($index = $tokens->count() - 1; 0 <= $index; --$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind($controlTokens)) { - continue; - } - - $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); - $nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); - $tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex]; - - // if Token after parenthesis is { then we do not need to insert brace, but to fix whitespace before it - if ($tokenAfterParenthesis->equals('{') && self::LINE_SAME === $this->configuration['position_after_control_structures']) { - $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); - - continue; - } - - // do not add braces for cases: - // - structure without block, e.g. while ($iter->next()); - // - structure with block, e.g. while ($i) {...}, while ($i) : {...} endwhile; - if ($tokenAfterParenthesis->equalsAny([';', '{', ':'])) { - continue; - } - - // do not add for 'short if' followed by alternative loop, for example: if ($a) while ($b): ? > X < ?php endwhile; ? > - // or 'short if' after an alternative loop, for example: foreach ($arr as $index => $item) if ($item): - if ($tokenAfterParenthesis->isGivenKind([T_FOR, T_FOREACH, T_SWITCH, T_WHILE, T_IF])) { - $tokenAfterParenthesisBlockEnd = $tokens->findBlockEnd( // go to ')' - Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, - $tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex) - ); - - if ($tokens[$tokens->getNextMeaningfulToken($tokenAfterParenthesisBlockEnd)]->equals(':')) { - continue; - } - } - - $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); - - // insert closing brace - $tokens->insertAt($statementEndIndex + 1, [new Token([T_WHITESPACE, ' ']), new Token('}')]); - - // insert missing `;` if needed - if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) { - $tokens->insertAt($statementEndIndex + 1, new Token(';')); - } - - // insert opening brace - $tokens->insertAt($parenthesisEndIndex + 1, new Token('{')); - $tokens->ensureWhitespaceAtIndex($parenthesisEndIndex + 1, 0, ' '); - } - } - - private function fixSpaceAroundToken(Tokens $tokens): void - { - $controlTokens = $this->getControlTokens(); - - for ($index = $tokens->count() - 1; 0 <= $index; --$index) { - $token = $tokens[$index]; - - // Declare tokens don't follow the same rules are other control statements - if ($token->isGivenKind(T_DECLARE)) { - continue; // delegated to DeclareParenthesesFixer - } - - if ($token->isGivenKind($controlTokens) || $token->isGivenKind(CT::T_USE_LAMBDA)) { - $nextNonWhitespaceIndex = $tokens->getNextNonWhitespace($index); - - if (!$tokens[$nextNonWhitespaceIndex]->equals(':')) { - $tokens->ensureWhitespaceAtIndex( - $index + 1, - 0, - self::LINE_NEXT === $this->configuration['position_after_control_structures'] && !$tokens[$nextNonWhitespaceIndex]->equals('(') ? - $this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $index) - : ' ' - ); - } - - $prevToken = $tokens[$index - 1]; - - if (!$prevToken->isWhitespace() && !$prevToken->isComment() && !$prevToken->isGivenKind(T_OPEN_TAG)) { - $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); - } - } - } - } - - private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int - { - $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); - $nextToken = $tokens[$nextIndex]; - - // return if next token is not opening parenthesis - if (!$nextToken->equals('(')) { - return $structureTokenIndex; - } - - return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); - } - - private function findStatementEnd(Tokens $tokens, int $parenthesisEndIndex): int - { - $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); - $nextToken = $tokens[$nextIndex]; - - if (!$nextToken) { - return $parenthesisEndIndex; - } - - if ($nextToken->equals('{')) { - return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex); - } - - if ($nextToken->isGivenKind($this->getControlTokens())) { - $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); - - $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); - - if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) { - $openingTokenKind = $nextToken->getId(); - - while (true) { - $nextIndex = $tokens->getNextMeaningfulToken($endIndex); - $nextToken = isset($nextIndex) ? $tokens[$nextIndex] : null; - if ($nextToken && $nextToken->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) { - $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); - - $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); - - if ($nextToken->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) { - return $endIndex; - } - } else { - break; - } - } - } - - return $endIndex; - } - - $index = $parenthesisEndIndex; - - while (true) { - $token = $tokens[++$index]; - - // if there is some block in statement (eg lambda function) we need to skip it - if ($token->equals('{')) { - $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); - - continue; - } - - if ($token->equals(';')) { - return $index; - } - - if ($token->isGivenKind(T_CLOSE_TAG)) { - return $tokens->getPrevNonWhitespace($index); - } - } - } - - private function getControlTokens(): array - { - static $tokens = [ - T_DECLARE, - T_DO, - T_ELSE, - T_ELSEIF, - T_FINALLY, - T_FOR, - T_FOREACH, - T_IF, - T_WHILE, - T_TRY, - T_CATCH, - T_SWITCH, - ]; - - // @TODO: drop condition when PHP 8.0+ is required - if (\defined('T_MATCH')) { - $tokens['match'] = T_MATCH; - } - - return $tokens; - } - - private function getControlContinuationTokens(): array - { - static $tokens = [ - T_CATCH, - T_ELSE, - T_ELSEIF, - T_FINALLY, - ]; - - return $tokens; - } - - private function getControlContinuationTokensForOpeningToken(int $openingTokenKind): array - { - if (T_IF === $openingTokenKind) { - return [ - T_ELSE, - T_ELSEIF, - ]; - } - - if (T_DO === $openingTokenKind) { - return [T_WHILE]; - } - - if (T_TRY === $openingTokenKind) { - return [ - T_CATCH, - T_FINALLY, - ]; - } - - return []; - } - - private function getFinalControlContinuationTokensForOpeningToken(int $openingTokenKind): array - { - if (T_IF === $openingTokenKind) { - return [T_ELSE]; - } - - if (T_TRY === $openingTokenKind) { - return [T_FINALLY]; - } - - return []; - } - - private function ensureWhitespaceAtIndexAndIndentMultilineComment(Tokens $tokens, int $index, string $whitespace): void - { - if ($tokens[$index]->isWhitespace()) { - $nextTokenIndex = $tokens->getNextNonWhitespace($index); - } else { - $nextTokenIndex = $index; - } - - $nextToken = $tokens[$nextTokenIndex]; - if ($nextToken->isComment()) { - $previousToken = $tokens[$nextTokenIndex - 1]; - $nextTokenContent = $nextToken->getContent(); - - // do not indent inline comments used to comment out unused code - if ( - $previousToken->isWhitespace() - && 1 === Preg::match('/\R$/', $previousToken->getContent()) - && ( - (str_starts_with($nextTokenContent, '//'.$this->whitespacesConfig->getIndent()) || '//' === $nextTokenContent) - || (str_starts_with($nextTokenContent, '#'.$this->whitespacesConfig->getIndent()) || '#' === $nextTokenContent) - ) - ) { - return; - } - - $tokens[$nextTokenIndex] = new Token([ - $nextToken->getId(), - Preg::replace( - '/(\R)'.WhitespacesAnalyzer::detectIndent($tokens, $nextTokenIndex).'(\h*\S+.*)/', - '$1'.Preg::replace('/^.*\R(\h*)$/s', '$1', $whitespace).'$2', - $nextToken->getContent() - ), - ]); - } - - $tokens->ensureWhitespaceAtIndex($index, 0, $whitespace); - } - - private function isMultilined(Tokens $tokens, int $startParenthesisIndex, int $endParenthesisIndex): bool + private function getControlStructureContinuationPositionFixer(): ControlStructureContinuationPositionFixer { - for ($i = $startParenthesisIndex; $i < $endParenthesisIndex; ++$i) { - if (str_contains($tokens[$i]->getContent(), "\n")) { - return true; - } + if (null === $this->controlStructureContinuationPositionFixer) { + $this->controlStructureContinuationPositionFixer = new ControlStructureContinuationPositionFixer(); } - return false; + return $this->controlStructureContinuationPositionFixer; } /** - * Returns whether the token at given index is a comment whose indentation - * can be fixed. - * - * Indentation of a comment is not changed when the comment is part of a - * multi-line message whose lines are all single-line comments and at least - * one line has meaningful content. + * @return BracesPositionFixer::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END|BracesPositionFixer::SAME_LINE */ - private function isCommentWithFixableIndentation(Tokens $tokens, int $index): bool - { - if (!$tokens[$index]->isComment()) { - return false; - } - - if (str_starts_with($tokens[$index]->getContent(), '/*')) { - return true; - } - - $firstCommentIndex = $index; - while (true) { - $i = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false); - if (null === $i) { - break; - } - - $firstCommentIndex = $i; - } - - $lastCommentIndex = $index; - while (true) { - $i = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true); - if (null === $i) { - break; - } - - $lastCommentIndex = $i; - } - - if ($firstCommentIndex === $lastCommentIndex) { - return true; - } - - for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) { - if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { - return false; - } - } - - return true; - } - - private function getSiblingContinuousSingleLineComment(Tokens $tokens, int $index, bool $after): ?int + private function translatePositionOption(string $option): string { - $siblingIndex = $index; - do { - $siblingIndex = $tokens->getTokenOfKindSibling($siblingIndex, $after ? 1 : -1, [[T_COMMENT]]); - - if (null === $siblingIndex) { - return null; - } - } while (str_starts_with($tokens[$siblingIndex]->getContent(), '/*')); - - $newLines = 0; - for ($i = min($siblingIndex, $index) + 1, $max = max($siblingIndex, $index); $i < $max; ++$i) { - if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) { - if (1 === $newLines || Preg::match('/\R.*\R/', $tokens[$i]->getContent())) { - return null; - } - - ++$newLines; - } - } - - return $siblingIndex; - } - - private function getControlStructureContinuationPositionFixer(): ControlStructureContinuationPositionFixer - { - if (null === $this->controlStructureContinuationPositionFixer) { - $this->controlStructureContinuationPositionFixer = new ControlStructureContinuationPositionFixer(); - } - - return $this->controlStructureContinuationPositionFixer; + return self::LINE_NEXT === $option + ? BracesPositionFixer::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END + : BracesPositionFixer::SAME_LINE; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesPositionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesPositionFixer.php new file mode 100644 index 0000000000..f010a1ddab --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/BracesPositionFixer.php @@ -0,0 +1,437 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * allow_single_line_anonymous_functions?: bool, + * allow_single_line_empty_anonymous_classes?: bool, + * anonymous_classes_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * anonymous_functions_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * classes_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * control_structures_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * functions_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * allow_single_line_anonymous_functions: bool, + * allow_single_line_empty_anonymous_classes: bool, + * anonymous_classes_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * anonymous_functions_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * classes_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * control_structures_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * functions_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line' + * } + */ +final class BracesPositionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + use Indentation; + + /** + * @internal + */ + public const NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END = 'next_line_unless_newline_at_signature_end'; + + /** + * @internal + */ + public const SAME_LINE = 'same_line'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Braces must be placed as configured.', + [ + new CodeSample( + ' self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END] + ), + new CodeSample( + ' self::SAME_LINE] + ), + new CodeSample( + ' self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END] + ), + new CodeSample( + ' self::SAME_LINE] + ), + new CodeSample( + ' self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('{'); + } + + /** + * {@inheritdoc} + * + * Must run before SingleLineEmptyBodyFixer, StatementIndentationFixer. + * Must run after ControlStructureBracesFixer, NoMultipleStatementsPerLineFixer. + */ + public function getPriority(): int + { + return -2; + } + + /** @protected */ + public function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('control_structures_opening_brace', 'The position of the opening brace of control structures‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + (new FixerOptionBuilder('functions_opening_brace', 'The position of the opening brace of functions‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END) + ->getOption(), + (new FixerOptionBuilder('anonymous_functions_opening_brace', 'The position of the opening brace of anonymous functions‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + (new FixerOptionBuilder('classes_opening_brace', 'The position of the opening brace of classes‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END) + ->getOption(), + (new FixerOptionBuilder('anonymous_classes_opening_brace', 'The position of the opening brace of anonymous classes‘ body.')) + ->setAllowedValues([self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END, self::SAME_LINE]) + ->setDefault(self::SAME_LINE) + ->getOption(), + (new FixerOptionBuilder('allow_single_line_empty_anonymous_classes', 'Allow anonymous classes to have opening and closing braces on the same line.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('allow_single_line_anonymous_functions', 'Allow anonymous functions to have opening and closing braces on the same line.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $classyTokens = Token::getClassyTokenKinds(); + $controlStructureTokens = [T_DECLARE, T_DO, T_ELSE, T_ELSEIF, T_FINALLY, T_FOR, T_FOREACH, T_IF, T_WHILE, T_TRY, T_CATCH, T_SWITCH]; + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_MATCH')) { + $controlStructureTokens[] = T_MATCH; + } + + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $allowSingleLineUntil = null; + + foreach ($tokens as $index => $token) { + $allowSingleLine = false; + $allowSingleLineIfEmpty = false; + + if ($token->isGivenKind($classyTokens)) { + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); + + if ($tokensAnalyzer->isAnonymousClass($index)) { + $allowSingleLineIfEmpty = true === $this->configuration['allow_single_line_empty_anonymous_classes']; + $positionOption = 'anonymous_classes_opening_brace'; + } else { + $positionOption = 'classes_opening_brace'; + } + } elseif ($token->isGivenKind(T_FUNCTION)) { + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + + if ($tokens[$openBraceIndex]->equals(';')) { + continue; + } + + if ($tokensAnalyzer->isLambda($index)) { + $allowSingleLine = true === $this->configuration['allow_single_line_anonymous_functions']; + $positionOption = 'anonymous_functions_opening_brace'; + } else { + $positionOption = 'functions_opening_brace'; + } + } elseif ($token->isGivenKind($controlStructureTokens)) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $openBraceIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + + if (!$tokens[$openBraceIndex]->equals('{')) { + continue; + } + + $positionOption = 'control_structures_opening_brace'; + } else { + continue; + } + + $closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openBraceIndex); + + $addNewlinesInsideBraces = true; + if ($allowSingleLine || $allowSingleLineIfEmpty || $index < $allowSingleLineUntil) { + $addNewlinesInsideBraces = false; + + for ($indexInsideBraces = $openBraceIndex + 1; $indexInsideBraces < $closeBraceIndex; ++$indexInsideBraces) { + $tokenInsideBraces = $tokens[$indexInsideBraces]; + + if ( + ($allowSingleLineIfEmpty && !$tokenInsideBraces->isWhitespace() && !$tokenInsideBraces->isComment()) + || ($tokenInsideBraces->isWhitespace() && Preg::match('/\R/', $tokenInsideBraces->getContent())) + ) { + $addNewlinesInsideBraces = true; + + break; + } + } + + if (!$addNewlinesInsideBraces && null === $allowSingleLineUntil) { + $allowSingleLineUntil = $closeBraceIndex; + } + } + + if ( + $addNewlinesInsideBraces + && !$this->isFollowedByNewLine($tokens, $openBraceIndex) + && !$this->hasCommentOnSameLine($tokens, $openBraceIndex) + && !$tokens[$tokens->getNextMeaningfulToken($openBraceIndex)]->isGivenKind(T_CLOSE_TAG) + ) { + $whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $openBraceIndex); + if ($tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, $whitespace)) { + ++$closeBraceIndex; + } + } + + $whitespace = ' '; + if (self::NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END === $this->configuration[$positionOption]) { + $whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $index); + + $previousTokenIndex = $openBraceIndex; + do { + $previousTokenIndex = $tokens->getPrevMeaningfulToken($previousTokenIndex); + } while ($tokens[$previousTokenIndex]->isGivenKind([CT::T_TYPE_COLON, CT::T_NULLABLE_TYPE, T_STRING, T_NS_SEPARATOR, CT::T_ARRAY_TYPEHINT, T_STATIC, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_CALLABLE, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE])); + + if ($tokens[$previousTokenIndex]->equals(')')) { + if ($tokens[--$previousTokenIndex]->isComment()) { + --$previousTokenIndex; + } + if ( + $tokens[$previousTokenIndex]->isWhitespace() + && Preg::match('/\R/', $tokens[$previousTokenIndex]->getContent()) + ) { + $whitespace = ' '; + } + } + } + + $moveBraceToIndex = null; + + if (' ' === $whitespace) { + $previousMeaningfulIndex = $tokens->getPrevMeaningfulToken($openBraceIndex); + for ($indexBeforeOpenBrace = $openBraceIndex - 1; $indexBeforeOpenBrace > $previousMeaningfulIndex; --$indexBeforeOpenBrace) { + if (!$tokens[$indexBeforeOpenBrace]->isComment()) { + continue; + } + + $tokenBeforeOpenBrace = $tokens[--$indexBeforeOpenBrace]; + if ($tokenBeforeOpenBrace->isWhitespace()) { + $moveBraceToIndex = $indexBeforeOpenBrace; + } elseif ($indexBeforeOpenBrace === $previousMeaningfulIndex) { + $moveBraceToIndex = $previousMeaningfulIndex + 1; + } + } + } elseif (!$tokens[$openBraceIndex - 1]->isWhitespace() || !Preg::match('/\R/', $tokens[$openBraceIndex - 1]->getContent())) { + for ($indexAfterOpenBrace = $openBraceIndex + 1; $indexAfterOpenBrace < $closeBraceIndex; ++$indexAfterOpenBrace) { + if ($tokens[$indexAfterOpenBrace]->isWhitespace() && Preg::match('/\R/', $tokens[$indexAfterOpenBrace]->getContent())) { + break; + } + + if ($tokens[$indexAfterOpenBrace]->isComment() && !str_starts_with($tokens[$indexAfterOpenBrace]->getContent(), '/*')) { + $moveBraceToIndex = $indexAfterOpenBrace + 1; + } + } + } + + if (null !== $moveBraceToIndex) { + /** @var Token $movedToken */ + $movedToken = clone $tokens[$openBraceIndex]; + + $delta = $openBraceIndex < $moveBraceToIndex ? 1 : -1; + + if ($tokens[$openBraceIndex + $delta]->isWhitespace()) { + if (-1 === $delta && Preg::match('/\R/', $tokens[$openBraceIndex - 1]->getContent())) { + $content = Preg::replace('/^(\h*?\R)?\h*/', '', $tokens[$openBraceIndex + 1]->getContent()); + if ('' !== $content) { + $tokens[$openBraceIndex + 1] = new Token([T_WHITESPACE, $content]); + } else { + $tokens->clearAt($openBraceIndex + 1); + } + } elseif ($tokens[$openBraceIndex - 1]->isWhitespace()) { + $tokens->clearAt($openBraceIndex - 1); + } + } + + for (; $openBraceIndex !== $moveBraceToIndex; $openBraceIndex += $delta) { + /** @var Token $siblingToken */ + $siblingToken = $tokens[$openBraceIndex + $delta]; + $tokens[$openBraceIndex] = $siblingToken; + } + + $tokens[$openBraceIndex] = $movedToken; + + $openBraceIndex = $moveBraceToIndex; + } + + if ($tokens->ensureWhitespaceAtIndex($openBraceIndex - 1, 1, $whitespace)) { + ++$closeBraceIndex; + if (null !== $allowSingleLineUntil) { + ++$allowSingleLineUntil; + } + } + + if ( + !$addNewlinesInsideBraces + || $tokens[$tokens->getPrevMeaningfulToken($closeBraceIndex)]->isGivenKind(T_OPEN_TAG) + ) { + continue; + } + + for ($prevIndex = $closeBraceIndex - 1; $tokens->isEmptyAt($prevIndex); --$prevIndex); + + $prevToken = $tokens[$prevIndex]; + if ($prevToken->isWhitespace() && Preg::match('/\R/', $prevToken->getContent())) { + continue; + } + + $whitespace = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $openBraceIndex); + $tokens->ensureWhitespaceAtIndex($prevIndex, 1, $whitespace); + } + } + + private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); + $nextToken = $tokens[$nextIndex]; + + // return if next token is not opening parenthesis + if (!$nextToken->equals('(')) { + return $structureTokenIndex; + } + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); + } + + private function isFollowedByNewLine(Tokens $tokens, int $index): bool + { + for (++$index, $max = \count($tokens) - 1; $index < $max; ++$index) { + $token = $tokens[$index]; + if (!$token->isComment()) { + return $token->isWhitespace() && Preg::match('/\R/', $token->getContent()); + } + } + + return false; + } + + private function hasCommentOnSameLine(Tokens $tokens, int $index): bool + { + $token = $tokens[$index + 1]; + + if ($token->isWhitespace() && !Preg::match('/\R/', $token->getContent())) { + $token = $tokens[$index + 2]; + } + + return $token->isComment(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/CurlyBracesPositionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/CurlyBracesPositionFixer.php new file mode 100644 index 0000000000..099b638164 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/CurlyBracesPositionFixer.php @@ -0,0 +1,126 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; + +/** + * @deprecated + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * allow_single_line_anonymous_functions?: bool, + * allow_single_line_empty_anonymous_classes?: bool, + * anonymous_classes_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * anonymous_functions_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * classes_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * control_structures_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line', + * functions_opening_brace?: 'next_line_unless_newline_at_signature_end'|'same_line' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * allow_single_line_anonymous_functions: bool, + * allow_single_line_empty_anonymous_classes: bool, + * anonymous_classes_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * anonymous_functions_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * classes_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * control_structures_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line', + * functions_opening_brace: 'next_line_unless_newline_at_signature_end'|'same_line' + * } + */ +final class CurlyBracesPositionFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface, WhitespacesAwareFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + use Indentation; + + /** + * @internal + */ + public const NEXT_LINE_UNLESS_NEWLINE_AT_SIGNATURE_END = 'next_line_unless_newline_at_signature_end'; + + /** + * @internal + */ + public const SAME_LINE = 'same_line'; + + private BracesPositionFixer $bracesPositionFixer; + + public function __construct() + { + $this->bracesPositionFixer = new BracesPositionFixer(); + + parent::__construct(); + } + + public function getDefinition(): FixerDefinitionInterface + { + $fixerDefinition = $this->bracesPositionFixer->getDefinition(); + + return new FixerDefinition( + 'Curly braces must be placed as configured.', + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription() + ); + } + + /** + * {@inheritdoc} + * + * Must run before SingleLineEmptyBodyFixer, StatementIndentationFixer. + * Must run after ControlStructureBracesFixer, NoMultipleStatementsPerLineFixer. + */ + public function getPriority(): int + { + return $this->bracesPositionFixer->getPriority(); + } + + public function getSuccessorsNames(): array + { + return [ + $this->bracesPositionFixer->getName(), + ]; + } + + /** + * @param _AutogeneratedInputConfiguration $configuration + */ + protected function configurePreNormalisation(array $configuration): void + { + $this->bracesPositionFixer->configure($configuration); + } + + protected function createProxyFixers(): array + { + return [ + $this->bracesPositionFixer, + ]; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return $this->bracesPositionFixer->createConfigurationDefinition(); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php index 50044e3aa6..2dc98e47cd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/EncodingFixer.php @@ -28,28 +28,22 @@ */ final class EncodingFixer extends AbstractFixer { - /** - * @var string - */ - private $BOM; + private string $bom; public function __construct() { parent::__construct(); - $this->BOM = pack('CCC', 0xEF, 0xBB, 0xBF); + $this->bom = pack('CCC', 0xEF, 0xBB, 0xBF); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'PHP code MUST use only UTF-8 without BOM (remove BOM).', [ new CodeSample( - $this->BOM.'bom.'getContent(); - if (0 === strncmp($content, $this->BOM, 3)) { + if (str_starts_with($content, $this->bom)) { $newContent = substr($content, 3); if ('' === $newContent) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoMultipleStatementsPerLineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoMultipleStatementsPerLineFixer.php new file mode 100644 index 0000000000..fcd9d2925f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoMultipleStatementsPerLineFixer.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶2.3 Lines: There must not be more than one statement per line. + */ +final class NoMultipleStatementsPerLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + use Indentation; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There must not be more than one statement per line.', + [new CodeSample("isTokenKindFound(';'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = 1, $max = \count($tokens) - 1; $index < $max; ++$index) { + if ($tokens[$index]->isGivenKind(T_FOR)) { + $index = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextTokenOfKind($index, ['(']) + ); + + continue; + } + + if (!$tokens[$index]->equals(';')) { + continue; + } + + for ($nextIndex = $index + 1; $nextIndex < $max; ++$nextIndex) { + $token = $tokens[$nextIndex]; + + if ($token->isWhitespace() || $token->isComment()) { + if (Preg::match('/\R/', $token->getContent())) { + break; + } + + continue; + } + + if (!$token->equalsAny(['}', [T_CLOSE_TAG], [T_ENDIF], [T_ENDFOR], [T_ENDSWITCH], [T_ENDWHILE], [T_ENDFOREACH]])) { + $whitespaceIndex = $index; + do { + $token = $tokens[++$whitespaceIndex]; + } while ($token->isComment()); + + $newline = $this->whitespacesConfig->getLineEnding().$this->getLineIndentation($tokens, $index); + + if ($tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, $newline)) { + ++$max; + } + } + + break; + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoTrailingCommaInSinglelineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoTrailingCommaInSinglelineFixer.php new file mode 100644 index 0000000000..f7d692266d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NoTrailingCommaInSinglelineFixer.php @@ -0,0 +1,162 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * elements?: list<'arguments'|'array'|'array_destructuring'|'group_import'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * elements: list<'arguments'|'array'|'array_destructuring'|'group_import'> + * } + */ +final class NoTrailingCommaInSinglelineFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'If a list of values separated by a comma is contained on a single line, then the last item MUST NOT have a trailing comma.', + [ + new CodeSample(" ['array_destructuring']]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return + $tokens->isTokenKindFound(',') + && $tokens->isAnyTokenKindsFound([')', CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $elements = ['arguments', 'array_destructuring', 'array', 'group_import']; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'Which elements to fix.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset($elements)]) + ->setDefault($elements) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->equals(')') && !$tokens[$index]->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_CLOSE])) { + continue; + } + + $commaIndex = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$commaIndex]->equals(',')) { + continue; + } + + $block = Tokens::detectBlockType($tokens[$index]); + $blockOpenIndex = $tokens->findBlockStart($block['type'], $index); + + if ($tokens->isPartialCodeMultiline($blockOpenIndex, $index)) { + continue; + } + + if (!$this->shouldBeCleared($tokens, $blockOpenIndex)) { + continue; + } + + do { + $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($commaIndex); + } while ($tokens[$commaIndex]->equals(',')); + + $tokens->removeTrailingWhitespace($commaIndex); + } + } + + private function shouldBeCleared(Tokens $tokens, int $openIndex): bool + { + $elements = $this->configuration['elements']; + + if ($tokens[$openIndex]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + return \in_array('array', $elements, true); + } + + if ($tokens[$openIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) { + return \in_array('array_destructuring', $elements, true); + } + + if ($tokens[$openIndex]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + return \in_array('group_import', $elements, true); + } + + if (!$tokens[$openIndex]->equals('(')) { + return false; + } + + $beforeOpen = $tokens->getPrevMeaningfulToken($openIndex); + + if ($tokens[$beforeOpen]->isGivenKind(T_ARRAY)) { + return \in_array('array', $elements, true); + } + + if ($tokens[$beforeOpen]->isGivenKind(T_LIST)) { + return \in_array('array_destructuring', $elements, true); + } + + if ($tokens[$beforeOpen]->isGivenKind([T_UNSET, T_ISSET, T_VARIABLE, T_CLASS])) { + return \in_array('arguments', $elements, true); + } + + if ($tokens[$beforeOpen]->isGivenKind(T_STRING)) { + return !AttributeAnalyzer::isAttribute($tokens, $beforeOpen) && \in_array('arguments', $elements, true); + } + + if ($tokens[$beforeOpen]->equalsAny([')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]])) { + $block = Tokens::detectBlockType($tokens[$beforeOpen]); + + return + ( + Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type'] + || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type'] + ) && \in_array('arguments', $elements, true); + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php index 953e709c40..7c3acfbaf7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -30,16 +31,28 @@ * Removes Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols. * * @author Ivan Boprzenkov + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * use_escape_sequences_in_strings?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * use_escape_sequences_in_strings: bool + * } */ final class NonPrintableCharacterFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var array + * @var array */ private array $symbolsReplace; /** - * @var int[] + * @var list */ private static array $tokens = [ T_STRING_VARNAME, @@ -64,9 +77,6 @@ public function __construct() ]; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -85,25 +95,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(self::$tokens); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -114,9 +115,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $replacements = []; @@ -131,7 +129,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $content = $token->getContent(); if ( - $this->configuration['use_escape_sequences_in_strings'] + true === $this->configuration['use_escape_sequences_in_strings'] && $token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE]) ) { if (!Preg::match('/'.implode('|', array_keys($escapeSequences)).'/', $content)) { @@ -159,7 +157,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } if ($stringTypeChanged) { - $content = Preg::replace('/(\\\\{1,2})/', '\\\\\\\\', $content); + $content = Preg::replace('/(\\\{1,2})/', '\\\\\\\\', $content); $content = str_replace('$', '\$', $content); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NumericLiteralSeparatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NumericLiteralSeparatorFixer.php new file mode 100644 index 0000000000..b062dcde59 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NumericLiteralSeparatorFixer.php @@ -0,0 +1,232 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Let's you add underscores to numeric literals. + * + * Inspired by: + * - {@link https://github.com/kubawerlos/php-cs-fixer-custom-fixers/blob/main/src/Fixer/NumericLiteralSeparatorFixer.php} + * - {@link https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/numeric-separators-style.js} + * + * @author Marvin Heilemann + * @author Greg Korba + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * override_existing?: bool, + * strategy?: 'no_separator'|'use_separator' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * override_existing: bool, + * strategy: 'no_separator'|'use_separator' + * } + */ +final class NumericLiteralSeparatorFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public const STRATEGY_USE_SEPARATOR = 'use_separator'; + public const STRATEGY_NO_SEPARATOR = 'no_separator'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Adds separators to numeric literals of any kind.', + [ + new CodeSample( + <<<'PHP' + self::STRATEGY_NO_SEPARATOR], + ), + new CodeSample( + <<<'PHP' + self::STRATEGY_USE_SEPARATOR], + ), + new CodeSample( + " true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_DNUMBER, T_LNUMBER]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'override_existing', + 'Whether literals already containing underscores should be reformatted.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'strategy', + 'Whether numeric literal should be separated by underscores or not.' + )) + ->setAllowedValues([self::STRATEGY_USE_SEPARATOR, self::STRATEGY_NO_SEPARATOR]) + ->setDefault(self::STRATEGY_USE_SEPARATOR) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind([T_DNUMBER, T_LNUMBER])) { + continue; + } + + $content = $token->getContent(); + + $newContent = $this->formatValue($content); + + if ($content === $newContent) { + // Skip Token override if its the same content, like when it + // already got a valid literal separator structure. + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } + + private function formatValue(string $value): string + { + if (self::STRATEGY_NO_SEPARATOR === $this->configuration['strategy']) { + return str_contains($value, '_') ? str_replace('_', '', $value) : $value; + } + + if (true === $this->configuration['override_existing']) { + $value = str_replace('_', '', $value); + } elseif (str_contains($value, '_')) { + // Keep already underscored literals untouched. + return $value; + } + + $lowerValue = strtolower($value); + + if (str_starts_with($lowerValue, '0b')) { + // Binary + return $this->insertEveryRight($value, 8, 2); + } + + if (str_starts_with($lowerValue, '0x')) { + // Hexadecimal + return $this->insertEveryRight($value, 2, 2); + } + + if (str_starts_with($lowerValue, '0o')) { + // Octal + return $this->insertEveryRight($value, 3, 2); + } + if (str_starts_with($lowerValue, '0') && !str_contains($lowerValue, '.')) { + // Octal notation prior PHP 8.1 but still valid + return $this->insertEveryRight($value, 3, 1); + } + + // All other types + + /** If its a negative value we need an offset */ + $negativeOffset = static fn ($v) => str_contains($v, '-') ? 1 : 0; + + Preg::matchAll('/([0-9-_]+)?((\.)([0-9_]*))?((e)([0-9-_]+))?/i', $value, $result); + + $integer = $result[1][0]; + $joinedValue = $this->insertEveryRight($integer, 3, $negativeOffset($integer)); + + $dot = $result[3][0]; + if ('' !== $dot) { + $integer = $result[4][0]; + $decimal = $this->insertEveryLeft($integer, 3, $negativeOffset($integer)); + $joinedValue = $joinedValue.$dot.$decimal; + } + + $tim = $result[6][0]; + if ('' !== $tim) { + $integer = $result[7][0]; + $times = $this->insertEveryRight($integer, 3, $negativeOffset($integer)); + $joinedValue = $joinedValue.$tim.$times; + } + + return $joinedValue; + } + + private function insertEveryRight(string $value, int $length, int $offset = 0): string + { + $position = $length * -1; + while ($position > -(\strlen($value) - $offset)) { + $value = substr_replace($value, '_', $position, 0); + $position -= $length + 1; + } + + return $value; + } + + private function insertEveryLeft(string $value, int $length, int $offset = 0): string + { + $position = $length; + while ($position < \strlen($value)) { + $value = substr_replace($value, '_', $position, $offset); + $position += $length + 1; + } + + return $value; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php index 1e6755f3e4..36f19e888b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/OctalNotationFixer.php @@ -25,9 +25,6 @@ final class OctalNotationFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -35,23 +32,17 @@ public function getDefinition(): FixerDefinitionInterface [ new VersionSpecificCodeSample( "= 80100 && $tokens->isTokenKindFound(T_LNUMBER); + return \PHP_VERSION_ID >= 8_01_00 && $tokens->isTokenKindFound(T_LNUMBER); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -61,14 +52,13 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $content = $token->getContent(); - if (1 !== Preg::match('#^0\d+$#', $content)) { + $newContent = Preg::replace('#^0_*+([0-7_]+)$#', '0o$1', $content); + + if ($content === $newContent) { continue; } - $tokens[$index] = 1 === Preg::match('#^0+$#', $content) - ? new Token([T_LNUMBER, '0']) - : new Token([T_LNUMBER, '0o'.substr($content, 1)]) - ; + $tokens[$index] = new Token([T_LNUMBER, $newContent]); } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php index 3d22c4e05c..7f795cb9a8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/PsrAutoloadingFixer.php @@ -15,7 +15,9 @@ namespace PhpCsFixer\Fixer\Basic; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -34,12 +36,21 @@ * @author Bram Gotink * @author Graham Campbell * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * dir?: null|string + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * dir: null|string + * } */ final class PsrAutoloadingFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -66,29 +77,11 @@ class InvalidName {} ); } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - if (null !== $this->configuration['dir']) { - $this->configuration['dir'] = realpath($this->configuration['dir']); - } - } - - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -96,15 +89,14 @@ public function isRisky(): bool /** * {@inheritdoc} + * + * Must run before SelfAccessorFixer. */ public function getPriority(): int { return -10; } - /** - * {@inheritdoc} - */ public function supports(\SplFileInfo $file): bool { if ($file instanceof StdinFileInfo) { @@ -115,13 +107,13 @@ public function supports(\SplFileInfo $file): bool // ignore file with extension other than php ('php' !== $file->getExtension()) // ignore file with name that cannot be a class name - || 0 === Preg::match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $file->getBasename('.php')) + || !Preg::match('/^'.TypeExpression::REGEX_IDENTIFIER.'$/', $file->getBasename('.php')) ) { return false; } try { - $tokens = Tokens::fromCode(sprintf('getBasename('.php'))); + $tokens = Tokens::fromCode(\sprintf('getBasename('.php'))); if ($tokens[3]->isKeyword() || $tokens[3]->isMagicConstant()) { // name cannot be a class name - detected by PHP 5.x @@ -133,12 +125,22 @@ public function supports(\SplFileInfo $file): bool } // ignore stubs/fixtures, since they typically contain invalid files for various reasons - return !Preg::match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath()); + return !Preg::match('{[/\\\](stub|fixture)s?[/\\\]}i', $file->getRealPath()); + } + + protected function configurePostNormalisation(): void + { + if (null !== $this->configuration['dir']) { + $realpath = realpath($this->configuration['dir']); + + if (false === $realpath) { + throw new \InvalidArgumentException(\sprintf('Failed to resolve configured directory "%s".', $this->configuration['dir'])); + } + + $this->configuration['dir'] = $realpath; + } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -149,9 +151,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokenAnalyzer = new TokensAnalyzer($tokens); @@ -242,7 +241,7 @@ private function calculateClassyName(\SplFileInfo $file, ?string $namespace, str $namespaceParts = array_reverse(explode('\\', $maxNamespace)); foreach ($namespaceParts as $namespacePart) { - $nameCandidate = sprintf('%s_%s', $namespacePart, $name); + $nameCandidate = \sprintf('%s_%s', $namespacePart, $name); if (strtolower($nameCandidate) !== strtolower(substr($currentName, -\strlen($nameCandidate)))) { break; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/SingleLineEmptyBodyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/SingleLineEmptyBodyFixer.php new file mode 100644 index 0000000000..c3a8176947 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/SingleLineEmptyBodyFixer.php @@ -0,0 +1,83 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Basic; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class SingleLineEmptyBodyFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Empty body of class, interface, trait, enum or function must be abbreviated as `{}` and placed on the same line as the previous symbol, separated by a single space.', + [new CodeSample('isTokenKindFound(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required + return true; + } + + return $tokens->isAnyTokenKindsFound([T_INTERFACE, T_CLASS, T_FUNCTION, T_TRAIT]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind([...Token::getClassyTokenKinds(), T_FUNCTION])) { + continue; + } + + $openBraceIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + if (!$tokens[$openBraceIndex]->equals('{')) { + continue; + } + + $closeBraceIndex = $tokens->getNextNonWhitespace($openBraceIndex); + if (!$tokens[$closeBraceIndex]->equals('}')) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($openBraceIndex + 1, 0, ''); + + $beforeOpenBraceIndex = $tokens->getPrevNonWhitespace($openBraceIndex); + if (!$tokens[$beforeOpenBraceIndex]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + $tokens->ensureWhitespaceAtIndex($openBraceIndex - 1, 1, ' '); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php index c7fb0afb80..20b951d112 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ClassReferenceNameCasingFixer.php @@ -19,7 +19,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; @@ -27,9 +26,6 @@ final class ClassReferenceNameCasingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -40,24 +36,17 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $namespacesAnalyzer = new NamespacesAnalyzer(); $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); $classNames = $this->getClassNames(); - foreach ($namespacesAnalyzer->getDeclarations($tokens) as $namespace) { + foreach ($tokens->getNamespaceDeclarations() as $namespace) { $uses = []; foreach ($namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace) as $use) { @@ -95,6 +84,10 @@ private function getClassReference(Tokens $tokens, NamespaceAnalysis $namespace) T_TRAIT, ]; + if (\defined('T_NULLSAFE_OBJECT_OPERATOR')) { // @TODO: drop condition when PHP 8.0+ is required + $notBeforeKinds[] = T_NULLSAFE_OBJECT_OPERATOR; + } + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required $notBeforeKinds[] = T_ENUM; } @@ -153,6 +146,9 @@ private function getClassReference(Tokens $tokens, NamespaceAnalysis $namespace) } } + /** + * @return array + */ private function getClassNames(): array { static $classes = null; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php index 5b71c649d1..00389a1739 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/ConstantCaseFixer.php @@ -16,13 +16,13 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -30,9 +30,21 @@ * Fixer for constants case. * * @author Pol Dellaiera + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case?: 'lower'|'upper' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case: 'lower'|'upper' + * } */ final class ConstantCaseFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * Hold the function that will be used to convert the constants. * @@ -40,29 +52,6 @@ final class ConstantCaseFixer extends AbstractFixer implements ConfigurableFixer */ private $fixFunction; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - if ('lower' === $this->configuration['case']) { - $this->fixFunction = static function (string $content): string { - return strtolower($content); - }; - } - - if ('upper' === $this->configuration['case']) { - $this->fixFunction = static function (string $content): string { - return strtoupper($content); - }; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -74,17 +63,22 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + if ('lower' === $this->configuration['case']) { + $this->fixFunction = static fn (string $content): string => strtolower($content); + } + + if ('upper' === $this->configuration['case']) { + $this->fixFunction = static fn (string $content): string => strtoupper($content); + } + } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -95,60 +89,42 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $fixFunction = $this->fixFunction; + static $forbiddenPrevKinds = null; + if (null === $forbiddenPrevKinds) { + $forbiddenPrevKinds = [ + T_DOUBLE_COLON, + T_EXTENDS, + T_IMPLEMENTS, + T_INSTANCEOF, + T_NAMESPACE, + T_NEW, + T_NS_SEPARATOR, + ...Token::getObjectOperatorKinds(), + ]; + } foreach ($tokens as $index => $token) { - if (!$token->isNativeConstant()) { + if (!$token->equalsAny([[T_STRING, 'true'], [T_STRING, 'false'], [T_STRING, 'null']], false)) { continue; } - if ( - $this->isNeighbourAccepted($tokens, $tokens->getPrevMeaningfulToken($index)) - && $this->isNeighbourAccepted($tokens, $tokens->getNextMeaningfulToken($index)) - ) { - $tokens[$index] = new Token([$token->getId(), $fixFunction($token->getContent())]); + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind($forbiddenPrevKinds)) { + continue; } - } - } - private function isNeighbourAccepted(Tokens $tokens, int $index): bool - { - static $forbiddenTokens = null; - - if (null === $forbiddenTokens) { - $forbiddenTokens = array_merge( - [ - T_AS, - T_CLASS, - T_CONST, - T_EXTENDS, - T_IMPLEMENTS, - T_INSTANCEOF, - T_INSTEADOF, - T_INTERFACE, - T_NEW, - T_NS_SEPARATOR, - T_PAAMAYIM_NEKUDOTAYIM, - T_TRAIT, - T_USE, - CT::T_USE_TRAIT, - CT::T_USE_LAMBDA, - ], - Token::getObjectOperatorKinds() - ); - } + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($tokens[$nextIndex]->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM) || $tokens[$nextIndex]->equalsAny(['='], false)) { + continue; + } - $token = $tokens[$index]; + if ($tokens[$prevIndex]->isGivenKind(T_CASE) && $tokens[$nextIndex]->equals(';')) { + continue; + } - if ($token->equalsAny(['{', '}'])) { - return false; + $tokens[$index] = new Token([$token->getId(), ($this->fixFunction)($token->getContent())]); } - - return !$token->isGivenKind($forbiddenTokens); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php index 5d262ffef1..1d85abba40 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/IntegerLiteralCaseFixer.php @@ -24,9 +24,6 @@ final class IntegerLiteralCaseFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,11 +50,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $content = $token->getContent(); - if (1 !== Preg::match('#^0[bxoBXO][0-9a-fA-F]+$#', $content)) { - continue; - } - - $newContent = '0'.strtolower($content[1]).strtoupper(substr($content, 2)); + $newContent = Preg::replaceCallback('#^0([boxBOX])([0-9a-fA-F_]+)$#', static fn ($matches) => '0'.strtolower($matches[1]).strtoupper($matches[2]), $content); if ($content === $newContent) { continue; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php index 304b10aa7f..f11b9988db 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseKeywordsFixer.php @@ -29,13 +29,10 @@ final class LowercaseKeywordsFixer extends AbstractFixer { /** - * @var int[] + * @var list */ private static array $excludedTokens = [T_HALT_COMPILER]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -59,17 +56,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getKeywords()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php index 770e66a1c5..7615b296be 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/LowercaseStaticReferenceFixer.php @@ -27,9 +27,6 @@ */ final class LowercaseStaticReferenceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -87,7 +84,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $prevIndex = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR]) || $tokens[$prevIndex]->isObjectOperator()) { + if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR, T_STATIC, T_STRING, CT::T_ARRAY_TYPEHINT, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]) || $tokens[$prevIndex]->isObjectOperator()) { continue; } @@ -100,6 +97,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } + if ($tokens[$prevIndex]->isGivenKind(T_CASE) && !$tokens[$nextIndex]->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { + continue; + } + $tokens[$index] = new Token([$token->getId(), $newContent]); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php index 3cfae9bfa2..b8de332eb1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicConstantCasingFixer.php @@ -27,9 +27,6 @@ */ final class MagicConstantCasingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -38,17 +35,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound($this->getMagicConstantTokens()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $magicConstants = $this->getMagicConstants(); @@ -86,7 +77,7 @@ private function getMagicConstants(): array } /** - * @return array + * @return list */ private function getMagicConstantTokens(): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php index 9179845fdf..544d2cbb9a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/MagicMethodCasingFixer.php @@ -24,7 +24,7 @@ final class MagicMethodCasingFixer extends AbstractFixer { /** - * @var array + * @var array */ private static array $magicNames = [ '__call' => '__call', @@ -46,9 +46,6 @@ final class MagicMethodCasingFixer extends AbstractFixer '__wakeup' => '__wakeup', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -73,17 +70,11 @@ public function __Sleep() ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound(array_merge([T_FUNCTION, T_DOUBLE_COLON], Token::getObjectOperatorKinds())); + return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_DOUBLE_COLON, ...Token::getObjectOperatorKinds()]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $inClass = 0; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php index cf0f68da34..84c7c64346 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionCasingFixer.php @@ -24,9 +24,6 @@ final class NativeFunctionCasingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,17 +42,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php index 070a68e529..2b2d5094f7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php @@ -14,91 +14,21 @@ namespace PhpCsFixer\Fixer\Casing; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; -use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; -use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; -final class NativeFunctionTypeDeclarationCasingFixer extends AbstractFixer +/** + * @deprecated in favor of NativeTypeDeclarationCasingFixer + */ +final class NativeFunctionTypeDeclarationCasingFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration. - * - * self PHP 5.0 - * array PHP 5.1 - * callable PHP 5.4 - * bool PHP 7.0 - * float PHP 7.0 - * int PHP 7.0 - * string PHP 7.0 - * iterable PHP 7.1 - * void PHP 7.1 - * object PHP 7.2 - * static PHP 8.0 (return type only) - * mixed PHP 8.0 - * never PHP 8.1 - * - * @var array - */ - private array $hints; - - private FunctionsAnalyzer $functionsAnalyzer; - - public function __construct() - { - parent::__construct(); - - $this->hints = [ - 'array' => true, - 'callable' => true, - 'self' => true, - ]; - - $this->hints = array_merge( - $this->hints, - [ - 'bool' => true, - 'float' => true, - 'int' => true, - 'string' => true, - ] - ); - - $this->hints = array_merge( - $this->hints, - [ - 'iterable' => true, - 'void' => true, - ] - ); - - $this->hints = array_merge($this->hints, ['object' => true]); - - if (\PHP_VERSION_ID >= 80000) { - $this->hints = array_merge($this->hints, ['static' => true]); - $this->hints = array_merge($this->hints, ['mixed' => true]); - } - - if (\PHP_VERSION_ID >= 80100) { - $this->hints = array_merge($this->hints, ['never' => true]); - } - - $this->functionsAnalyzer = new FunctionsAnalyzer(); - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Native type hints for functions should use the correct case.', + 'Native type declarations for functions should use the correct case.', [ new CodeSample("isAllTokenKindsFound([T_FUNCTION, T_STRING]); - } - - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void - { - for ($index = $tokens->count() - 1; $index >= 0; --$index) { - if ($tokens[$index]->isGivenKind(T_FUNCTION)) { - $this->fixFunctionReturnType($tokens, $index); - $this->fixFunctionArgumentTypes($tokens, $index); - } - } - } - - private function fixFunctionArgumentTypes(Tokens $tokens, int $index): void - { - foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) { - $this->fixArgumentType($tokens, $argument->getTypeAnalysis()); - } - } - - private function fixFunctionReturnType(Tokens $tokens, int $index): void + public function getSuccessorsNames(): array { - $this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index)); + return array_keys($this->proxyFixers); } - private function fixArgumentType(Tokens $tokens, ?TypeAnalysis $type = null): void + protected function createProxyFixers(): array { - if (null === $type) { - return; - } - - for ($index = $type->getStartIndex(); $index <= $type->getEndIndex(); ++$index) { - if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { - continue; - } - - $lowerCasedName = strtolower($tokens[$index]->getContent()); - - if (!isset($this->hints[$lowerCasedName])) { - continue; - } + $fixer = new NativeTypeDeclarationCasingFixer(); - $tokens[$index] = new Token([$tokens[$index]->getId(), $lowerCasedName]); - } + return [$fixer]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php new file mode 100644 index 0000000000..e46f10df16 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Casing/NativeTypeDeclarationCasingFixer.php @@ -0,0 +1,360 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Casing; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class NativeTypeDeclarationCasingFixer extends AbstractFixer +{ + /* + * https://wiki.php.net/rfc/typed_class_constants + * Supported types + * Class constant type declarations support all type declarations supported by PHP, + * except `void`, `callable`, `never`. + * + * array + * bool + * callable + * float + * int + * iterable + * object + * mixed + * parent + * self + * string + * any class or interface name -> not native, so not applicable for this Fixer + * ?type -> not native, `?` has no casing, so not applicable for this Fixer + * + * Not in the list referenced but supported: + * null + * static + */ + private const CLASS_CONST_SUPPORTED_HINTS = [ + 'array' => true, + 'bool' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'mixed' => true, + 'null' => true, + 'object' => true, + 'parent' => true, + 'self' => true, + 'string' => true, + 'static' => true, + ]; + + private const CLASS_PROPERTY_SUPPORTED_HINTS = [ + 'array' => true, + 'bool' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'mixed' => true, + 'null' => true, + 'object' => true, + 'parent' => true, + 'self' => true, + 'static' => true, + 'string' => true, + ]; + + private const TYPE_SEPARATION_TYPES = [ + CT::T_TYPE_ALTERNATION, + CT::T_TYPE_INTERSECTION, + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, + ]; + + /** + * https://secure.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration. + * + * self PHP 5.0 + * array PHP 5.1 + * callable PHP 5.4 + * bool PHP 7.0 + * float PHP 7.0 + * int PHP 7.0 + * string PHP 7.0 + * iterable PHP 7.1 + * void PHP 7.1 + * object PHP 7.2 + * static PHP 8.0 (return type only) + * mixed PHP 8.0 + * false PHP 8.0 (union return type only) + * null PHP 8.0 (union return type only) + * never PHP 8.1 (return type only) + * true PHP 8.2 (standalone type: https://wiki.php.net/rfc/true-type) + * false PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types) + * null PHP 8.2 (standalone type: https://wiki.php.net/rfc/null-false-standalone-types) + * + * @var array + */ + private array $functionTypeHints; + + private FunctionsAnalyzer $functionsAnalyzer; + + /** + * @var list + */ + private array $beforePropertyTypeTokens; + + public function __construct() + { + parent::__construct(); + + $this->beforePropertyTypeTokens = ['{', ';', [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR]]; + + $this->functionTypeHints = [ + 'array' => true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'self' => true, + 'string' => true, + 'void' => true, + ]; + + if (\PHP_VERSION_ID >= 8_00_00) { + $this->functionTypeHints['false'] = true; + $this->functionTypeHints['mixed'] = true; + $this->functionTypeHints['null'] = true; + $this->functionTypeHints['static'] = true; + } + + if (\PHP_VERSION_ID >= 8_01_00) { + $this->functionTypeHints['never'] = true; + + $this->beforePropertyTypeTokens[] = [T_READONLY]; + } + + if (\PHP_VERSION_ID >= 8_02_00) { + $this->functionTypeHints['true'] = true; + } + + $this->functionsAnalyzer = new FunctionsAnalyzer(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Native type declarations should be used in the correct case.', + [ + new CodeSample( + "isAnyTokenKindsFound(Token::getClassyTokenKinds()); + + return + $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]) + || ($classyFound && $tokens->isTokenKindFound(T_STRING)) + || ( + \PHP_VERSION_ID >= 8_03_00 + && $tokens->isTokenKindFound(T_CONST) + && $classyFound + ); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->fixFunctions($tokens); + $this->fixClassConstantsAndProperties($tokens); + } + + private function fixFunctions(Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { + $this->fixFunctionReturnType($tokens, $index); + $this->fixFunctionArgumentTypes($tokens, $index); + } + } + } + + private function fixFunctionArgumentTypes(Tokens $tokens, int $index): void + { + foreach ($this->functionsAnalyzer->getFunctionArguments($tokens, $index) as $argument) { + $this->fixArgumentType($tokens, $argument->getTypeAnalysis()); + } + } + + private function fixFunctionReturnType(Tokens $tokens, int $index): void + { + $this->fixArgumentType($tokens, $this->functionsAnalyzer->getFunctionReturnType($tokens, $index)); + } + + private function fixArgumentType(Tokens $tokens, ?TypeAnalysis $type = null): void + { + if (null === $type) { + return; + } + + for ($index = $type->getStartIndex(); $index <= $type->getEndIndex(); ++$index) { + if ($tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $this->fixCasing($this->functionTypeHints, $tokens, $index); + } + } + + private function fixClassConstantsAndProperties(Tokens $tokens): void + { + $analyzer = new TokensAnalyzer($tokens); + $elements = array_reverse($analyzer->getClassyElements(), true); + + foreach ($elements as $index => $element) { + if ('const' === $element['type']) { + if (\PHP_VERSION_ID >= 8_03_00 && !$this->isConstWithoutType($tokens, $index)) { + foreach ($this->getNativeTypeHintCandidatesForConstant($tokens, $index) as $nativeTypeHintIndex) { + $this->fixCasing($this::CLASS_CONST_SUPPORTED_HINTS, $tokens, $nativeTypeHintIndex); + } + } + + continue; + } + + if ('property' === $element['type']) { + foreach ($this->getNativeTypeHintCandidatesForProperty($tokens, $index) as $nativeTypeHintIndex) { + $this->fixCasing($this::CLASS_PROPERTY_SUPPORTED_HINTS, $tokens, $nativeTypeHintIndex); + } + } + } + } + + /** @return iterable */ + private function getNativeTypeHintCandidatesForConstant(Tokens $tokens, int $index): iterable + { + $constNameIndex = $this->getConstNameIndex($tokens, $index); + $index = $this->getFirstIndexOfType($tokens, $index); + + do { + $typeEnd = $this->getTypeEnd($tokens, $index, $constNameIndex); + + if ($typeEnd === $index) { + yield $index; + } + + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind(self::TYPE_SEPARATION_TYPES)); + } while ($index < $constNameIndex); + } + + private function isConstWithoutType(Tokens $tokens, int $index): bool + { + $index = $tokens->getNextMeaningfulToken($index); + + return $tokens[$index]->isGivenKind(T_STRING) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('='); + } + + private function getConstNameIndex(Tokens $tokens, int $index): int + { + return $tokens->getPrevMeaningfulToken( + $tokens->getNextTokenOfKind($index, ['=']), + ); + } + + /** @return iterable */ + private function getNativeTypeHintCandidatesForProperty(Tokens $tokens, int $index): iterable + { + $propertyNameIndex = $index; + $index = $tokens->getPrevTokenOfKind($index, $this->beforePropertyTypeTokens); + + $index = $this->getFirstIndexOfType($tokens, $index); + + do { + $typeEnd = $this->getTypeEnd($tokens, $index, $propertyNameIndex); + + if ($typeEnd === $index) { + yield $index; + } + + do { + $index = $tokens->getNextMeaningfulToken($index); + } while ($tokens[$index]->isGivenKind(self::TYPE_SEPARATION_TYPES)); + } while ($index < $propertyNameIndex); + + return []; + } + + private function getFirstIndexOfType(Tokens $tokens, int $index): int + { + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind(CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN)) { + $index = $tokens->getNextMeaningfulToken($index); + } + + return $index; + } + + private function getTypeEnd(Tokens $tokens, int $index, int $upperLimit): int + { + if (!$tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + return $index; // callable, array, self, static, etc. + } + + $endIndex = $index; + while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR]) && $index < $upperLimit) { + $endIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + } + + return $endIndex; + } + + /** + * @param array $supportedTypeHints + */ + private function fixCasing(array $supportedTypeHints, Tokens $tokens, int $index): void + { + $typeContent = $tokens[$index]->getContent(); + $typeContentLower = strtolower($typeContent); + + if (isset($supportedTypeHints[$typeContentLower]) && $typeContent !== $typeContentLower) { + $tokens[$index] = new Token([$tokens[$index]->getId(), $typeContentLower]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php index 5310960a23..11ee3732da 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/CastSpacesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,9 +28,21 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * space?: 'none'|'single' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * space: 'none'|'single' + * } */ final class CastSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + private const INSIDE_CAST_SPACE_REPLACE_MAP = [ ' ' => '', "\t" => '', @@ -39,9 +52,6 @@ final class CastSpacesFixer extends AbstractFixer implements ConfigurableFixerIn "\x0B" => '', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -72,17 +82,11 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getCastTokenKinds()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -115,13 +119,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('space', 'spacing to apply between cast and variable.')) + (new FixerOptionBuilder('space', 'Spacing to apply between cast and variable.')) ->setAllowedValues(['none', 'single']) ->setDefault('single') ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php index f0e30ce2d0..e50db282a9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/LowercaseCastFixer.php @@ -15,24 +15,20 @@ namespace PhpCsFixer\Fixer\CastNotation; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; final class LowercaseCastFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Cast should be written in lower case.', [ - new VersionSpecificCodeSample( + new CodeSample( 'isAnyTokenKindsFound(Token::getCastTokenKinds()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php index 7d13051f8a..75f2a437ad 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ModernizeTypesCastingFixer.php @@ -27,9 +27,6 @@ */ final class ModernizeTypesCastingFixer extends AbstractFunctionReferenceFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -52,7 +49,14 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} + * + * Must run before NoUnneededControlParenthesesFixer. */ + public function getPriority(): int + { + return 31; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // replacement patterns diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php index d802d49526..80439c1766 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoShortBoolCastFixer.php @@ -33,9 +33,6 @@ public function getPriority(): int return -9; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,17 +41,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound('!'); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; $index > 1; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php index c75f8ba429..81c1567f14 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/NoUnsetCastFixer.php @@ -23,9 +23,6 @@ final class NoUnsetCastFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -34,9 +31,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_UNSET_CAST); @@ -52,9 +46,6 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; $index > 0; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php index 983effde72..934d8a89e5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/CastNotation/ShortScalarCastFixer.php @@ -15,46 +15,31 @@ namespace PhpCsFixer\Fixer\CastNotation; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; final class ShortScalarCastFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`.', [ - new VersionSpecificCodeSample( - "isAnyTokenKindsFound(Token::getCastTokenKinds()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { static $castMap = [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php index 9903af992b..45e1de77a8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -30,15 +31,36 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * Make sure there is one blank line above and below class elements. * * The exception is when an element is the first or last item in a 'classy'. + * + * @phpstan-type _Class array{ + * index: int, + * open: int, + * close: int, + * elements: non-empty-list<_Element> + * } + * @phpstan-type _Element array{token: Token, type: string, index: int, start?: int, end?: int} + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * elements?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * elements: array + * } */ final class ClassAttributesSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -56,23 +78,6 @@ final class ClassAttributesSeparationFixer extends AbstractFixer implements Conf */ private array $classElementTypes = []; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->classElementTypes = []; // reset previous configuration - - foreach ($this->configuration['elements'] as $elementType => $spacing) { - $this->classElementTypes[$elementType] = $spacing; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -146,7 +151,7 @@ class Sample public $e; } ', - new VersionSpecification(80000), + new VersionSpecification(8_00_00), ['elements' => ['property' => self::SPACING_ONLY_IF_META]] ), ] @@ -156,25 +161,28 @@ class Sample /** * {@inheritdoc} * - * Must run before BracesFixer, IndentationTypeFixer, NoExtraBlankLinesFixer. - * Must run after OrderedClassElementsFixer, SingleClassElementPerStatementFixer. + * Must run before BracesFixer, IndentationTypeFixer, NoExtraBlankLinesFixer, StatementIndentationFixer. + * Must run after OrderedClassElementsFixer, SingleClassElementPerStatementFixer, VisibilityRequiredFixer. */ public function getPriority(): int { return 55; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->classElementTypes = []; // reset previous configuration + + foreach ($this->configuration['elements'] as $elementType => $spacing) { + $this->classElementTypes[$elementType] = $spacing; + } + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($this->getElementsByClass($tokens) as $class) { @@ -198,23 +206,20 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('elements', 'Dictionary of `const|method|property|trait_import|case` => `none|one|only_if_meta` values.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $option): bool { foreach ($option as $type => $spacing) { $supportedTypes = ['const', 'method', 'property', 'trait_import', 'case']; if (!\in_array($type, $supportedTypes, true)) { throw new InvalidOptionsException( - sprintf( - 'Unexpected element type, expected any of "%s", got "%s".', - implode('", "', $supportedTypes), + \sprintf( + 'Unexpected element type, expected any of %s, got "%s".', + Utils::naturalLanguageJoin($supportedTypes), \gettype($type).'#'.$type ) ); @@ -224,10 +229,10 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn if (!\in_array($spacing, $supportedSpacings, true)) { throw new InvalidOptionsException( - sprintf( - 'Unexpected spacing for element type "%s", expected any of "%s", got "%s".', + \sprintf( + 'Unexpected spacing for element type "%s", expected any of %s, got "%s".', $spacing, - implode('", "', $supportedSpacings), + Utils::naturalLanguageJoin($supportedSpacings), \is_object($spacing) ? \get_class($spacing) : (null === $spacing ? 'null' : \gettype($spacing).'#'.$spacing) ) ); @@ -252,6 +257,8 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn * * Deals with comments, PHPDocs and spaces above the element with respect to the position of the * element within the class, interface or trait. + * + * @param _Class $class */ private function fixSpaceAboveClassElement(Tokens $tokens, array $class, int $elementIndex): void { @@ -322,6 +329,9 @@ private function fixSpaceAboveClassElement(Tokens $tokens, array $class, int $el $this->correctLineBreaks($tokens, $nonWhiteAbove, $element['start'], $this->determineRequiredLineCount($tokens, $class, $elementIndex)); } + /** + * @param _Class $class + */ private function determineRequiredLineCount(Tokens $tokens, array $class, int $elementIndex): int { $type = $class['elements'][$elementIndex]['type']; @@ -353,9 +363,12 @@ private function determineRequiredLineCount(Tokens $tokens, array $class, int $e return $tokens[$aboveElementDocCandidateIndex]->isGivenKind([T_DOC_COMMENT, CT::T_ATTRIBUTE_CLOSE]) ? 2 : 1; } - throw new \RuntimeException(sprintf('Unknown spacing "%s".', $spacing)); + throw new \RuntimeException(\sprintf('Unknown spacing "%s".', $spacing)); } + /** + * @param _Class $class + */ private function fixSpaceBelowClassElement(Tokens $tokens, array $class): void { $element = $class['elements'][0]; @@ -454,6 +467,11 @@ private function findCommentBlockStart(Tokens $tokens, int $start, int $elementA return $start; } + /** + * @TODO Introduce proper DTO instead of an array + * + * @return \Generator<_Class> + */ private function getElementsByClass(Tokens $tokens): \Generator { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -498,9 +516,15 @@ private function getElementsByClass(Tokens $tokens): \Generator } } + /** + * including trailing single line comments if belonging to the class element. + * + * @param _Class $class + * @param _Element $element + */ private function getFirstTokenIndexOfClassElement(Tokens $tokens, array $class, array $element): int { - $modifierTypes = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC, T_STRING, T_NS_SEPARATOR, T_VAR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]; + $modifierTypes = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC, T_STRING, T_NS_SEPARATOR, T_VAR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]; if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required $modifierTypes[] = T_READONLY; @@ -521,7 +545,12 @@ private function getFirstTokenIndexOfClassElement(Tokens $tokens, array $class, return $firstElementAttributeIndex; } - // including trailing single line comments if belonging to the class element + /** + * including trailing single line comments if belonging to the class element. + * + * @param _Class $class + * @param _Element $element + */ private function getLastTokenIndexOfClassElement(Tokens $tokens, array $class, array $element, TokensAnalyzer $tokensAnalyzer): int { // find last token of the element diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php index 9b1394b412..8ff8b03078 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ClassDefinitionFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -23,18 +24,39 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * Fixer for part of the rules defined in PSR2 ¶4.1 Extends and Implements and PSR12 ¶8. Anonymous Classes. + * + * @phpstan-type _ClassExtendsInfo array{start: int, numberOfExtends: int, multiLine: bool} + * @phpstan-type _ClassImplementsInfo array{start: int, numberOfImplements: int, multiLine: bool} + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * inline_constructor_arguments?: bool, + * multi_line_extends_each_single_line?: bool, + * single_item_single_line?: bool, + * single_line?: bool, + * space_before_parenthesis?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * inline_constructor_arguments: bool, + * multi_line_extends_each_single_line: bool, + * single_item_single_line: bool, + * single_line: bool, + * space_before_parenthesis: bool + * } */ final class ClassDefinitionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -104,25 +126,19 @@ interface Bar extends /** * {@inheritdoc} * - * Must run before BracesFixer. - * Must run after NewWithBracesFixer. + * Must run before BracesFixer, SingleLineEmptyBodyFixer. + * Must run after NewWithBracesFixer, NewWithParenthesesFixer. */ public function getPriority(): int { return 36; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // -4, one for count to index, 3 because min. of tokens for a classy location. @@ -133,9 +149,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -201,7 +214,7 @@ private function fixClassyDefinition(Tokens $tokens, int $classyIndex): void $end = $tokens->getPrevNonWhitespace($classDefInfo['open']); } - if ($classDefInfo['anonymousClass'] && !$this->configuration['inline_constructor_arguments']) { + if ($classDefInfo['anonymousClass'] && false === $this->configuration['inline_constructor_arguments']) { if (!$tokens[$end]->equals(')')) { // anonymous class with `extends` and/or `implements` $start = $tokens->getPrevMeaningfulToken($end); $this->makeClassyDefinitionSingleLine($tokens, $start, $end); @@ -215,8 +228,15 @@ private function fixClassyDefinition(Tokens $tokens, int $classyIndex): void // 4.1 The extends and implements keywords MUST be declared on the same line as the class name. $this->makeClassyDefinitionSingleLine($tokens, $classDefInfo['start'], $end); + + $this->sortClassModifiers($tokens, $classDefInfo); } + /** + * @param _ClassExtendsInfo $classExtendsInfo + * + * @return _ClassExtendsInfo + */ private function fixClassyDefinitionExtends(Tokens $tokens, int $classOpenIndex, array $classExtendsInfo): array { $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); @@ -235,6 +255,11 @@ private function fixClassyDefinitionExtends(Tokens $tokens, int $classOpenIndex, return $classExtendsInfo; } + /** + * @param _ClassImplementsInfo $classImplementsInfo + * + * @return _ClassImplementsInfo + */ private function fixClassyDefinitionImplements(Tokens $tokens, int $classOpenIndex, array $classImplementsInfo): array { $endIndex = $tokens->getPrevNonWhitespace($classOpenIndex); @@ -253,6 +278,19 @@ private function fixClassyDefinitionImplements(Tokens $tokens, int $classOpenInd return $classImplementsInfo; } + /** + * @param array{ + * start: int, + * classy: int, + * open: int, + * extends: false|_ClassExtendsInfo, + * implements: false|_ClassImplementsInfo, + * anonymousClass: bool, + * final: false|int, + * abstract: false|int, + * readonly: false|int, + * } $classDefInfo + */ private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefInfo): int { if ($classDefInfo['anonymousClass']) { @@ -286,42 +324,69 @@ private function fixClassyDefinitionOpenSpacing(Tokens $tokens, array $classDefI return $openIndex + 1; } + /** + * @return array{ + * start: int, + * classy: int, + * open: int, + * extends: false|_ClassExtendsInfo, + * implements: false|_ClassImplementsInfo, + * anonymousClass: bool, + * final: false|int, + * abstract: false|int, + * readonly: false|int, + * } + */ private function getClassyDefinitionInfo(Tokens $tokens, int $classyIndex): array { + $tokensAnalyzer = new TokensAnalyzer($tokens); $openIndex = $tokens->getNextTokenOfKind($classyIndex, ['{']); - $extends = false; - $implements = false; - $anonymousClass = false; + $def = [ + 'classy' => $classyIndex, + 'open' => $openIndex, + 'extends' => false, + 'implements' => false, + 'anonymousClass' => false, + 'final' => false, + 'abstract' => false, + 'readonly' => false, + ]; if (!$tokens[$classyIndex]->isGivenKind(T_TRAIT)) { $extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex); - $extends = \count($extends) ? $this->getClassyInheritanceInfo($tokens, key($extends), 'numberOfExtends') : false; + $def['extends'] = [] !== $extends ? $this->getClassyInheritanceInfo($tokens, array_key_first($extends), 'numberOfExtends') : false; if (!$tokens[$classyIndex]->isGivenKind(T_INTERFACE)) { $implements = $tokens->findGivenKind(T_IMPLEMENTS, $classyIndex, $openIndex); - $implements = \count($implements) ? $this->getClassyInheritanceInfo($tokens, key($implements), 'numberOfImplements') : false; - $tokensAnalyzer = new TokensAnalyzer($tokens); - $anonymousClass = $tokensAnalyzer->isAnonymousClass($classyIndex); + $def['implements'] = [] !== $implements ? $this->getClassyInheritanceInfo($tokens, array_key_first($implements), 'numberOfImplements') : false; + $def['anonymousClass'] = $tokensAnalyzer->isAnonymousClass($classyIndex); } } - if ($anonymousClass) { - $startIndex = $tokens->getPrevMeaningfulToken($classyIndex); // go to "new" for anonymous class + if ($def['anonymousClass']) { + $startIndex = $tokens->getPrevTokenOfKind($classyIndex, [[T_NEW]]); // go to "new" for anonymous class } else { - $prev = $tokens->getPrevMeaningfulToken($classyIndex); - $startIndex = $tokens[$prev]->isGivenKind([T_FINAL, T_ABSTRACT]) ? $prev : $classyIndex; + $modifiers = $tokensAnalyzer->getClassyModifiers($classyIndex); + $startIndex = $classyIndex; + + foreach (['final', 'abstract', 'readonly'] as $modifier) { + if (isset($modifiers[$modifier])) { + $def[$modifier] = $modifiers[$modifier]; + $startIndex = min($startIndex, $modifiers[$modifier]); + } else { + $def[$modifier] = false; + } + } } - return [ - 'start' => $startIndex, - 'classy' => $classyIndex, - 'open' => $openIndex, - 'extends' => $extends, - 'implements' => $implements, - 'anonymousClass' => $anonymousClass, - ]; + $def['start'] = $startIndex; + + return $def; } + /** + * @return array|array{start: int, multiLine: bool} + */ private function getClassyInheritanceInfo(Tokens $tokens, int $startIndex, string $label): array { $implementsInfo = ['start' => $startIndex, $label => 1, 'multiLine' => false]; @@ -348,15 +413,37 @@ private function makeClassyDefinitionSingleLine(Tokens $tokens, int $startIndex, { for ($i = $endIndex; $i >= $startIndex; --$i) { if ($tokens[$i]->isWhitespace()) { - if ($tokens[$i - 1]->isComment() || $tokens[$i + 1]->isComment()) { + if (str_contains($tokens[$i]->getContent(), "\n")) { + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition and else when PHP 8.0+ is required + if ($tokens[$i - 1]->isGivenKind(CT::T_ATTRIBUTE_CLOSE) || $tokens[$i + 1]->isGivenKind(T_ATTRIBUTE)) { + continue; + } + } else { + if (($tokens[$i - 1]->isComment() && str_ends_with($tokens[$i - 1]->getContent(), ']')) + || ($tokens[$i + 1]->isComment() && str_starts_with($tokens[$i + 1]->getContent(), '#[')) + ) { + continue; + } + } + + if ($tokens[$i - 1]->isGivenKind(T_DOC_COMMENT) || $tokens[$i + 1]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + } + + if ($tokens[$i - 1]->isComment()) { $content = $tokens[$i - 1]->getContent(); + if (!str_starts_with($content, '//') && !str_starts_with($content, '#')) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); + } - if (!('#' === $content || str_starts_with($content, '//'))) { - $content = $tokens[$i + 1]->getContent(); + continue; + } - if (!('#' === $content || str_starts_with($content, '//'))) { - $tokens[$i] = new Token([T_WHITESPACE, ' ']); - } + if ($tokens[$i + 1]->isComment()) { + $content = $tokens[$i + 1]->getContent(); + if (!str_starts_with($content, '//')) { + $tokens[$i] = new Token([T_WHITESPACE, ' ']); } continue; @@ -447,4 +534,39 @@ private function makeClassyInheritancePartMultiLine(Tokens $tokens, int $startIn $i = $previousInterfaceImplementingIndex + 1; } } + + /** + * @param array{ + * final: false|int, + * abstract: false|int, + * readonly: false|int, + * } $classDefInfo + */ + private function sortClassModifiers(Tokens $tokens, array $classDefInfo): void + { + if (false === $classDefInfo['readonly']) { + return; + } + + $readonlyIndex = $classDefInfo['readonly']; + + foreach (['final', 'abstract'] as $accessModifier) { + if (false === $classDefInfo[$accessModifier] || $classDefInfo[$accessModifier] < $readonlyIndex) { + continue; + } + + $accessModifierIndex = $classDefInfo[$accessModifier]; + + /** @var Token $readonlyToken */ + $readonlyToken = clone $tokens[$readonlyIndex]; + + /** @var Token $accessToken */ + $accessToken = clone $tokens[$accessModifierIndex]; + + $tokens[$readonlyIndex] = $accessToken; + $tokens[$accessModifierIndex] = $readonlyToken; + + break; + } + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php index 5372ca7690..c79a100829 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalClassFixer.php @@ -24,9 +24,6 @@ */ final class FinalClassFixer extends AbstractProxyFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -42,19 +39,26 @@ class MyApp {} .'If you want to subclass a class, mark the parent class as abstract and create two child classes, one empty if necessary: you\'ll gain much more fine grained type-hinting. ' .'If you need to mock a standalone class, create an interface, or maybe it\'s a value-object that shouldn\'t be mocked at all. ' .'If you need to extend a standalone class, create an interface and use the Composite pattern. ' - .'If you aren\'t ready yet for serious OOP, go with FinalInternalClassFixer, it\'s fine.', + .'If these rules are too strict for you, you can use `FinalInternalClassFixer` instead.', 'Risky when subclassing non-abstract classes.' ); } /** * {@inheritdoc} + * + * Must run before ProtectedToPrivateFixer, SelfStaticAccessorFixer. */ + public function getPriority(): int + { + return parent::getPriority(); + } + protected function createProxyFixers(): array { $fixer = new FinalInternalClassFixer(); $fixer->configure([ - 'annotation_include' => [], + 'include' => [], 'consider_absent_docblock_as_internal_class' => true, ]); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php index 8dfc65e5b7..44278aa51c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalInternalClassFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -25,36 +26,62 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Options; /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * annotation_exclude?: list, + * annotation_include?: list, + * consider_absent_docblock_as_internal_class?: bool, + * exclude?: list, + * include?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * annotation_exclude: array, + * annotation_include: array, + * consider_absent_docblock_as_internal_class: bool, + * exclude: array, + * include: array + * } */ final class FinalInternalClassFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; - $intersect = array_intersect_assoc( - $this->configuration['annotation_include'], - $this->configuration['annotation_exclude'] - ); + private const DEFAULTS = [ + 'include' => [ + 'internal', + ], + 'exclude' => [ + 'final', + 'Entity', + 'ORM\Entity', + 'ORM\Mapping\Entity', + 'Mapping\Entity', + 'Document', + 'ODM\Document', + ], + ]; - if (\count($intersect) > 0) { - throw new InvalidFixerConfigurationException($this->getName(), sprintf('Annotation cannot be used in both the include and exclude list, got duplicates: "%s".', implode('", "', array_keys($intersect)))); - } + private bool $checkAttributes; + + public function __construct() + { + parent::__construct(); + + $this->checkAttributes = \PHP_VERSION_ID >= 8_00_00; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -64,8 +91,8 @@ public function getDefinition(): FixerDefinitionInterface new CodeSample( " ['@Custom'], - 'annotation_exclude' => ['@not-fix'], + 'include' => ['@Custom'], + 'exclude' => ['@not-fix'], ] ), ], @@ -85,53 +112,45 @@ public function getPriority(): int return 67; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_CLASS); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->assertConfigHasNoConflicts(); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 1; 0 <= $index; --$index) { - if (!$tokens[$index]->isGivenKind(T_CLASS) || $tokensAnalyzer->isAnonymousClass($index) || !$this->isClassCandidate($tokens, $index)) { + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isClassCandidate($tokensAnalyzer, $tokens, $index)) { continue; } - // make class final - $tokens->insertAt( - $index, - [ + // make class 'final' + $tokens->insertSlices([ + $index => [ new Token([T_FINAL, 'final']), new Token([T_WHITESPACE, ' ']), - ] - ); + ], + ]); } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $annotationsAsserts = [static function (array $values): bool { foreach ($values as $value) { - if (!\is_string($value) || '' === $value) { + if ('' === $value) { return false; } } @@ -142,7 +161,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn $annotationsNormalizer = static function (Options $options, array $value): array { $newValue = []; foreach ($value as $key) { - if ('@' === $key[0]) { + if (str_starts_with($key, '@')) { $key = substr($key, 1); } @@ -153,27 +172,43 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn }; return new FixerConfigurationResolver([ - (new FixerOptionBuilder('annotation_include', 'Class level annotations tags that must be set in order to fix the class. (case insensitive)')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('annotation_include', 'Class level attribute or annotation tags that must be set in order to fix the class (case insensitive).')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues($annotationsAsserts) + ->setDefault( + array_map( + static fn (string $string) => '@'.$string, + self::DEFAULTS['include'], + ), + ) + ->setNormalizer($annotationsNormalizer) + ->setDeprecationMessage('Use `include` to configure PHPDoc annotations tags and attributes.') + ->getOption(), + (new FixerOptionBuilder('annotation_exclude', 'Class level attribute or annotation tags that must be omitted to fix the class, even if all of the white list ones are used as well (case insensitive).')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues($annotationsAsserts) + ->setDefault( + array_map( + static fn (string $string) => '@'.$string, + self::DEFAULTS['exclude'], + ), + ) + ->setNormalizer($annotationsNormalizer) + ->setDeprecationMessage('Use `exclude` to configure PHPDoc annotations tags and attributes.') + ->getOption(), + (new FixerOptionBuilder('include', 'Class level attribute or annotation tags that must be set in order to fix the class (case insensitive).')) + ->setAllowedTypes(['string[]']) ->setAllowedValues($annotationsAsserts) - ->setDefault(['@internal']) + ->setDefault(self::DEFAULTS['include']) ->setNormalizer($annotationsNormalizer) ->getOption(), - (new FixerOptionBuilder('annotation_exclude', 'Class level annotations tags that must be omitted to fix the class, even if all of the white list ones are used as well. (case insensitive)')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('exclude', 'Class level attribute or annotation tags that must be omitted to fix the class, even if all of the white list ones are used as well (case insensitive).')) + ->setAllowedTypes(['string[]']) ->setAllowedValues($annotationsAsserts) - ->setDefault([ - '@final', - '@Entity', - '@ORM\Entity', - '@ORM\Mapping\Entity', - '@Mapping\Entity', - '@Document', - '@ODM\Document', - ]) + ->setDefault(self::DEFAULTS['exclude']) ->setNormalizer($annotationsNormalizer) ->getOption(), - (new FixerOptionBuilder('consider_absent_docblock_as_internal_class', 'Should classes without any DocBlock be fixed to final?')) + (new FixerOptionBuilder('consider_absent_docblock_as_internal_class', 'Whether classes without any DocBlock should be fixed to final.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), @@ -183,41 +218,157 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn /** * @param int $index T_CLASS index */ - private function isClassCandidate(Tokens $tokens, int $index): bool + private function isClassCandidate(TokensAnalyzer $tokensAnalyzer, Tokens $tokens, int $index): bool { - if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_ABSTRACT, T_FINAL])) { + if ($tokensAnalyzer->isAnonymousClass($index)) { + return false; + } + + $modifiers = $tokensAnalyzer->getClassyModifiers($index); + + if (isset($modifiers['final']) || isset($modifiers['abstract'])) { return false; // ignore class; it is abstract or already final } - $docToken = $tokens[$tokens->getPrevNonWhitespace($index)]; + $decisions = []; + $currentIndex = $index; + + $acceptTypes = [ + CT::T_ATTRIBUTE_CLOSE, + T_DOC_COMMENT, + T_COMMENT, // Skip comments + ]; + + if (\defined('T_READONLY')) { + // Skip readonly classes for PHP 8.2+ + $acceptTypes[] = T_READONLY; + } + + while ($currentIndex) { + $currentIndex = $tokens->getPrevNonWhitespace($currentIndex); + + if (!$tokens[$currentIndex]->isGivenKind($acceptTypes)) { + break; + } + + if ($this->checkAttributes && $tokens[$currentIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $attributeStartIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $currentIndex); + $decisions[] = $this->isClassCandidateBasedOnAttribute($tokens, $attributeStartIndex, $currentIndex); + + $currentIndex = $attributeStartIndex; + } + + if ($tokens[$currentIndex]->isGivenKind([T_DOC_COMMENT])) { + $decisions[] = $this->isClassCandidateBasedOnPhpDoc($tokens, $currentIndex); + } + } - if (!$docToken->isGivenKind(T_DOC_COMMENT)) { - return $this->configuration['consider_absent_docblock_as_internal_class']; + if (\in_array(false, $decisions, true)) { + return false; } - $doc = new DocBlock($docToken->getContent()); + return \in_array(true, $decisions, true) + || ([] === $decisions && true === $this->configuration['consider_absent_docblock_as_internal_class']); + } + + private function isClassCandidateBasedOnPhpDoc(Tokens $tokens, int $index): ?bool + { + $doc = new DocBlock($tokens[$index]->getContent()); $tags = []; foreach ($doc->getAnnotations() as $annotation) { - if (1 !== Preg::match('/@\S+(?=\s|$)/', $annotation->getContent(), $matches)) { + if (!Preg::match('/@([^\(\s]+)/', $annotation->getContent(), $matches)) { continue; } $tag = strtolower(substr(array_shift($matches), 1)); - foreach ($this->configuration['annotation_exclude'] as $tagStart => $true) { - if (str_starts_with($tag, $tagStart)) { - return false; // ignore class: class-level PHPDoc contains tag that has been excluded through configuration + + $tags[$tag] = true; + } + + if (\count(array_intersect_key($this->configuration['exclude'], $tags)) > 0) { + return false; + } + + if ($this->isConfiguredAsInclude($tags)) { + return true; + } + + return null; + } + + private function isClassCandidateBasedOnAttribute(Tokens $tokens, int $startIndex, int $endIndex): ?bool + { + $attributeCandidates = []; + $attributeString = ''; + $currentIndex = $startIndex; + + while ($currentIndex < $endIndex && null !== ($currentIndex = $tokens->getNextMeaningfulToken($currentIndex))) { + if (!$tokens[$currentIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + if ('' !== $attributeString) { + $attributeCandidates[$attributeString] = true; + $attributeString = ''; } + + continue; } - $tags[$tag] = true; + $attributeString .= strtolower($tokens[$currentIndex]->getContent()); + } + + if (\count(array_intersect_key($this->configuration['exclude'], $attributeCandidates)) > 0) { + return false; } - foreach ($this->configuration['annotation_include'] as $tag => $true) { - if (!isset($tags[$tag])) { - return false; // ignore class: class-level PHPDoc does not contain all tags that has been included through configuration + if ($this->isConfiguredAsInclude($attributeCandidates)) { + return true; + } + + return null; + } + + /** + * @param array $attributes + */ + private function isConfiguredAsInclude(array $attributes): bool + { + if (0 === \count($this->configuration['include'])) { + return true; + } + + return \count(array_intersect_key($this->configuration['include'], $attributes)) > 0; + } + + private function assertConfigHasNoConflicts(): void + { + foreach (['include' => 'annotation_include', 'exclude' => 'annotation_exclude'] as $newConfigKey => $oldConfigKey) { + $defaults = []; + + foreach (self::DEFAULTS[$newConfigKey] as $foo) { + $defaults[strtolower($foo)] = true; + } + + $newConfigIsSet = $this->configuration[$newConfigKey] !== $defaults; + $oldConfigIsSet = $this->configuration[$oldConfigKey] !== $defaults; + + if ($newConfigIsSet && $oldConfigIsSet) { + throw new InvalidFixerConfigurationException($this->getName(), \sprintf('Configuration cannot contain deprecated option "%s" and new option "%s".', $oldConfigKey, $newConfigKey)); + } + + if ($oldConfigIsSet) { + $this->configuration[$newConfigKey] = $this->configuration[$oldConfigKey]; // @phpstan-ignore-line crazy mapping, to be removed while cleaning up deprecated options + $this->checkAttributes = false; // run in old mode } + + // if ($newConfigIsSet) - only new config is set, all good + // if (!$newConfigIsSet && !$oldConfigIsSet) - both are set as to default values, all good + + unset($this->configuration[$oldConfigKey]); // @phpstan-ignore-line crazy mapping, to be removed while cleaning up deprecated options } - return true; + $intersect = array_intersect_assoc($this->configuration['include'], $this->configuration['exclude']); + + if (\count($intersect) > 0) { + throw new InvalidFixerConfigurationException($this->getName(), \sprintf('Annotation cannot be used in both "include" and "exclude" list, got duplicates: %s.', Utils::naturalLanguageJoin(array_keys($intersect)))); + } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php index c13c69db88..ed9a7ac7a5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/FinalPublicMethodForAbstractClassFixer.php @@ -26,6 +26,9 @@ */ final class FinalPublicMethodForAbstractClassFixer extends AbstractFixer { + /** + * @var array + */ private array $magicMethods = [ '__construct' => true, '__destruct' => true, @@ -44,9 +47,6 @@ final class FinalPublicMethodForAbstractClassFixer extends AbstractFixer '__debuginfo' => true, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -69,32 +69,23 @@ public function start() ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isAllTokenKindsFound([T_CLASS, T_ABSTRACT, T_PUBLIC, T_FUNCTION]); + return $tokens->isAllTokenKindsFound([T_ABSTRACT, T_PUBLIC, T_FUNCTION]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $classes = array_keys($tokens->findGivenKind(T_CLASS)); + $abstracts = array_keys($tokens->findGivenKind(T_ABSTRACT)); - while ($classIndex = array_pop($classes)) { - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)]; - if (!$prevToken->isGivenKind(T_ABSTRACT)) { + while ($abstractIndex = array_pop($abstracts)) { + $classIndex = $tokens->getNextTokenOfKind($abstractIndex, [[T_CLASS], [T_FUNCTION]]); + if (!$tokens[$classIndex]->isGivenKind(T_CLASS)) { continue; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php index 868cfd64fd..b09c2a2b9f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php @@ -27,17 +27,11 @@ */ final class NoBlankLinesAfterClassOpeningFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -68,9 +62,6 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php index 76f71cffb3..b08a92313e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php @@ -25,9 +25,6 @@ */ final class NoNullPropertyInitializationFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,17 +48,11 @@ class Foo { ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT]) && $tokens->isAnyTokenKindsFound([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $inClass = []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php index 33a22e78fd..6547cc624e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoPhp4ConstructorFixer.php @@ -27,9 +27,6 @@ */ final class NoPhp4ConstructorFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -59,25 +56,16 @@ public function getPriority(): int return 75; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_CLASS); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -118,6 +106,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void ++$i; } } + // and continue checking the classes that might follow continue; } @@ -150,7 +139,7 @@ private function fixConstructor(Tokens $tokens, string $className, int $classSta return; // no PHP4-constructor! } - if (!empty($php4['modifiers'][T_ABSTRACT]) || !empty($php4['modifiers'][T_STATIC])) { + if (isset($php4['modifiers'][T_ABSTRACT]) || isset($php4['modifiers'][T_STATIC])) { return; // PHP4 constructor can't be abstract or static } @@ -301,7 +290,7 @@ private function fixInfiniteRecursion(Tokens $tokens, int $start, int $end): voi * @param int $startIndex function/method start index * @param int $bodyIndex function/method body index * - * @return array an array containing the sequence and case sensitiveness [ 0 => $seq, 1 => $case ] + * @return array{list>, array{3: false}} */ private function getWrapperMethodSequence(Tokens $tokens, string $method, int $startIndex, int $bodyIndex): array { @@ -346,25 +335,31 @@ private function getWrapperMethodSequence(Tokens $tokens, string $method, int $s $sequences[] = $seq; } - return [$sequences, [3 => false]]; + return [$sequences, [3 => false]]; } /** * Find a function or method matching a given name within certain bounds. * + * Returns: + * - nameIndex (int): The index of the function/method name. + * - startIndex (int): The index of the function/method start. + * - endIndex (int): The index of the function/method end. + * - bodyIndex (int): The index of the function/method body. + * - modifiers (array): The modifiers as array keys and their index as the values, e.g. array(T_PUBLIC => 10) + * * @param Tokens $tokens the Tokens instance * @param string $name the function/Method name * @param int $startIndex the search start index * @param int $endIndex the search end index * - * @return null|array An associative array, if a match is found: - * - * - nameIndex (int): The index of the function/method name. - * - startIndex (int): The index of the function/method start. - * - endIndex (int): The index of the function/method end. - * - bodyIndex (int): The index of the function/method body. - * - modifiers (array): The modifiers as array keys and their index as - * the values, e.g. array(T_PUBLIC => 10) + * @return null|array{ + * nameIndex: int, + * startIndex: int, + * endIndex: int, + * bodyIndex: int, + * modifiers: list, + * } */ private function findFunction(Tokens $tokens, string $name, int $startIndex, int $endIndex): ?array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php index 9c366863c6..f4f4108210 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php @@ -16,23 +16,34 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * private_methods?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * private_methods: bool + * } */ final class NoUnneededFinalMethodFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -73,9 +84,6 @@ final private function bar1() {} ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { if (!$tokens->isAllTokenKindsFound([T_FINAL, T_FUNCTION])) { @@ -94,9 +102,6 @@ public function isRisky(): bool return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($this->getMethods($tokens) as $element) { @@ -116,9 +121,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -129,6 +131,18 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } + /** + * @return \Generator + */ private function getMethods(Tokens $tokens): \Generator { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -181,8 +195,8 @@ private function getMethods(Tokens $tokens): \Generator } if (!\array_key_exists($classIndex, $classesAreFinal)) { - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)]; - $classesAreFinal[$classIndex] = $prevToken->isGivenKind(T_FINAL); + $modifiers = $tokensAnalyzer->getClassyModifiers($classIndex); + $classesAreFinal[$classIndex] = isset($modifiers['final']); } $element['method_of_enum'] = false; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php index e3c69f7242..af3340c438 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedClassElementsFixer.php @@ -16,7 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; -use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,12 +26,40 @@ use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; /** * @author Gregor Harlan + * + * @phpstan-type _ClassElement array{ + * start: int, + * visibility: string, + * abstract: bool, + * static: bool, + * readonly: bool, + * type: string, + * name: string, + * end: int, + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool, + * order?: list, + * sort_algorithm?: 'alpha'|'none' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool, + * order: list, + * sort_algorithm: 'alpha'|'none' + * } */ final class OrderedClassElementsFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** @internal */ public const SORT_ALPHA = 'alpha'; @@ -44,7 +72,7 @@ final class OrderedClassElementsFixer extends AbstractFixer implements Configura ]; /** - * @var array Array containing all class element base types (keys) and their parent types (values) + * @var array> Array containing all class element base types (keys) and their parent types (values) */ private static array $typeHierarchy = [ 'use_trait' => null, @@ -85,7 +113,7 @@ final class OrderedClassElementsFixer extends AbstractFixer implements Configura ]; /** - * @var array Array containing special method types + * @var array Array containing special method types */ private static array $specialTypes = [ 'construct' => null, @@ -95,68 +123,15 @@ final class OrderedClassElementsFixer extends AbstractFixer implements Configura ]; /** - * @var array Resolved configuration array (type => position) - */ - private $typePosition; - - /** - * {@inheritdoc} + * @var array Resolved configuration array (type => position) */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->typePosition = []; - $pos = 0; - - foreach ($this->configuration['order'] as $type) { - $this->typePosition[$type] = $pos++; - } - - foreach (self::$typeHierarchy as $type => $parents) { - if (isset($this->typePosition[$type])) { - continue; - } - - if (!$parents) { - $this->typePosition[$type] = null; - - continue; - } - - foreach ($parents as $parent) { - if (isset($this->typePosition[$parent])) { - $this->typePosition[$type] = $this->typePosition[$parent]; - - continue 2; - } - } - - $this->typePosition[$type] = null; - } - - $lastPosition = \count($this->configuration['order']); - - foreach ($this->typePosition as &$pos) { - if (null === $pos) { - $pos = $lastPosition; - } - - $pos *= 10; // last digit is used by phpunit method ordering - } - } + private array $typePosition; - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -217,7 +192,28 @@ public function C(){} ', ['order' => ['method_public'], 'sort_algorithm' => self::SORT_ALPHA] ), - ] + new CodeSample( + ' ['method_public'], 'sort_algorithm' => self::SORT_ALPHA, 'case_sensitive' => true] + ), + ], + 'Accepts a subset of pre-defined element types, special element groups, and custom patterns. + +Element types: `[\''.implode('\', \'', array_keys(self::$typeHierarchy)).'\']` + +Special element types: `[\''.implode('\', \'', array_keys(self::$specialTypes)).'\']` + +Custom values: + +- `method:*`: specify a single method name (e.g. `method:__invoke`) to set the order of that specific method.' ); } @@ -232,9 +228,48 @@ public function getPriority(): int return 65; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->typePosition = []; + $position = 0; + + foreach ($this->configuration['order'] as $type) { + $this->typePosition[$type] = $position++; + } + + foreach (self::$typeHierarchy as $type => $parents) { + if (isset($this->typePosition[$type])) { + continue; + } + + if (null === $parents) { + $this->typePosition[$type] = null; + + continue; + } + + foreach ($parents as $parent) { + if (isset($this->typePosition[$parent])) { + $this->typePosition[$type] = $this->typePosition[$parent]; + + continue 2; + } + } + + $this->typePosition[$type] = null; + } + + $lastPosition = \count($this->configuration['order']); + + foreach ($this->typePosition as &$pos) { + if (null === $pos) { + $pos = $lastPosition; + } + + $pos *= 10; // last digit is used by phpunit method ordering + } + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($i = 1, $count = $tokens->count(); $i < $count; ++$i) { @@ -260,15 +295,28 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { + $builtIns = array_keys(array_merge(self::$typeHierarchy, self::$specialTypes)); + return new FixerConfigurationResolver([ (new FixerOptionBuilder('order', 'List of strings defining order of elements.')) - ->setAllowedTypes(['array']) - ->setAllowedValues([new AllowedValueSubset(array_keys(array_merge(self::$typeHierarchy, self::$specialTypes)))]) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([ + static function (array $values) use ($builtIns): bool { + foreach ($values as $value) { + if (\in_array($value, $builtIns, true)) { + return true; + } + + if ('method:' === substr($value, 0, 7)) { + return true; + } + } + + return false; + }, + ]) ->setDefault([ 'use_trait', 'case', @@ -287,15 +335,19 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn 'method_private', ]) ->getOption(), - (new FixerOptionBuilder('sort_algorithm', 'How multiple occurrences of same type statements should be sorted')) + (new FixerOptionBuilder('sort_algorithm', 'How multiple occurrences of same type statements should be sorted.')) ->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS) ->setDefault(self::SORT_NONE) ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), ]); } /** - * @return array[] + * @return list<_ClassElement> */ private function getElements(Tokens $tokens, int $startIndex): array { @@ -373,7 +425,7 @@ private function getElements(Tokens $tokens, int $startIndex): array } /** - * @return array|string type or array of type and name + * @return list|string type or array of type and name */ private function detectElementType(Tokens $tokens, int $index) { @@ -441,9 +493,9 @@ private function findElementEnd(Tokens $tokens, int $index): int } /** - * @param array[] $elements + * @param list<_ClassElement> $elements * - * @return array[] + * @return list<_ClassElement> */ private function sortElements(array $elements): array { @@ -460,18 +512,22 @@ private function sortElements(array $elements): array 'doteardown' => 10, ]; - foreach ($elements as &$element) { + $getPositionType = function (array $element) use ($phpunitPositions): int { $type = $element['type']; + if (\in_array($type, ['method', 'magic', 'phpunit'], true) && isset($this->typePosition["method:{$element['name']}"])) { + return $this->typePosition["method:{$element['name']}"]; + } + if (\array_key_exists($type, self::$specialTypes)) { if (isset($this->typePosition[$type])) { - $element['position'] = $this->typePosition[$type]; + $position = $this->typePosition[$type]; if ('phpunit' === $type) { - $element['position'] += $phpunitPositions[$element['name']]; + $position += $phpunitPositions[$element['name']]; } - continue; + return $position; } $type = 'method'; @@ -493,35 +549,42 @@ private function sortElements(array $elements): array } } - $element['position'] = $this->typePosition[$type]; - } - - unset($element); - - usort($elements, function (array $a, array $b): int { - if ($a['position'] === $b['position']) { - return $this->sortGroupElements($a, $b); - } - - return $a['position'] <=> $b['position']; - }); - - return $elements; + return $this->typePosition[$type]; + }; + + return Utils::stableSort( + $elements, + /** + * @return array{element: _ClassElement, position: int} + */ + static fn (array $element): array => ['element' => $element, 'position' => $getPositionType($element)], + /** + * @param array{element: _ClassElement, position: int} $a + * @param array{element: _ClassElement, position: int} $b + * + * @return -1|0|1 + */ + fn (array $a, array $b): int => ($a['position'] === $b['position']) ? $this->sortGroupElements($a['element'], $b['element']) : $a['position'] <=> $b['position'], + ); } + /** + * @param _ClassElement $a + * @param _ClassElement $b + */ private function sortGroupElements(array $a, array $b): int { - $selectedSortAlgorithm = $this->configuration['sort_algorithm']; - - if (self::SORT_ALPHA === $selectedSortAlgorithm) { - return strcasecmp($a['name'], $b['name']); + if (self::SORT_ALPHA === $this->configuration['sort_algorithm']) { + return true === $this->configuration['case_sensitive'] + ? $a['name'] <=> $b['name'] + : strcasecmp($a['name'], $b['name']); } return $a['start'] <=> $b['start']; } /** - * @param array[] $elements + * @param list<_ClassElement> $elements */ private function sortTokens(Tokens $tokens, int $startIndex, int $endIndex, array $elements): void { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php index a27bb38ff0..c2589df126 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedInterfacesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,9 +28,25 @@ /** * @author Dave van der Brugge + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool, + * direction?: 'ascend'|'descend', + * order?: 'alpha'|'length' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool, + * direction: 'ascend'|'descend', + * order: 'alpha'|'length' + * } */ final class OrderedInterfacesFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** @internal */ public const OPTION_DIRECTION = 'direction'; @@ -51,7 +68,7 @@ final class OrderedInterfacesFixer extends AbstractFixer implements Configurable /** * Array of supported directions in configuration. * - * @var string[] + * @var list */ private const SUPPORTED_DIRECTION_OPTIONS = [ self::DIRECTION_ASCEND, @@ -61,16 +78,13 @@ final class OrderedInterfacesFixer extends AbstractFixer implements Configurable /** * Array of supported orders in configuration. * - * @var string[] + * @var list */ private const SUPPORTED_ORDER_OPTIONS = [ self::ORDER_ALPHA, self::ORDER_LENGTH, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -94,32 +108,39 @@ public function getDefinition(): FixerDefinitionInterface self::OPTION_DIRECTION => self::DIRECTION_DESCEND, ] ), + new CodeSample( + " self::ORDER_ALPHA, + ] + ), + new CodeSample( + " self::ORDER_ALPHA, + 'case_sensitive' => true, + ] + ), ], - null, - "Risky for `implements` when specifying both an interface and its parent interface, because PHP doesn't break on `parent, child` but does on `child, parent`." ); } /** * {@inheritdoc} + * + * Must run after FullyQualifiedStrictTypesFixer. */ - public function isCandidate(Tokens $tokens): bool + public function getPriority(): int { - return $tokens->isTokenKindFound(T_IMPLEMENTS) - || $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]); + return 0; } - /** - * {@inheritdoc} - */ - public function isRisky(): bool + public function isCandidate(Tokens $tokens): bool { - return true; + return $tokens->isTokenKindFound(T_IMPLEMENTS) + || $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -138,7 +159,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $implementsStart = $index + 1; - $implementsEnd = $tokens->getPrevNonWhitespace($tokens->getNextTokenOfKind($implementsStart, ['{'])); + $implementsEnd = $tokens->getPrevMeaningfulToken($tokens->getNextTokenOfKind($implementsStart, ['{'])); $interfaces = $this->getInterfaces($tokens, $implementsStart, $implementsEnd); @@ -147,7 +168,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } foreach ($interfaces as $interfaceIndex => $interface) { - $interfaceTokens = Tokens::fromArray($interface, false); + $interfaceTokens = Tokens::fromArray($interface); $normalized = ''; $actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1); @@ -172,7 +193,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void usort($interfaces, function (array $first, array $second): int { $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER] ? \strlen($first['normalized']) - \strlen($second['normalized']) - : strcasecmp($first['normalized'], $second['normalized']); + : ( + true === $this->configuration['case_sensitive'] + ? $first['normalized'] <=> $second['normalized'] + : strcasecmp($first['normalized'], $second['normalized']) + ); if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) { $score *= -1; @@ -205,23 +230,27 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered')) + (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered.')) ->setAllowedValues(self::SUPPORTED_ORDER_OPTIONS) ->setDefault(self::ORDER_ALPHA) ->getOption(), - (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered')) + (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered.')) ->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS) ->setDefault(self::DIRECTION_ASCEND) ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), ]); } + /** + * @return array> + */ private function getInterfaces(Tokens $tokens, int $implementsStart, int $implementsEnd): array { $interfaces = []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php index 9602ec3afe..4d3b6dd7d0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTraitsFixer.php @@ -15,48 +15,70 @@ namespace PhpCsFixer\Fixer\ClassNotation; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Tokens; -final class OrderedTraitsFixer extends AbstractFixer +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool + * } + */ +final class OrderedTraitsFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Trait `use` statements must be sorted alphabetically.', [ new CodeSample(" true, + ] + ), ], null, 'Risky when depending on order of the imports.' ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(CT::T_USE_TRAIT); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($this->findUseStatementsGroups($tokens) as $uses) { @@ -88,6 +110,7 @@ private function findUseStatementsGroups(Tokens $tokens): iterable continue; } + $startIndex = $tokens->getNextNonWhitespace($tokens->getPrevMeaningfulToken($index)); $endIndex = $tokens->getNextTokenOfKind($index, [';', '{']); if ($tokens[$endIndex]->equals('{')) { @@ -96,11 +119,11 @@ private function findUseStatementsGroups(Tokens $tokens): iterable $use = []; - for ($i = $index; $i <= $endIndex; ++$i) { + for ($i = $startIndex; $i <= $endIndex; ++$i) { $use[] = $tokens[$i]; } - $uses[$index] = Tokens::fromArray($use); + $uses[$startIndex] = Tokens::fromArray($use); $index = $endIndex; } @@ -174,15 +197,20 @@ private function sort(Tokens $tokens, array $elements): void }; $sortedElements = $elements; - uasort($sortedElements, static function (Tokens $useA, Tokens $useB) use ($toTraitName): int { - return strcasecmp($toTraitName($useA), $toTraitName($useB)); - }); + uasort( + $sortedElements, + fn (Tokens $useA, Tokens $useB): int => true === $this->configuration['case_sensitive'] + ? $toTraitName($useA) <=> $toTraitName($useB) + : strcasecmp($toTraitName($useA), $toTraitName($useB)) + ); $sortedElements = array_combine( array_keys($elements), array_values($sortedElements) ); + $beforeOverrideCount = $tokens->count(); + foreach (array_reverse($sortedElements, true) as $index => $tokensToInsert) { $tokens->overrideRange( $index, @@ -190,5 +218,9 @@ private function sort(Tokens $tokens, array $elements): void $tokensToInsert ); } + + if ($beforeOverrideCount < $tokens->count()) { + $tokens->clearEmptyTokens(); + } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTypesFixer.php new file mode 100644 index 0000000000..e70c6baa83 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/OrderedTypesFixer.php @@ -0,0 +1,453 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author John Paul E. Balandan, CPA + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool, + * null_adjustment?: 'always_first'|'always_last'|'none', + * sort_algorithm?: 'alpha'|'none' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool, + * null_adjustment: 'always_first'|'always_last'|'none', + * sort_algorithm: 'alpha'|'none' + * } + */ +final class OrderedTypesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Sort union types and intersection types using configured order.', + [ + new CodeSample( + 'save($foo); +} catch (\RuntimeException|CacheException $e) { + logger($e); + + throw $e; +} +' + ), + new VersionSpecificCodeSample( + ' true, + ] + ), + new VersionSpecificCodeSample( + ' 'always_last'] + ), + new VersionSpecificCodeSample( + ' 'none', + 'null_adjustment' => 'always_last', + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before TypesSpacesFixer. + * Must run after NullableTypeDeclarationFixer, NullableTypeDeclarationForDefaultNullValueFixer. + */ + public function getPriority(): int + { + return 0; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('sort_algorithm', 'Whether the types should be sorted alphabetically, or not sorted.')) + ->setAllowedValues(['alpha', 'none']) + ->setDefault('alpha') + ->getOption(), + (new FixerOptionBuilder('null_adjustment', 'Forces the position of `null` (overrides `sort_algorithm`).')) + ->setAllowedValues(['always_first', 'always_last', 'none']) + ->setDefault('always_first') + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach ($this->getElements($tokens) as $index => $type) { + if ('catch' === $type) { + $this->fixCatchArgumentType($tokens, $index); + + continue; + } + + if ('property' === $type) { + $this->fixPropertyType($tokens, $index); + + continue; + } + + $this->fixMethodArgumentType($functionsAnalyzer, $tokens, $index); + $this->fixMethodReturnType($functionsAnalyzer, $tokens, $index); + } + } + + /** + * @return array + * + * @phpstan-return array + */ + private function getElements(Tokens $tokens): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $elements = array_map( + static fn (array $element): string => $element['type'], + array_filter( + $tokensAnalyzer->getClassyElements(), + static fn (array $element): bool => \in_array($element['type'], ['method', 'property'], true) + ) + ); + + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_CATCH)) { + $elements[$index] = 'catch'; + + continue; + } + + if ( + $token->isGivenKind(T_FN) + || ($token->isGivenKind(T_FUNCTION) && !isset($elements[$index])) + ) { + $elements[$index] = 'method'; + } + } + + return $elements; + } + + private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis + { + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex); + $typeEndIndex = $typeStartIndex; + + for ($i = $typeStartIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null; + } + + private function fixCatchArgumentType(Tokens $tokens, int $index): void + { + $catchStart = $tokens->getNextTokenOfKind($index, ['(']); + $catchEnd = $tokens->getNextTokenOfKind($catchStart, [')', [T_VARIABLE]]); + + $catchArgumentType = $this->collectTypeAnalysis($tokens, $catchStart, $catchEnd); + + if (null === $catchArgumentType || !$this->isTypeSortable($catchArgumentType)) { + return; // nothing to fix + } + + $this->sortTypes($catchArgumentType, $tokens); + } + + private function fixPropertyType(Tokens $tokens, int $index): void + { + $propertyIndex = $index; + $propertyModifiers = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + + if (\defined('T_READONLY')) { + $propertyModifiers[] = T_READONLY; // @TODO drop condition when PHP 8.1 is supported + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while (!$tokens[$index]->isGivenKind($propertyModifiers)); + + $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyIndex); + + if (null === $propertyType || !$this->isTypeSortable($propertyType)) { + return; // nothing to fix + } + + $this->sortTypes($propertyType, $tokens); + } + + private function fixMethodArgumentType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + foreach ($functionsAnalyzer->getFunctionArguments($tokens, $index) as $argumentInfo) { + $argumentType = $argumentInfo->getTypeAnalysis(); + + if (null === $argumentType || !$this->isTypeSortable($argumentType)) { + continue; // nothing to fix + } + + $this->sortTypes($argumentType, $tokens); + } + } + + private function fixMethodReturnType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + $returnType = $functionsAnalyzer->getFunctionReturnType($tokens, $index); + + if (null === $returnType || !$this->isTypeSortable($returnType)) { + return; // nothing to fix + } + + $this->sortTypes($returnType, $tokens); + } + + private function sortTypes(TypeAnalysis $typeAnalysis, Tokens $tokens): void + { + $type = $typeAnalysis->getName(); + + if (str_contains($type, '|') && str_contains($type, '&')) { + // a DNF type of the form (A&B)|C, available as of PHP 8.2 + [$originalTypes, $glue] = $this->collectDisjunctiveNormalFormTypes($type); + } else { + [$originalTypes, $glue] = $this->collectUnionOrIntersectionTypes($type); + } + + // If the $types array is coming from a DNF type, then we have parts + // which are also array. If so, we sort those sub-types first before + // running the sorting algorithm to the entire $types array. + $sortedTypes = array_map(function ($subType) { + if (\is_array($subType)) { + return $this->runTypesThroughSortingAlgorithm($subType); + } + + return $subType; + }, $originalTypes); + + $sortedTypes = $this->runTypesThroughSortingAlgorithm($sortedTypes); + + if ($sortedTypes === $originalTypes) { + return; + } + + $tokens->overrideRange( + $typeAnalysis->getStartIndex(), + $typeAnalysis->getEndIndex(), + $this->createTypeDeclarationTokens($sortedTypes, $glue) + ); + } + + private function isTypeSortable(TypeAnalysis $type): bool + { + return str_contains($type->getName(), '|') || str_contains($type->getName(), '&'); + } + + /** + * @return array{0: list|string>, 1: string} + */ + private function collectDisjunctiveNormalFormTypes(string $type): array + { + $types = array_map(static function (string $subType) { + if (str_starts_with($subType, '(')) { + return explode('&', trim($subType, '()')); + } + + return $subType; + }, explode('|', $type)); + + return [$types, '|']; + } + + /** + * @return array{0: list, 1: string} + */ + private function collectUnionOrIntersectionTypes(string $type): array + { + $types = explode('|', $type); + $glue = '|'; + + if (1 === \count($types)) { + $types = explode('&', $type); + $glue = '&'; + } + + return [$types, $glue]; + } + + /** + * @param list|string> $types + * + * @return ($types is list ? list : list>) + */ + private function runTypesThroughSortingAlgorithm(array $types): array + { + $normalizeType = static fn (string $type): string => Preg::replace('/^\\\?/', '', $type); + + usort($types, function ($a, $b) use ($normalizeType): int { + if (\is_array($a)) { + $a = implode('&', $a); + } + + if (\is_array($b)) { + $b = implode('&', $b); + } + + $a = $normalizeType($a); + $b = $normalizeType($b); + $lowerCaseA = strtolower($a); + $lowerCaseB = strtolower($b); + + if ('none' !== $this->configuration['null_adjustment']) { + if ('null' === $lowerCaseA && 'null' !== $lowerCaseB) { + return 'always_last' === $this->configuration['null_adjustment'] ? 1 : -1; + } + + if ('null' !== $lowerCaseA && 'null' === $lowerCaseB) { + return 'always_last' === $this->configuration['null_adjustment'] ? -1 : 1; + } + } + + if ('alpha' === $this->configuration['sort_algorithm']) { + return true === $this->configuration['case_sensitive'] ? $a <=> $b : strcasecmp($a, $b); + } + + return 0; + }); + + return $types; + } + + /** + * @param list|string> $types + * + * @return list + */ + private function createTypeDeclarationTokens(array $types, string $glue, bool $isDisjunctive = false): array + { + static $specialTypes = [ + 'array' => [CT::T_ARRAY_TYPEHINT, 'array'], + 'callable' => [T_CALLABLE, 'callable'], + 'static' => [T_STATIC, 'static'], + ]; + + static $glues = [ + '|' => [CT::T_TYPE_ALTERNATION, '|'], + '&' => [CT::T_TYPE_INTERSECTION, '&'], + ]; + + $count = \count($types); + $newTokens = []; + + foreach ($types as $i => $type) { + if (\is_array($type)) { + $newTokens = [ + ...$newTokens, + ...$this->createTypeDeclarationTokens($type, '&', true), + ]; + } elseif (isset($specialTypes[$type])) { + $newTokens[] = new Token($specialTypes[$type]); + } else { + foreach (explode('\\', $type) as $nsIndex => $value) { + if (0 === $nsIndex && '' === $value) { + continue; + } + + if ($nsIndex > 0) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + $newTokens[] = new Token([T_STRING, $value]); + } + } + + if ($i <= $count - 2) { + $newTokens[] = new Token($glues[$glue]); + } + } + + if ($isDisjunctive) { + array_unshift($newTokens, new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '('])); + $newTokens[] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')']); + } + + return $newTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/PhpdocReadonlyClassCommentToKeywordFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/PhpdocReadonlyClassCommentToKeywordFixer.php new file mode 100644 index 0000000000..7891604738 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/PhpdocReadonlyClassCommentToKeywordFixer.php @@ -0,0 +1,129 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ClassNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Marcel Behrmann + */ +final class PhpdocReadonlyClassCommentToKeywordFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + * + * Must run before NoEmptyPhpdocFixer, NoExtraBlankLinesFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 4; + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_02_00 && $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts readonly comment on classes to the readonly keyword.', + [ + new VersionSpecificCodeSample( + << $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $doc = new DocBlock($token->getContent()); + + $annotations = $doc->getAnnotationsOfType('readonly'); + + if (0 === \count($annotations)) { + continue; + } + + foreach ($annotations as $annotation) { + $annotation->remove(); + } + + $mainIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + $addReadonly = true; + + while ($tokens[$index]->isGivenKind([ + T_ABSTRACT, + T_FINAL, + T_PRIVATE, + T_PUBLIC, + T_PROTECTED, + T_READONLY, + ])) { + if ($tokens[$index]->isGivenKind([T_READONLY])) { + $addReadonly = false; + } + + $index = $tokens->getNextMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_CLASS)) { + continue; + } + + if ($addReadonly) { + $tokens->insertAt($index, [new Token([T_READONLY, 'readonly']), new Token([T_WHITESPACE, ' '])]); + } + + $newContent = $doc->getContent(); + + if ('' === $newContent) { + $tokens->clearTokenAndMergeSurroundingWhitespace($mainIndex); + + continue; + } + + $tokens[$mainIndex] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php index 40e8583460..168bbb3b6c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php @@ -28,9 +28,8 @@ */ final class ProtectedToPrivateFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ + private TokensAnalyzer $tokensAnalyzer; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -56,16 +55,13 @@ protected function test() * {@inheritdoc} * * Must run before OrderedClassElementsFixer. - * Must run after FinalInternalClassFixer. + * Must run after FinalClassFixer, FinalInternalClassFixer. */ public function getPriority(): int { return 66; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { if (\defined('T_ENUM') && $tokens->isAllTokenKindsFound([T_ENUM, T_PROTECTED])) { // @TODO: drop condition when PHP 8.1+ is required @@ -75,12 +71,9 @@ public function isCandidate(Tokens $tokens): bool return $tokens->isAllTokenKindsFound([T_CLASS, T_FINAL, T_PROTECTED]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $tokensAnalyzer = new TokensAnalyzer($tokens); + $this->tokensAnalyzer = new TokensAnalyzer($tokens); $modifierKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_NS_SEPARATOR, T_STRING, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, T_STATIC, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]; if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required @@ -90,7 +83,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $classesCandidate = []; $classElementTypes = ['method' => true, 'property' => true, 'const' => true]; - foreach ($tokensAnalyzer->getClassyElements() as $index => $element) { + foreach ($this->tokensAnalyzer->getClassyElements() as $index => $element) { $classIndex = $element['classIndex']; if (!\array_key_exists($classIndex, $classesCandidate)) { @@ -98,7 +91,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } if (false === $classesCandidate[$classIndex]) { - continue; // not "final" class, "extends", is "anonymous", enum or uses trait + continue; } if (!isset($classElementTypes[$element['type']])) { @@ -132,15 +125,28 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + /** + * Consider symbol as candidate for fixing if it's: + * - an Enum (PHP8.1+) + * - a class, which: + * - is not anonymous + * - is not final + * - does not use traits + * - does not extend other class. + */ private function isClassCandidate(Tokens $tokens, int $classIndex): bool { if (\defined('T_ENUM') && $tokens[$classIndex]->isGivenKind(T_ENUM)) { // @TODO: drop condition when PHP 8.1+ is required return true; } - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classIndex)]; + if (!$tokens[$classIndex]->isGivenKind(T_CLASS) || $this->tokensAnalyzer->isAnonymousClass($classIndex)) { + return false; + } + + $modifiers = $this->tokensAnalyzer->getClassyModifiers($classIndex); - if (!$prevToken->isGivenKind(T_FINAL)) { + if (!isset($modifiers['final'])) { return false; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php index 568ab6e23d..7150b6bbd9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfAccessorFixer.php @@ -19,7 +19,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -30,9 +29,6 @@ */ final class SelfAccessorFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -58,9 +54,6 @@ public function getBar() ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); @@ -68,20 +61,24 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} + * + * Must run after PsrAutoloadingFixer. */ + public function getPriority(): int + { + return -11; + } + public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); - foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { + foreach ($tokens->getNamespaceDeclarations() as $namespace) { for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) { if (!$tokens[$index]->isGivenKind([T_CLASS, T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($index)) { continue; @@ -123,7 +120,21 @@ private function replaceNameOccurrences(Tokens $tokens, string $namespace, strin continue; } + if ($token->isGivenKind(T_FN)) { + $i = $tokensAnalyzer->getLastTokenIndexOfArrowFunction($i); + $i = $tokens->getNextMeaningfulToken($i); + + continue; + } + if ($token->isGivenKind(T_FUNCTION)) { + if ($tokensAnalyzer->isLambda($i)) { + $i = $tokens->getNextTokenOfKind($i, ['{']); + $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i); + + continue; + } + $i = $tokens->getNextTokenOfKind($i, ['(']); $insideMethodSignatureUntil = $tokens->getNextTokenOfKind($i, ['{', ';']); @@ -158,7 +169,7 @@ private function replaceNameOccurrences(Tokens $tokens, string $namespace, strin || ( null !== $insideMethodSignatureUntil && $i < $insideMethodSignatureUntil - && $prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [CT::T_NULLABLE_TYPE]]) + && $prevToken->equalsAny(['(', ',', [CT::T_NULLABLE_TYPE], [CT::T_TYPE_ALTERNATION], [CT::T_TYPE_COLON]]) ) ) { for ($j = $classStartIndex; $j < $i; ++$j) { @@ -173,7 +184,7 @@ private function getClassStart(Tokens $tokens, int $index, string $namespace): ? { $namespace = ('' !== $namespace ? '\\'.$namespace : '').'\\'; - foreach (array_reverse(Preg::split('/(\\\\)/', $namespace, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) as $piece) { + foreach (array_reverse(Preg::split('/(\\\)/', $namespace, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) as $piece) { $index = $tokens->getPrevMeaningfulToken($index); if ('\\' === $piece) { if (!$tokens[$index]->isGivenKind(T_NS_SEPARATOR)) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php index 0c66ca6d6c..0bf56ab0d0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SelfStaticAccessorFixer.php @@ -18,24 +18,20 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; final class SelfStaticAccessorFixer extends AbstractFixer { - /** - * @var TokensAnalyzer - */ - private $tokensAnalyzer; + private TokensAnalyzer $tokensAnalyzer; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Inside a `final` class or anonymous class `self` should be preferred to `static`.', + 'Inside an enum or `final`/anonymous class, `self` should be preferred over `static`.', [ new CodeSample( 'isAllTokenKindsFound([T_CLASS, T_STATIC]) && $tokens->isAnyTokenKindsFound([T_DOUBLE_COLON, T_NEW, T_INSTANCEOF]); + $classyTypes = [T_CLASS]; + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $classyTypes[] = T_ENUM; + } + + return $tokens->isTokenKindFound(T_STATIC) + && $tokens->isAnyTokenKindsFound($classyTypes) + && $tokens->isAnyTokenKindsFound([T_DOUBLE_COLON, T_NEW, T_INSTANCEOF]); } /** * {@inheritdoc} * - * Must run after FinalInternalClassFixer, FunctionToConstantFixer, PhpUnitTestCaseStaticMethodCallsFixer. + * Must run after FinalClassFixer, FinalInternalClassFixer, FunctionToConstantFixer, PhpUnitTestCaseStaticMethodCallsFixer. */ public function getPriority(): int { return -10; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { + $classyTokensOfInterest = [[T_CLASS]]; + + if (\defined('T_ENUM')) { + $classyTokensOfInterest[] = [T_ENUM]; // @TODO drop condition when PHP 8.1+ is required + } + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + $classyIndex = $tokens->getNextTokenOfKind(0, $classyTokensOfInterest); - $classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]); + while (null !== $classyIndex) { + if ($tokens[$classyIndex]->isGivenKind(T_CLASS)) { + $modifiers = $this->tokensAnalyzer->getClassyModifiers($classyIndex); - while (null !== $classIndex) { - if ( - $this->tokensAnalyzer->isAnonymousClass($classIndex) - || $tokens[$tokens->getPrevMeaningfulToken($classIndex)]->isGivenKind(T_FINAL) - ) { - $classIndex = $this->fixClass($tokens, $classIndex); + if ( + isset($modifiers['final']) + || $this->tokensAnalyzer->isAnonymousClass($classyIndex) + ) { + $classyIndex = $this->fixClassy($tokens, $classyIndex); + } + } else { + $classyIndex = $this->fixClassy($tokens, $classyIndex); } - $classIndex = $tokens->getNextTokenOfKind($classIndex, [[T_CLASS]]); + $classyIndex = $tokens->getNextTokenOfKind($classyIndex, $classyTokensOfInterest); } } - private function fixClass(Tokens $tokens, int $index): int + private function fixClassy(Tokens $tokens, int $index): int { $index = $tokens->getNextTokenOfKind($index, ['{']); $classOpenCount = 1; @@ -164,7 +187,7 @@ private function fixClass(Tokens $tokens, int $index): int } elseif ($tokens[$index]->equals('{')) { ++$openCount; } else { - $index = $this->fixClass($tokens, $index); + $index = $this->fixClassy($tokens, $index); } } while ($openCount > 0); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php index 2c8c8dcbab..5b1f781e1f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; @@ -35,12 +36,21 @@ * * @author Javier Spagnoletti * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * elements?: list<'const'|'property'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * elements: list<'const'|'property'> + * } */ final class SingleClassElementPerStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); @@ -56,9 +66,6 @@ public function getPriority(): int return 56; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -87,9 +94,6 @@ final class Example ); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $analyzer = new TokensAnalyzer($tokens); @@ -104,9 +108,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $values = ['const', 'property']; @@ -114,7 +115,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn return new FixerConfigurationResolver([ (new FixerOptionBuilder('elements', 'List of strings which element should be modified.')) ->setDefault($values) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($values)]) ->getOption(), ]); @@ -207,7 +208,7 @@ private function expandElement(Tokens $tokens, string $type, int $startIndex, in } /** - * @return Token[] + * @return list */ private function getModifiersSequences(Tokens $tokens, string $type, int $startIndex, int $endIndex): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php index d3d839fb01..03eb845aa2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/SingleTraitInsertPerStatementFixer.php @@ -70,7 +70,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @param int[] $candidates ',' indices to fix + * @param list $candidates ',' indices to fix */ private function fixTraitUse(Tokens $tokens, int $useTraitIndex, array $candidates): void { @@ -83,7 +83,7 @@ private function fixTraitUse(Tokens $tokens, int $useTraitIndex, array $candidat $nextImportStartIndex = $tokens->getNextMeaningfulToken($commaIndex); if ($tokens[$nextImportStartIndex - 1]->isWhitespace()) { - if (1 === Preg::match('/\R/', $tokens[$nextImportStartIndex - 1]->getContent())) { + if (Preg::match('/\R/', $tokens[$nextImportStartIndex - 1]->getContent())) { array_unshift($inserts, clone $tokens[$useTraitIndex - 1]); } $tokens->clearAt($nextImportStartIndex - 1); @@ -95,7 +95,7 @@ private function fixTraitUse(Tokens $tokens, int $useTraitIndex, array $candidat } /** - * @return int[] + * @return list */ private function getCandidates(Tokens $tokens, int $index): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php index 4034f7265f..b85f4cf923 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassNotation/VisibilityRequiredFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -32,12 +33,21 @@ * Fixer for rules defined in PSR2 ¶4.3, ¶4.5. * * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * elements?: list<'const'|'method'|'property'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * elements: list<'const'|'method'|'property'> + * } */ final class VisibilityRequiredFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -71,34 +81,35 @@ class Sample /** * {@inheritdoc} + * + * Must run before ClassAttributesSeparationFixer. */ + public function getPriority(): int + { + return 56; + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('elements', 'The structural elements to fix (PHP >= 7.1 required for `const`).')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])]) ->setDefault(['property', 'method', 'const']) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); - $propertyTypeDeclarationKinds = [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]; + $propertyTypeDeclarationKinds = [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]; if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required $propertyReadOnlyType = T_READONLY; @@ -108,7 +119,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $expectedKindsGeneric = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; - $expectedKindsPropertyKinds = array_merge($expectedKindsGeneric, $propertyTypeDeclarationKinds); + $expectedKindsPropertyKinds = [...$expectedKindsGeneric, ...$propertyTypeDeclarationKinds]; foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) { if (!\in_array($element['type'], $this->configuration['elements'], true)) { @@ -123,8 +134,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $prevIndex = $tokens->getPrevMeaningfulToken($index); $expectedKinds = 'property' === $element['type'] ? $expectedKindsPropertyKinds - : $expectedKindsGeneric - ; + : $expectedKindsGeneric; while ($tokens[$prevIndex]->isGivenKind($expectedKinds)) { if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php index 1d0610ea2f..2add0308df 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ClassUsage/DateTimeImmutableFixer.php @@ -27,9 +27,6 @@ */ final class DateTimeImmutableFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -40,25 +37,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php index c28b8891a7..770814b20d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/CommentToPhpdocFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -31,25 +32,31 @@ /** * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * ignored_tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * ignored_tags: list + * } */ final class CommentToPhpdocFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var string[] + * @var list */ private array $ignoredTags = []; - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_COMMENT); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -58,7 +65,7 @@ public function isRisky(): bool /** * {@inheritdoc} * - * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. * Must run after AlignMultilineCommentFixer. */ public function getPriority(): int @@ -67,9 +74,6 @@ public function getPriority(): int return 26; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -83,37 +87,24 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - $this->ignoredTags = array_map( - static function (string $tag): string { - return strtolower($tag); - }, + static fn (string $tag): string => strtolower($tag), $this->configuration['ignored_tags'] ); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('ignored_tags', 'List of ignored tags')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('ignored_tags', 'List of ignored tags.')) + ->setAllowedTypes(['string[]']) ->setDefault([]) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $commentsAnalyzer = new CommentsAnalyzer(); @@ -144,7 +135,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @param int[] $indices + * @param list $indices */ private function isCommentCandidate(Tokens $tokens, array $indices): bool { @@ -154,7 +145,7 @@ function (bool $carry, int $index) use ($tokens): bool { if ($carry) { return true; } - if (1 !== Preg::match('~(?:#|//|/\*+|\R(?:\s*\*)?)\s*\@([a-zA-Z0-9_\\\\-]+)(?=\s|\(|$)~', $tokens[$index]->getContent(), $matches)) { + if (!Preg::match('~(?:#|//|/\*+|\R(?:\s*\*)?)\s*\@([a-zA-Z0-9_\\\-]+)(?=\s|\(|$)~', $tokens[$index]->getContent(), $matches)) { return false; } @@ -165,12 +156,12 @@ function (bool $carry, int $index) use ($tokens): bool { } /** - * @param int[] $indices + * @param non-empty-list $indices */ private function fixComment(Tokens $tokens, array $indices): void { if (1 === \count($indices)) { - $this->fixCommentSingleLine($tokens, reset($indices)); + $this->fixCommentSingleLine($tokens, $indices[0]); } else { $this->fixCommentMultiLine($tokens, $indices); } @@ -192,11 +183,11 @@ private function fixCommentSingleLine(Tokens $tokens, int $index): void } /** - * @param int[] $indices + * @param non-empty-list $indices */ private function fixCommentMultiLine(Tokens $tokens, array $indices): void { - $startIndex = reset($indices); + $startIndex = $indices[0]; $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$startIndex - 1]); $newContent = '/**'.$this->whitespacesConfig->getLineEnding(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php index 044bf1e833..cdf0be00c7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/HeaderCommentFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -31,9 +32,27 @@ /** * @author Antonio J. García Lagar + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * comment_type?: 'PHPDoc'|'comment', + * header: string, + * location?: 'after_declare_strict'|'after_open', + * separate?: 'both'|'bottom'|'none'|'top' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * comment_type: 'PHPDoc'|'comment', + * header: string, + * location: 'after_declare_strict'|'after_open', + * separate: 'both'|'bottom'|'none'|'top' + * } */ final class HeaderCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -44,9 +63,6 @@ final class HeaderCommentFixer extends AbstractFixer implements ConfigurableFixe */ public const HEADER_COMMENT = 'comment'; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -113,18 +129,15 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isMonolithicPhp(); + return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } /** * {@inheritdoc} * - * Must run before SingleLineCommentStyleFixer. + * Must run before BlankLinesBeforeNamespaceFixer, SingleBlankLineBeforeNamespaceFixer, SingleLineCommentStyleFixer. * Must run after DeclareStrictTypesFixer, NoBlankLinesAfterPhpdocFixer. */ public function getPriority(): int @@ -135,9 +148,6 @@ public function getPriority(): int return -30; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $location = $this->configuration['location']; @@ -172,7 +182,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $expectedLocation = $possibleLocation === $location; if (!$sameComment || !$expectedLocation) { - if ($expectedLocation ^ $sameComment) { + if ($expectedLocation xor $sameComment) { $this->removeHeader($tokens, $headerCurrentIndex); } @@ -191,9 +201,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $fixerName = $this->getName(); @@ -278,11 +285,7 @@ private function findHeaderCommentCurrentIndex(Tokens $tokens, int $headerNewInd */ private function findHeaderCommentInsertionIndex(Tokens $tokens, string $location): int { - $openTagIndex = $tokens[0]->isGivenKind(T_OPEN_TAG) ? 0 : $tokens->getNextTokenOfKind(0, [[T_OPEN_TAG]]); - - if (null === $openTagIndex) { - return 1; - } + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; if ('after_open' === $location) { return $openTagIndex + 1; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php index d27585ea0b..055c701bb1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php @@ -27,9 +27,6 @@ */ final class MultilineCommentOpeningClosingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,37 +34,31 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( <<<'EOT' -isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -84,11 +75,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // Fix opening if ($token->isGivenKind(T_COMMENT)) { - $newContent = Preg::replace('/^\\/\\*{2,}(?!\\/)/', '/*', $newContent); + $newContent = Preg::replace('/^\/\*{2,}(?!\/)/', '/*', $newContent); } // Fix closing - $newContent = Preg::replace('/(?getId(), $newContent]); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php index 7d3ca2c510..23f0b1a3a8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoEmptyCommentFixer.php @@ -40,9 +40,6 @@ public function getPriority(): int return 2; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,17 +48,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { @@ -69,7 +60,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - [$blockStart, $index, $isEmpty] = $this->getCommentBlock($tokens, $index); + $blockInfo = $this->getCommentBlock($tokens, $index); + $blockStart = $blockInfo['blockStart']; + $index = $blockInfo['blockEnd']; + $isEmpty = $blockInfo['isEmpty']; + if (false === $isEmpty) { continue; } @@ -84,6 +79,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void * Return the start index, end index and a flag stating if the comment block is empty. * * @param int $index T_COMMENT index + * + * @return array{blockStart: int, blockEnd: int, isEmpty: bool} */ private function getCommentBlock(Tokens $tokens, int $index): array { @@ -91,7 +88,11 @@ private function getCommentBlock(Tokens $tokens, int $index): array $empty = $this->isEmptyComment($tokens[$index]->getContent()); if (self::TYPE_SLASH_ASTERISK === $commentType) { - return [$index, $index, $empty]; + return [ + 'blockStart' => $index, + 'blockEnd' => $index, + 'isEmpty' => $empty, + ]; } $start = $index; @@ -116,7 +117,11 @@ private function getCommentBlock(Tokens $tokens, int $index): array } } - return [$start, $index - 1, $empty]; + return [ + 'blockStart' => $start, + 'blockEnd' => $index - 1, + 'isEmpty' => $empty, + ]; } private function getCommentType(string $content): int @@ -152,6 +157,6 @@ private function isEmptyComment(string $content): bool $type = $this->getCommentType($content); - return 1 === Preg::match($mapper[$type], $content); + return Preg::match($mapper[$type], $content); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php index 2c37ae098a..d17a449591 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/NoTrailingWhitespaceInCommentFixer.php @@ -27,9 +27,6 @@ */ final class NoTrailingWhitespaceInCommentFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,17 +48,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php index 490ee88f92..7b6931ccd3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentSpacingFixer.php @@ -24,9 +24,6 @@ final class SingleLineCommentSpacingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,17 +50,11 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { @@ -108,7 +99,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // fix space between comment open and leading text private function fixCommentLeadingSpace(string $content, string $prefix): string { - if (0 !== Preg::match(sprintf('@^%s\h+.*$@', preg_quote($prefix, '@')), $content)) { + if (Preg::match(\sprintf('@^%s\h+.*$@', preg_quote($prefix, '@')), $content)) { return $content; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php index 7ce0148c03..2fc38dcb47 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Comment/SingleLineCommentStyleFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -29,9 +30,21 @@ /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * comment_types?: list<'asterisk'|'hash'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * comment_types: list<'asterisk'|'hash'> + * } */ final class SingleLineCommentStyleFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var bool */ @@ -42,20 +55,6 @@ final class SingleLineCommentStyleFixer extends AbstractFixer implements Configu */ private $hashEnabled; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true); - $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true); - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -112,17 +111,17 @@ public function getPriority(): int return -31; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_COMMENT); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true); + $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -131,7 +130,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $content = $token->getContent(); - $commentContent = substr($content, 2, -2) ?: ''; + + /** @TODO PHP 8.0 - no more need for `?: ''` */ + $commentContent = substr($content, 2, -2) ?: ''; // @phpstan-ignore-line if ($this->hashEnabled && str_starts_with($content, '#')) { if (isset($content[1]) && '[' === $content[1]) { @@ -147,7 +148,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void !$this->asteriskEnabled || str_contains($commentContent, '?>') || !str_starts_with($content, '/*') - || 1 === Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent) + || Preg::match('/[^\s\*].*\R.*[^\s\*]/s', $commentContent) ) { continue; } @@ -155,7 +156,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $nextTokenIndex = $index + 1; if (isset($tokens[$nextTokenIndex])) { $nextToken = $tokens[$nextTokenIndex]; - if (!$nextToken->isWhitespace() || 1 !== Preg::match('/\R/', $nextToken->getContent())) { + if (!$nextToken->isWhitespace() || !Preg::match('/\R/', $nextToken->getContent())) { continue; } @@ -163,21 +164,18 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $content = '//'; - if (1 === Preg::match('/[^\s\*]/', $commentContent)) { + if (Preg::match('/[^\s\*]/', $commentContent)) { $content = '// '.Preg::replace('/[\s\*]*([^\s\*](?:.+[^\s\*])?)[\s\*]*/', '\1', $commentContent); } $tokens[$index] = new Token([$token->getId(), $content]); } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('comment_types', 'List of comment types to fix')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('comment_types', 'List of comment types to fix.')) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset(['asterisk', 'hash'])]) ->setDefault(['asterisk', 'hash']) ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php index 1cc18ad1ae..443141c332 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerInterface.php @@ -19,6 +19,9 @@ /** * @author Dariusz Rumiński + * + * @template TFixerInputConfig of array + * @template TFixerComputedConfig of array */ interface ConfigurableFixerInterface extends FixerInterface { @@ -34,7 +37,7 @@ interface ConfigurableFixerInterface extends FixerInterface * eg `php_unit_strict` fixer allows to configure which methods should be fixed. * Finally, some fixers need configuration to work, eg `header_comment`. * - * @param array $configuration configuration depends on Fixer + * @param TFixerInputConfig $configuration configuration depends on Fixer * * @throws InvalidFixerConfigurationException */ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerTrait.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerTrait.php new file mode 100644 index 0000000000..366dfd4af4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConfigurableFixerTrait.php @@ -0,0 +1,127 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\ConfigurationException\InvalidForEnvFixerConfigurationException; +use PhpCsFixer\ConfigurationException\RequiredFixerConfigurationException; +use PhpCsFixer\Console\Application; +use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\ExceptionInterface; +use Symfony\Component\OptionsResolver\Exception\MissingOptionsException; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @template TFixerInputConfig of array + * @template TFixerComputedConfig of array + */ +trait ConfigurableFixerTrait +{ + /** + * @var null|TFixerComputedConfig + */ + protected $configuration; + + /** + * @var null|FixerConfigurationResolverInterface + */ + private $configurationDefinition; + + /** + * @param TFixerInputConfig $configuration + */ + final public function configure(array $configuration): void + { + $this->configurePreNormalisation($configuration); + + foreach ($this->getConfigurationDefinition()->getOptions() as $option) { + if (!$option instanceof DeprecatedFixerOption) { + continue; + } + + $name = $option->getName(); + if (\array_key_exists($name, $configuration)) { + Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf( + 'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s', + $name, + $this->getName(), + Application::getMajorVersion() + 1, + str_replace('`', '"', $option->getDeprecationMessage()) + ))); + } + } + + try { + $this->configuration = $this->getConfigurationDefinition()->resolve($configuration); // @phpstan-ignore-line ->configuration typehint is autogenerated base on ConfigurationDefinition + } catch (MissingOptionsException $exception) { + throw new RequiredFixerConfigurationException( + $this->getName(), + \sprintf('Missing required configuration: %s', $exception->getMessage()), + $exception + ); + } catch (InvalidOptionsForEnvException $exception) { + throw new InvalidForEnvFixerConfigurationException( + $this->getName(), + \sprintf('Invalid configuration for env: %s', $exception->getMessage()), + $exception + ); + } catch (ExceptionInterface $exception) { + throw new InvalidFixerConfigurationException( + $this->getName(), + \sprintf('Invalid configuration: %s', $exception->getMessage()), + $exception + ); + } + + $this->configurePostNormalisation(); + } + + final public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + if (null === $this->configurationDefinition) { + $this->configurationDefinition = $this->createConfigurationDefinition(); + } + + return $this->configurationDefinition; + } + + abstract public function getName(): string; + + /** + * One can override me. + * + * @param TFixerInputConfig $configuration + */ + protected function configurePreNormalisation(array &$configuration): void + { + // 🤔 ideally this method won't be needed, maybe we can remove it over time + } + + /** + * One can override me. + */ + protected function configurePostNormalisation(): void + { + // 🤔 ideally this method won't be needed, maybe we can remove it over time + } + + abstract protected function createConfigurationDefinition(): FixerConfigurationResolverInterface; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php index c6828d6ff6..7b53770bb9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -23,7 +24,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -32,9 +32,29 @@ /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * exclude?: list, + * fix_built_in?: bool, + * include?: list, + * scope?: 'all'|'namespaced', + * strict?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * exclude: list, + * fix_built_in: bool, + * include: list, + * scope: 'all'|'namespaced', + * strict: bool + * } */ final class NativeConstantInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var array */ @@ -45,9 +65,6 @@ final class NativeConstantInvocationFixer extends AbstractFixer implements Confi */ private array $caseInsensitiveConstantsToEscape = []; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -100,35 +117,25 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before GlobalNamespaceImportFixer. + * Must run after FunctionToConstantFixer. */ public function getPriority(): int { - return 10; + return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - $uniqueConfiguredExclude = array_unique($this->configuration['exclude']); // Case-sensitive constants handling @@ -138,10 +145,11 @@ public function configure(array $configuration): void $getDefinedConstants = get_defined_constants(true); unset($getDefinedConstants['user']); foreach ($getDefinedConstants as $constants) { - $constantsToEscape = array_merge($constantsToEscape, array_keys($constants)); + $constantsToEscape = [...$constantsToEscape, ...array_keys($constants)]; } } + /** @var list */ $constantsToEscape = array_diff( array_unique($constantsToEscape), $uniqueConfiguredExclude @@ -161,7 +169,10 @@ public function configure(array $configuration): void $caseInsensitiveConstantsToEscape = array_diff( array_unique($caseInsensitiveConstantsToEscape), - array_map(static function (string $function): string { return strtolower($function); }, $uniqueConfiguredExclude) + array_map( + static fn (string $function): string => strtolower($function), + $uniqueConfiguredExclude, + ), ); // Store the cache @@ -172,9 +183,6 @@ public function configure(array $configuration): void ksort($this->caseInsensitiveConstantsToEscape); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if ('all' === $this->configuration['scope']) { @@ -183,7 +191,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void return; } - $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); + $namespaces = $tokens->getNamespaceDeclarations(); // 'scope' is 'namespaced' here /** @var NamespaceAnalysis $namespace */ @@ -196,15 +204,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $constantChecker = static function (array $value): bool { foreach ($value as $constantName) { - if (!\is_string($constantName) || '' === trim($constantName) || trim($constantName) !== $constantName) { - throw new InvalidOptionsException(sprintf( + if (trim($constantName) !== $constantName) { + throw new InvalidOptionsException(\sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', get_debug_type($constantName) )); @@ -220,12 +225,12 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setDefault(true) ->getOption(), (new FixerOptionBuilder('include', 'List of additional constants to fix.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([$constantChecker]) ->setDefault([]) ->getOption(), (new FixerOptionBuilder('exclude', 'List of constants to ignore.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([$constantChecker]) ->setDefault(['null', 'false', 'true']) ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureBracesFixer.php new file mode 100644 index 0000000000..f89d959ad1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureBracesFixer.php @@ -0,0 +1,256 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class ControlStructureBracesFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The body of each control structure MUST be enclosed within braces.', + [new CodeSample("getControlTokens(); + + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($controlTokens)) { + continue; + } + + if ( + $token->isGivenKind(T_ELSE) + && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF) + ) { + continue; + } + + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $index); + $nextAfterParenthesisEndIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + $tokenAfterParenthesis = $tokens[$nextAfterParenthesisEndIndex]; + + if ($tokenAfterParenthesis->equalsAny([';', '{', ':', [T_CLOSE_TAG]])) { + continue; + } + + $statementEndIndex = null; + + if ($tokenAfterParenthesis->isGivenKind([T_IF, T_FOR, T_FOREACH, T_SWITCH, T_WHILE])) { + $tokenAfterParenthesisBlockEnd = $tokens->findBlockEnd( // go to ')' + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextMeaningfulToken($nextAfterParenthesisEndIndex) + ); + + if ($tokens[$tokens->getNextMeaningfulToken($tokenAfterParenthesisBlockEnd)]->equals(':')) { + $statementEndIndex = $alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $nextAfterParenthesisEndIndex); + + $tokenAfterStatementEndIndex = $tokens->getNextMeaningfulToken($statementEndIndex); + if ($tokens[$tokenAfterStatementEndIndex]->equals(';')) { + $statementEndIndex = $tokenAfterStatementEndIndex; + } + } + } + + if (null === $statementEndIndex) { + $statementEndIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + } + + $tokensToInsertAfterStatement = [ + new Token([T_WHITESPACE, ' ']), + new Token('}'), + ]; + + if (!$tokens[$statementEndIndex]->equalsAny([';', '}'])) { + array_unshift($tokensToInsertAfterStatement, new Token(';')); + } + + $tokens->insertSlices([$statementEndIndex + 1 => $tokensToInsertAfterStatement]); + + // insert opening brace + $tokens->insertSlices([$parenthesisEndIndex + 1 => [ + new Token([T_WHITESPACE, ' ']), + new Token('{'), + ]]); + } + } + + private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($structureTokenIndex); + $nextToken = $tokens[$nextIndex]; + + if (!$nextToken->equals('(')) { + return $structureTokenIndex; + } + + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex); + } + + private function findStatementEnd(Tokens $tokens, int $parenthesisEndIndex): int + { + $nextIndex = $tokens->getNextMeaningfulToken($parenthesisEndIndex); + if (null === $nextIndex) { + return $parenthesisEndIndex; + } + + $nextToken = $tokens[$nextIndex]; + + if ($nextToken->equals('{')) { + return $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex); + } + + if ($nextToken->isGivenKind($this->getControlTokens())) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); + + $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + + if ($nextToken->isGivenKind([T_IF, T_TRY, T_DO])) { + $openingTokenKind = $nextToken->getId(); + + while (true) { + $nextIndex = $tokens->getNextMeaningfulToken($endIndex); + if (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind($this->getControlContinuationTokensForOpeningToken($openingTokenKind))) { + $parenthesisEndIndex = $this->findParenthesisEnd($tokens, $nextIndex); + + $endIndex = $this->findStatementEnd($tokens, $parenthesisEndIndex); + + if ($tokens[$nextIndex]->isGivenKind($this->getFinalControlContinuationTokensForOpeningToken($openingTokenKind))) { + return $endIndex; + } + } else { + break; + } + } + } + + return $endIndex; + } + + $index = $parenthesisEndIndex; + + while (true) { + $token = $tokens[++$index]; + + // if there is some block in statement (eg lambda function) we need to skip it + if ($token->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($token->equals(';')) { + return $index; + } + + if ($token->isGivenKind(T_CLOSE_TAG)) { + return $tokens->getPrevNonWhitespace($index); + } + } + } + + /** + * @return list + */ + private function getControlTokens(): array + { + static $tokens = [ + T_DECLARE, + T_DO, + T_ELSE, + T_ELSEIF, + T_FINALLY, + T_FOR, + T_FOREACH, + T_IF, + T_WHILE, + T_TRY, + T_CATCH, + T_SWITCH, + ]; + + return $tokens; + } + + /** + * @return list + */ + private function getControlContinuationTokensForOpeningToken(int $openingTokenKind): array + { + if (T_IF === $openingTokenKind) { + return [ + T_ELSE, + T_ELSEIF, + ]; + } + + if (T_DO === $openingTokenKind) { + return [T_WHILE]; + } + + if (T_TRY === $openingTokenKind) { + return [ + T_CATCH, + T_FINALLY, + ]; + } + + return []; + } + + /** + * @return list + */ + private function getFinalControlContinuationTokensForOpeningToken(int $openingTokenKind): array + { + if (T_IF === $openingTokenKind) { + return [T_ELSE]; + } + + if (T_TRY === $openingTokenKind) { + return [T_FINALLY]; + } + + return []; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php index dd3088b0c0..95639763b9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -26,8 +27,21 @@ use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * position?: 'next_line'|'same_line' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * position: 'next_line'|'same_line' + * } + */ final class ControlStructureContinuationPositionFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -46,9 +60,6 @@ final class ControlStructureContinuationPositionFixer extends AbstractFixer impl T_WHILE, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -84,6 +95,8 @@ public function isCandidate(Tokens $tokens): bool } /** + * {@inheritdoc} + * * Must run after ControlStructureBracesFixer. */ public function getPriority(): int @@ -94,7 +107,7 @@ public function getPriority(): int protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('position', 'the position of the keyword that continues the control structure.')) + (new FixerOptionBuilder('position', 'The position of the keyword that continues the control structure.')) ->setAllowedValues([self::NEXT_LINE, self::SAME_LINE]) ->setDefault(self::SAME_LINE) ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php index 10c234373c..19c810ec60 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/ElseifFixer.php @@ -28,9 +28,6 @@ */ final class ElseifFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -42,7 +39,6 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BracesFixer. * Must run after NoAlternativeSyntaxFixer. */ public function getPriority(): int @@ -50,9 +46,6 @@ public function getPriority(): int return 40; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_IF, T_ELSE]); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php index f0898a6fd4..03f6a9058a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopBodyFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,17 +27,27 @@ use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * style?: 'braces'|'semicolon' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * style: 'braces'|'semicolon' + * } + */ final class EmptyLoopBodyFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + private const STYLE_BRACES = 'braces'; private const STYLE_SEMICOLON = 'semicolon'; private const TOKEN_LOOP_KINDS = [T_FOR, T_FOREACH, T_WHILE]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -64,17 +75,11 @@ public function getPriority(): int return 39; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (self::STYLE_BRACES === $this->configuration['style']) { @@ -101,7 +106,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void return; } - $braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex); + $braceCloseIndex = $tokens->getNextNonWhitespace($braceOpenIndex); if (!$tokens[$braceCloseIndex]->equals('}')) { return; @@ -121,9 +126,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php index 7f82fc4aae..2feac4ea6f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/EmptyLoopConditionFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -25,17 +26,27 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * style?: 'for'|'while' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * style: 'for'|'while' + * } + */ final class EmptyLoopConditionFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + private const STYLE_FOR = 'for'; private const STYLE_WHILE = 'while'; private const TOKEN_LOOP_KINDS = [T_FOR, T_WHILE]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -57,17 +68,11 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (self::STYLE_WHILE === $this->configuration['style']) { @@ -119,9 +124,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -143,7 +145,7 @@ private static function clearNotCommentsInRange(Tokens $tokens, int $indexStart, } /** - * @param Token[] $replacement + * @param list $replacement */ private static function cloneAndInsert(Tokens $tokens, int $index, array $replacement): void { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php index 788c6fd746..a0bd5accb9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/IncludeFixer.php @@ -29,13 +29,10 @@ */ final class IncludeFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Include/Require and file path should be divided with a single space. File path should not be placed under brackets.', + 'Include/Require and file path should be divided with a single space. File path should not be placed within parentheses.', [ new CodeSample( 'isAnyTokenKindsFound([T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $this->clearIncludies($tokens, $this->findIncludies($tokens)); } + /** + * @param array $includies + */ private function clearIncludies(Tokens $tokens, array $includies): void { $blocksAnalyzer = new BlocksAnalyzer(); foreach ($includies as $includy) { - if ($includy['end'] && !$tokens[$includy['end']]->isGivenKind(T_CLOSE_TAG)) { + if (!$tokens[$includy['end']]->isGivenKind(T_CLOSE_TAG)) { $afterEndIndex = $tokens->getNextNonWhitespace($includy['end']); if (null === $afterEndIndex || !$tokens[$afterEndIndex]->isComment()) { @@ -105,6 +99,9 @@ private function clearIncludies(Tokens $tokens, array $includies): void } } + /** + * @return array + */ private function findIncludies(Tokens $tokens): array { static $includyTokenKinds = [T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php index b863638af8..c0622fd259 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoAlternativeSyntaxFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,12 +28,21 @@ /** * @author Eddilbert Macharia + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * fix_non_monolithic_code?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * fix_non_monolithic_code: bool + * } */ final class NoAlternativeSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -49,9 +59,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->hasAlternativeSyntax() && (true === $this->configuration['fix_non_monolithic_code'] || $tokens->isMonolithicPhp()); @@ -60,16 +67,13 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} * - * Must run before BracesFixer, ElseifFixer, NoSuperfluousElseifFixer, NoUselessElseFixer, SwitchContinueToBreakFixer. + * Must run before BracesFixer, ElseifFixer, NoSuperfluousElseifFixer, NoUnneededControlParenthesesFixer, NoUselessElseFixer, SwitchContinueToBreakFixer. */ public function getPriority(): int { return 42; } - /** - * {@inheritDoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -80,9 +84,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { @@ -100,8 +101,7 @@ private function findParenthesisEnd(Tokens $tokens, int $structureTokenIndex): i return $nextToken->equals('(') ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex) - : $structureTokenIndex // return if next token is not opening parenthesis - ; + : $structureTokenIndex; // return if next token is not opening parenthesis } /** diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php index dfcb208804..821df86cf5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoBreakCommentFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -32,12 +33,21 @@ /** * Fixer for rule defined in PSR2 ¶5.2. + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * comment_text?: string + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * comment_text: string + * } */ final class NoBreakCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -73,9 +83,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_SWITCH); @@ -91,9 +98,6 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -108,17 +112,12 @@ static function (string $value): bool { return true; }, ]) - ->setNormalizer(static function (Options $options, string $value): string { - return rtrim($value); - }) + ->setNormalizer(static fn (Options $options, string $value): string => rtrim($value)) ->setDefault('no break') ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; $index >= 0; --$index) { @@ -141,7 +140,7 @@ private function fixCase(Tokens $tokens, int $casePosition): void $commentPosition = null; for ($i = $casePosition + 1, $max = \count($tokens); $i < $max; ++$i) { - if ($tokens[$i]->isGivenKind([T_SWITCH, T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_DO, T_FUNCTION, T_CLASS])) { + if ($tokens[$i]->isGivenKind([...self::getParenthesisedStructureKinds(), T_ELSE, T_DO, T_CLASS])) { $empty = false; $i = $this->getStructureEnd($tokens, $i); @@ -217,7 +216,7 @@ private function isNoBreakComment(Token $token): bool $text = preg_quote($this->configuration['comment_text'], '~'); - return 1 === Preg::match("~^((//|#)\\s*{$text}\\s*)|(/\\*\\*?\\s*{$text}(\\s+.*)*\\*/)$~i", $token->getContent()); + return Preg::match("~^((//|#)\\s*{$text}\\s*)|(/\\*\\*?\\s*{$text}(\\s+.*)*\\*/)$~i", $token->getContent()); } private function insertCommentAt(Tokens $tokens, int $casePosition): void @@ -312,7 +311,7 @@ private function getStructureEnd(Tokens $tokens, int $position): int { $initialToken = $tokens[$position]; - if ($initialToken->isGivenKind([T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION])) { + if ($initialToken->isGivenKind(self::getParenthesisedStructureKinds())) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']) @@ -347,4 +346,21 @@ private function getStructureEnd(Tokens $tokens, int $position): int return $position; } + + /** + * @return list + */ + private static function getParenthesisedStructureKinds(): array + { + static $structureKinds = null; + + if (null === $structureKinds) { + $structureKinds = [T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSEIF, T_SWITCH, T_FUNCTION]; + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + $structureKinds[] = T_MATCH; + } + } + + return $structureKinds; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php index a8b6f92495..93dbb08376 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoSuperfluousElseifFixer.php @@ -24,17 +24,11 @@ final class NoSuperfluousElseifFixer extends AbstractNoUselessElseFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_ELSE, T_ELSEIF]); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -56,9 +50,6 @@ public function getPriority(): int return parent::getPriority(); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -72,8 +63,7 @@ private function isElseif(Tokens $tokens, int $index): bool { return $tokens[$index]->isGivenKind(T_ELSEIF) - || ($tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF)) - ; + || ($tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF)); } private function convertElseifToIf(Tokens $tokens, int $index): void diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php index 0252750278..4b71005890 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoTrailingCommaInListCallFixer.php @@ -14,20 +14,20 @@ namespace PhpCsFixer\Fixer\ControlStructure; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Tokens; /** + * @deprecated + * * @author Dariusz Rumiński */ -final class NoTrailingCommaInListCallFixer extends AbstractFixer +final class NoTrailingCommaInListCallFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -36,42 +36,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isTokenKindFound(T_LIST); + return array_keys($this->proxyFixers); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function createProxyFixers(): array { - for ($index = $tokens->count() - 1; $index >= 0; --$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind(T_LIST)) { - continue; - } - - $openIndex = $tokens->getNextMeaningfulToken($index); - $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); - $markIndex = null; - $prevIndex = $tokens->getPrevNonWhitespace($closeIndex); - - while ($tokens[$prevIndex]->equals(',')) { - $markIndex = $prevIndex; - $prevIndex = $tokens->getPrevNonWhitespace($prevIndex); - } + $fixer = new NoTrailingCommaInSinglelineFixer(); + $fixer->configure(['elements' => ['array_destructuring']]); - if (null !== $markIndex) { - $tokens->clearRange( - $tokens->getPrevNonWhitespace($markIndex) + 1, - $closeIndex - 1 - ); - } - } + return [$fixer]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededBracesFixer.php new file mode 100644 index 0000000000..ce46c859ed --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededBracesFixer.php @@ -0,0 +1,182 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\ControlStructure; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * namespaces?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * namespaces: bool + * } + */ +final class NoUnneededBracesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Removes unneeded braces that are superfluous and aren\'t part of a control structure\'s body.', + [ + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before NoUselessElseFixer, NoUselessReturnFixer, ReturnAssignmentFixer, SimplifiedIfReturnFixer. + */ + public function getPriority(): int + { + return 40; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('}'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($this->findBraceOpen($tokens) as $index) { + if ($this->isOverComplete($tokens, $index)) { + $this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); + } + } + + if (true === $this->configuration['namespaces']) { + $this->clearIfIsOverCompleteNamespaceBlock($tokens); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('namespaces', 'Remove unneeded braces from bracketed namespaces.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param int $openIndex index of `{` token + * @param int $closeIndex index of `}` token + */ + private function clearOverCompleteBraces(Tokens $tokens, int $openIndex, int $closeIndex): void + { + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); + } + + /** + * @return iterable + */ + private function findBraceOpen(Tokens $tokens): iterable + { + for ($i = \count($tokens) - 1; $i > 0; --$i) { + if ($tokens[$i]->equals('{')) { + yield $i; + } + } + } + + /** + * @param int $index index of `{` token + */ + private function isOverComplete(Tokens $tokens, int $index): bool + { + static $include = ['{', '}', [T_OPEN_TAG], ':', ';']; + + return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($include); + } + + private function clearIfIsOverCompleteNamespaceBlock(Tokens $tokens): void + { + if (1 !== $tokens->countTokenKind(T_NAMESPACE)) { + return; // fast check, we never fix if multiple namespaces are defined + } + + $index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]); + + $namespaceIsNamed = false; + + $index = $tokens->getNextMeaningfulToken($index); + while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + $index = $tokens->getNextMeaningfulToken($index); + $namespaceIsNamed = true; + } + + if (!$namespaceIsNamed) { + return; + } + + if (!$tokens[$index]->equals('{')) { + return; // `;` + } + + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); + + if (null !== $afterCloseIndex && (!$tokens[$afterCloseIndex]->isGivenKind(T_CLOSE_TAG) || null !== $tokens->getNextMeaningfulToken($afterCloseIndex))) { + return; + } + + // clear up + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); + $tokens[$index] = new Token(';'); + + if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index - 1); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php index b705105d6b..6b2c3cb1a8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -26,44 +27,130 @@ use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * @author Sullivan Senechal * @author Dariusz Rumiński * @author Gregor Harlan + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * statements?: list<'break'|'clone'|'continue'|'echo_print'|'negative_instanceof'|'others'|'return'|'switch_case'|'yield'|'yield_from'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * statements: list<'break'|'clone'|'continue'|'echo_print'|'negative_instanceof'|'others'|'return'|'switch_case'|'yield'|'yield_from'> + * } */ final class NoUnneededControlParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface { - private static array $loops = [ - 'break' => ['lookupTokens' => T_BREAK, 'neededSuccessors' => [';']], - 'clone' => ['lookupTokens' => T_CLONE, 'neededSuccessors' => [';', ':', ',', ')'], 'forbiddenContents' => ['?', ':', [T_COALESCE, '??']]], - 'continue' => ['lookupTokens' => T_CONTINUE, 'neededSuccessors' => [';']], - 'echo_print' => ['lookupTokens' => [T_ECHO, T_PRINT], 'neededSuccessors' => [';', [T_CLOSE_TAG]]], - 'return' => ['lookupTokens' => T_RETURN, 'neededSuccessors' => [';', [T_CLOSE_TAG]]], - 'switch_case' => ['lookupTokens' => T_CASE, 'neededSuccessors' => [';', ':']], - 'yield' => ['lookupTokens' => T_YIELD, 'neededSuccessors' => [';', ')']], - 'yield_from' => ['lookupTokens' => T_YIELD_FROM, 'neededSuccessors' => [';', ')']], - ]; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; /** - * {@inheritdoc} + * @var list */ - public function isCandidate(Tokens $tokens): bool - { - $types = []; + private const BLOCK_TYPES = [ + Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, + Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, + Tokens::BLOCK_TYPE_CURLY_BRACE, + Tokens::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE, + Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, + Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE, + Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + ]; - foreach (self::$loops as $loop) { - $types[] = (array) $loop['lookupTokens']; - } + private const BEFORE_TYPES = [ + ';', + '{', + [T_OPEN_TAG], + [T_OPEN_TAG_WITH_ECHO], + [T_ECHO], + [T_PRINT], + [T_RETURN], + [T_THROW], + [T_YIELD], + [T_YIELD_FROM], + [T_BREAK], + [T_CONTINUE], + // won't be fixed, but true in concept, helpful for fast check + [T_REQUIRE], + [T_REQUIRE_ONCE], + [T_INCLUDE], + [T_INCLUDE_ONCE], + ]; - $types = array_merge(...$types); + private const CONFIG_OPTIONS = [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'negative_instanceof', + 'others', + 'return', + 'switch_case', + 'yield', + 'yield_from', + ]; - return $tokens->isAnyTokenKindsFound($types); - } + private const TOKEN_TYPE_CONFIG_MAP = [ + T_BREAK => 'break', + T_CASE => 'switch_case', + T_CONTINUE => 'continue', + T_ECHO => 'echo_print', + T_PRINT => 'echo_print', + T_RETURN => 'return', + T_YIELD => 'yield', + T_YIELD_FROM => 'yield_from', + ]; + + // handled by the `include` rule + private const TOKEN_TYPE_NO_CONFIG = [ + T_REQUIRE, + T_REQUIRE_ONCE, + T_INCLUDE, + T_INCLUDE_ONCE, + ]; /** - * {@inheritdoc} + * @var list */ + private array $noopTypes; + + private TokensAnalyzer $tokensAnalyzer; + + public function __construct() + { + parent::__construct(); + + $this->noopTypes = [ + '$', + [T_CONSTANT_ENCAPSED_STRING], + [T_DNUMBER], + [T_DOUBLE_COLON], + [T_LNUMBER], + [T_NS_SEPARATOR], + [T_STRING], + [T_VARIABLE], + [T_STATIC], + // magic constants + [T_CLASS_C], + [T_DIR], + [T_FILE], + [T_FUNC_C], + [T_LINE], + [T_METHOD_C], + [T_NS_C], + [T_TRAIT_C], + ]; + + foreach (Token::getObjectOperatorKinds() as $kind) { + $this->noopTypes[] = [$kind]; + } + } + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -84,13 +171,10 @@ public function getDefinition(): FixerDefinitionInterface new CodeSample( ' ['break', 'continue']] ), @@ -101,86 +185,576 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before NoTrailingWhitespaceFixer. + * Must run before ConcatSpaceFixer, NoTrailingWhitespaceFixer. + * Must run after ModernizeTypesCastingFixer, NoAlternativeSyntaxFixer. */ public function getPriority(): int { return 30; } - /** - * {@inheritdoc} - */ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - // Checks if specific statements are set and uses them in this case. - $loops = array_intersect_key(self::$loops, array_flip($this->configuration['statements'])); + $this->tokensAnalyzer = new TokensAnalyzer($tokens); - foreach ($tokens as $index => $token) { - if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + foreach ($tokens as $openIndex => $token) { + if ($token->equals('(')) { + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + } elseif ($token->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_OPEN)) { + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, $openIndex); + } else { continue; } - $blockStartIndex = $index; - $index = $tokens->getPrevMeaningfulToken($index); - $prevToken = $tokens[$index]; + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($openIndex); + $afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); - foreach ($loops as $loop) { - if (!$prevToken->isGivenKind($loop['lookupTokens'])) { - continue; + // do a cheap check for negative case: `X()` + + if ($tokens->getNextMeaningfulToken($openIndex) === $closeIndex) { + if ($this->isExitStatement($tokens, $beforeOpenIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'others'); } - $blockEndIndex = $tokens->findBlockEnd( - $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION, - $blockStartIndex - ); + continue; + } - $blockEndNextIndex = $tokens->getNextMeaningfulToken($blockEndIndex); + // do a cheap check for negative case: `foo(1,2)` - if (!$tokens[$blockEndNextIndex]->equalsAny($loop['neededSuccessors'])) { - continue; - } + if ($this->isKnownNegativePre($tokens[$beforeOpenIndex])) { + continue; + } + + // check for the simple useless wrapped cases + + if ($this->isUselessWrapped($tokens, $beforeOpenIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, $this->getConfigType($tokens, $beforeOpenIndex)); - if (\array_key_exists('forbiddenContents', $loop)) { - $forbiddenTokenIndex = $tokens->getNextTokenOfKind($blockStartIndex, $loop['forbiddenContents']); + continue; + } - // A forbidden token is found and is inside the parenthesis. - if (null !== $forbiddenTokenIndex && $forbiddenTokenIndex < $blockEndIndex) { - continue; - } + // handle `clone` statements + + if ($this->isCloneStatement($tokens, $beforeOpenIndex)) { + if ($this->isWrappedCloneArgument($tokens, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, 'clone'); } - if ($tokens[$blockStartIndex - 1]->isWhitespace() || $tokens[$blockStartIndex - 1]->isComment()) { - $tokens->clearTokenAndMergeSurroundingWhitespace($blockStartIndex); - } else { - // Adds a space to prevent broken code like `return2`. - $tokens[$blockStartIndex] = new Token([T_WHITESPACE, ' ']); + continue; + } + + // handle `instance of` statements + + $instanceOfIndex = $this->getIndexOfInstanceOfStatement($tokens, $openIndex, $closeIndex); + + if (null !== $instanceOfIndex) { + if ($this->isWrappedInstanceOf($tokens, $instanceOfIndex, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair( + $tokens, + $beforeOpenIndex, + $afterCloseIndex, + $openIndex, + $closeIndex, + $tokens[$beforeOpenIndex]->equals('!') ? 'negative_instanceof' : 'others' + ); } - $tokens->clearTokenAndMergeSurroundingWhitespace($blockEndIndex); + continue; + } + + // last checks deal with operators, do not swap around + + if ($this->isWrappedPartOfOperation($tokens, $beforeOpenIndex, $openIndex, $closeIndex, $afterCloseIndex)) { + $this->removeUselessParenthesisPair($tokens, $beforeOpenIndex, $afterCloseIndex, $openIndex, $closeIndex, $this->getConfigType($tokens, $beforeOpenIndex)); } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { + $defaults = array_filter( + self::CONFIG_OPTIONS, + static fn (string $option): bool => 'negative_instanceof' !== $option && 'others' !== $option && 'yield_from' !== $option + ); + return new FixerConfigurationResolver([ (new FixerOptionBuilder('statements', 'List of control statements to fix.')) - ->setAllowedTypes(['array']) - ->setAllowedValues([new AllowedValueSubset(array_keys(self::$loops))]) - ->setDefault([ - 'break', - 'clone', - 'continue', - 'echo_print', - 'return', - 'switch_case', - 'yield', - ]) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset(self::CONFIG_OPTIONS)]) + ->setDefault(array_values($defaults)) ->getOption(), ]); } + + private function isUselessWrapped(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + return + $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedLanguageConstructArgument($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex); + } + + private function isExitStatement(Tokens $tokens, int $beforeOpenIndex): bool + { + return $tokens[$beforeOpenIndex]->isGivenKind(T_EXIT); + } + + private function isCloneStatement(Tokens $tokens, int $beforeOpenIndex): bool + { + return $tokens[$beforeOpenIndex]->isGivenKind(T_CLONE); + } + + private function isWrappedCloneArgument(Tokens $tokens, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool + { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if ( + !( + $tokens[$beforeOpenIndex]->equals('?') // For BC reasons + || $this->isSimpleAssignment($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex) + ) + ) { + return false; + } + + $newCandidateIndex = $tokens->getNextMeaningfulToken($openIndex); + + if ($tokens[$newCandidateIndex]->isGivenKind(T_NEW)) { + $openIndex = $newCandidateIndex; // `clone (new X)`, `clone (new X())`, clone (new X(Y))` + } + + return !$this->containsOperation($tokens, $openIndex, $closeIndex); + } + + private function getIndexOfInstanceOfStatement(Tokens $tokens, int $openIndex, int $closeIndex): ?int + { + $instanceOfIndex = $tokens->findGivenKind(T_INSTANCEOF, $openIndex, $closeIndex); + + return 1 === \count($instanceOfIndex) ? array_key_first($instanceOfIndex) : null; + } + + private function isWrappedInstanceOf(Tokens $tokens, int $instanceOfIndex, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool + { + if ( + $this->containsOperation($tokens, $openIndex, $instanceOfIndex) + || $this->containsOperation($tokens, $instanceOfIndex, $closeIndex) + ) { + return false; + } + + if ($tokens[$beforeOpenIndex]->equals('!')) { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } + + return + $this->isSimpleAssignment($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isSingleStatement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedFnBody($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedForElement($tokens, $beforeOpenIndex, $afterCloseIndex) + || $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex); + } + + private function isWrappedPartOfOperation(Tokens $tokens, int $beforeOpenIndex, int $openIndex, int $closeIndex, int $afterCloseIndex): bool + { + if ($this->containsOperation($tokens, $openIndex, $closeIndex)) { + return false; + } + + $boundariesMoved = false; + + if ($this->isPreUnaryOperation($tokens, $beforeOpenIndex)) { + $beforeOpenIndex = $this->getBeforePreUnaryOperation($tokens, $beforeOpenIndex); + $boundariesMoved = true; + } + + if ($this->isAccess($tokens, $afterCloseIndex)) { + $afterCloseIndex = $this->getAfterAccess($tokens, $afterCloseIndex); + $boundariesMoved = true; + + if ($this->tokensAnalyzer->isUnarySuccessorOperator($afterCloseIndex)) { // post unary operation are only valid here + $afterCloseIndex = $tokens->getNextMeaningfulToken($afterCloseIndex); + } + } + + if ($boundariesMoved) { + if ($this->isKnownNegativePre($tokens[$beforeOpenIndex])) { + return false; + } + + if ($this->isUselessWrapped($tokens, $beforeOpenIndex, $afterCloseIndex)) { + return true; + } + } + + // check if part of some operation sequence + + $beforeIsBinaryOperation = $this->tokensAnalyzer->isBinaryOperator($beforeOpenIndex); + $afterIsBinaryOperation = $this->tokensAnalyzer->isBinaryOperator($afterCloseIndex); + + if ($beforeIsBinaryOperation && $afterIsBinaryOperation) { + return true; // `+ (x) +` + } + + $beforeToken = $tokens[$beforeOpenIndex]; + $afterToken = $tokens[$afterCloseIndex]; + + $beforeIsBlockOpenOrComma = $beforeToken->equals(',') || null !== $this->getBlock($tokens, $beforeOpenIndex, true); + $afterIsBlockEndOrComma = $afterToken->equals(',') || null !== $this->getBlock($tokens, $afterCloseIndex, false); + + if (($beforeIsBlockOpenOrComma && $afterIsBinaryOperation) || ($beforeIsBinaryOperation && $afterIsBlockEndOrComma)) { + // $beforeIsBlockOpenOrComma && $afterIsBlockEndOrComma is covered by `isWrappedSequenceElement` + // `[ (x) +` or `+ (X) ]` or `, (X) +` or `+ (X) ,` + + return true; + } + + if ($tokens[$beforeOpenIndex]->equals('}')) { + $beforeIsStatementOpen = !$this->closeCurlyBelongsToDynamicElement($tokens, $beforeOpenIndex); + } else { + $beforeIsStatementOpen = $beforeToken->equalsAny(self::BEFORE_TYPES) || $beforeToken->isGivenKind(T_CASE); + } + + $afterIsStatementEnd = $afterToken->equalsAny([';', [T_CLOSE_TAG]]); + + return + ($beforeIsStatementOpen && $afterIsBinaryOperation) // `isGivenKind([T_PRINT, T_YIELD, T_YIELD_FROM, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE])) { + return false; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + return $this->isWrappedSequenceElement($tokens, $beforeOpenIndex, $afterCloseIndex); + } + + // any of `isGivenKind(T_CASE)) { + return $tokens[$afterCloseIndex]->equalsAny([':', ';']); // `switch case` + } + + if (!$tokens[$afterCloseIndex]->equalsAny([';', [T_CLOSE_TAG]])) { + return false; + } + + if ($tokens[$beforeOpenIndex]->equals('}')) { + return !$this->closeCurlyBelongsToDynamicElement($tokens, $beforeOpenIndex); + } + + return $tokens[$beforeOpenIndex]->equalsAny(self::BEFORE_TYPES); + } + + private function isSimpleAssignment(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + return $tokens[$beforeOpenIndex]->equals('=') && $tokens[$afterCloseIndex]->equalsAny([';', [T_CLOSE_TAG]]); // `= (X) ;` + } + + private function isWrappedSequenceElement(Tokens $tokens, int $startIndex, int $endIndex): bool + { + $startIsComma = $tokens[$startIndex]->equals(','); + $endIsComma = $tokens[$endIndex]->equals(','); + + if ($startIsComma && $endIsComma) { + return true; // `,(X),` + } + + $blockTypeStart = $this->getBlock($tokens, $startIndex, true); + $blockTypeEnd = $this->getBlock($tokens, $endIndex, false); + + return + ($startIsComma && null !== $blockTypeEnd) // `,(X)]` + || ($endIsComma && null !== $blockTypeStart) // `[(X),` + || (null !== $blockTypeEnd && null !== $blockTypeStart); // any type of `{(X)}`, `[(X)]` and `((X))` + } + + // any of `for( (X); ;(X)) ;` note that the middle element is covered as 'single statement' as it is `; (X) ;` + private function isWrappedForElement(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + $forCandidateIndex = null; + + if ($tokens[$beforeOpenIndex]->equals('(') && $tokens[$afterCloseIndex]->equals(';')) { + $forCandidateIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } elseif ($tokens[$afterCloseIndex]->equals(')') && $tokens[$beforeOpenIndex]->equals(';')) { + $forCandidateIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $afterCloseIndex); + $forCandidateIndex = $tokens->getPrevMeaningfulToken($forCandidateIndex); + } + + return null !== $forCandidateIndex && $tokens[$forCandidateIndex]->isGivenKind(T_FOR); + } + + // `fn() => (X);` + private function isWrappedFnBody(Tokens $tokens, int $beforeOpenIndex, int $afterCloseIndex): bool + { + if (!$tokens[$beforeOpenIndex]->isGivenKind(T_DOUBLE_ARROW)) { + return false; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if ($tokens[$beforeOpenIndex]->isGivenKind(T_STRING)) { + while (true) { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if (!$tokens[$beforeOpenIndex]->isGivenKind([T_STRING, CT::T_TYPE_INTERSECTION, CT::T_TYPE_ALTERNATION])) { + break; + } + } + + if (!$tokens[$beforeOpenIndex]->isGivenKind(CT::T_TYPE_COLON)) { + return false; + } + + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } + + if (!$tokens[$beforeOpenIndex]->equals(')')) { + return false; + } + + $beforeOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $beforeOpenIndex); + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + + if ($tokens[$beforeOpenIndex]->isGivenKind(CT::T_RETURN_REF)) { + $beforeOpenIndex = $tokens->getPrevMeaningfulToken($beforeOpenIndex); + } + + if (!$tokens[$beforeOpenIndex]->isGivenKind(T_FN)) { + return false; + } + + return $tokens[$afterCloseIndex]->equalsAny([';', ',', [T_CLOSE_TAG]]); + } + + private function isPreUnaryOperation(Tokens $tokens, int $index): bool + { + return $this->tokensAnalyzer->isUnaryPredecessorOperator($index) || $tokens[$index]->isCast(); + } + + private function getBeforePreUnaryOperation(Tokens $tokens, int $index): int + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while ($this->isPreUnaryOperation($tokens, $index)); + + return $index; + } + + // array access `(X)[` or `(X){` or object access `(X)->` or `(X)?->` + private function isAccess(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + return $token->isObjectOperator() || $token->equals('[') || $token->isGivenKind([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); + } + + private function getAfterAccess(Tokens $tokens, int $index): int + { + while (true) { + $block = $this->getBlock($tokens, $index, true); + + if (null !== $block) { + $index = $tokens->findBlockEnd($block['type'], $index); + $index = $tokens->getNextMeaningfulToken($index); + + continue; + } + + if ( + $tokens[$index]->isObjectOperator() + || $tokens[$index]->equalsAny(['$', [T_PAAMAYIM_NEKUDOTAYIM], [T_STRING], [T_VARIABLE]]) + ) { + $index = $tokens->getNextMeaningfulToken($index); + + continue; + } + + break; + } + + return $index; + } + + /** + * @return null|array{type: Tokens::BLOCK_TYPE_*, isStart: bool} + */ + private function getBlock(Tokens $tokens, int $index, bool $isStart): ?array + { + $block = Tokens::detectBlockType($tokens[$index]); + + return null !== $block && $isStart === $block['isStart'] && \in_array($block['type'], self::BLOCK_TYPES, true) ? $block : null; + } + + // cheap check on a tokens type before `(` of which we know the `(` will never be superfluous + private function isKnownNegativePre(Token $token): bool + { + static $knownNegativeTypes; + + if (null === $knownNegativeTypes) { + $knownNegativeTypes = [ + [CT::T_CLASS_CONSTANT], + [CT::T_DYNAMIC_VAR_BRACE_CLOSE], + [CT::T_RETURN_REF], + [CT::T_USE_LAMBDA], + [T_ARRAY], + [T_CATCH], + [T_CLASS], + [T_DECLARE], + [T_ELSEIF], + [T_EMPTY], + [T_EXIT], + [T_EVAL], + [T_FN], + [T_FOREACH], + [T_FOR], + [T_FUNCTION], + [T_HALT_COMPILER], + [T_IF], + [T_ISSET], + [T_LIST], + [T_STRING], + [T_SWITCH], + [T_STATIC], + [T_UNSET], + [T_VARIABLE], + [T_WHILE], + // handled by the `include` rule + [T_REQUIRE], + [T_REQUIRE_ONCE], + [T_INCLUDE], + [T_INCLUDE_ONCE], + ]; + + if (\defined('T_MATCH')) { // @TODO: drop condition and add directly in `$knownNegativeTypes` above when PHP 8.0+ is required + $knownNegativeTypes[] = T_MATCH; + } + } + + return $token->equalsAny($knownNegativeTypes); + } + + private function containsOperation(Tokens $tokens, int $startIndex, int $endIndex): bool + { + while (true) { + $startIndex = $tokens->getNextMeaningfulToken($startIndex); + + if ($startIndex === $endIndex) { + break; + } + + $block = Tokens::detectBlockType($tokens[$startIndex]); + + if (null !== $block && $block['isStart']) { + $startIndex = $tokens->findBlockEnd($block['type'], $startIndex); + + continue; + } + + if (!$tokens[$startIndex]->equalsAny($this->noopTypes)) { + return true; + } + } + + return false; + } + + private function getConfigType(Tokens $tokens, int $beforeOpenIndex): ?string + { + if ($tokens[$beforeOpenIndex]->isGivenKind(self::TOKEN_TYPE_NO_CONFIG)) { + return null; + } + + foreach (self::TOKEN_TYPE_CONFIG_MAP as $type => $configItem) { + if ($tokens[$beforeOpenIndex]->isGivenKind($type)) { + return $configItem; + } + } + + return 'others'; + } + + private function removeUselessParenthesisPair( + Tokens $tokens, + int $beforeOpenIndex, + int $afterCloseIndex, + int $openIndex, + int $closeIndex, + ?string $configType + ): void { + $statements = $this->configuration['statements']; + + if (null === $configType || !\in_array($configType, $statements, true)) { + return; + } + + $needsSpaceAfter = + !$this->isAccess($tokens, $afterCloseIndex) + && !$tokens[$afterCloseIndex]->equalsAny([';', ',', [T_CLOSE_TAG]]) + && null === $this->getBlock($tokens, $afterCloseIndex, false) + && !($tokens[$afterCloseIndex]->equalsAny([':', ';']) && $tokens[$beforeOpenIndex]->isGivenKind(T_CASE)); + + $needsSpaceBefore = + !$this->isPreUnaryOperation($tokens, $beforeOpenIndex) + && !$tokens[$beforeOpenIndex]->equalsAny(['}', [T_EXIT], [T_OPEN_TAG]]) + && null === $this->getBlock($tokens, $beforeOpenIndex, true); + + $this->removeBrace($tokens, $closeIndex, $needsSpaceAfter); + $this->removeBrace($tokens, $openIndex, $needsSpaceBefore); + } + + private function removeBrace(Tokens $tokens, int $index, bool $needsSpace): void + { + if ($needsSpace) { + foreach ([-1, 1] as $direction) { + $siblingIndex = $tokens->getNonEmptySibling($index, $direction); + + if ($tokens[$siblingIndex]->isWhitespace() || $tokens[$siblingIndex]->isComment()) { + $needsSpace = false; + + break; + } + } + } + + if ($needsSpace) { + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } else { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + private function closeCurlyBelongsToDynamicElement(Tokens $tokens, int $beforeOpenIndex): bool + { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $beforeOpenIndex); + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + return true; + } + + if ($tokens[$index]->equals(':')) { + $index = $tokens->getPrevTokenOfKind($index, [[T_CASE], '?']); + + return !$tokens[$index]->isGivenKind(T_CASE); + } + + return false; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php index 8f260d8cab..40610d2add 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php @@ -14,48 +14,51 @@ namespace PhpCsFixer\Fixer\ControlStructure; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; -use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; -final class NoUnneededCurlyBracesFixer extends AbstractFixer implements ConfigurableFixerInterface +/** + * @deprecated + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * namespaces?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * namespaces: bool + * } + */ +final class NoUnneededCurlyBracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private NoUnneededBracesFixer $noUnneededBracesFixer; + + public function __construct() + { + $this->noUnneededBracesFixer = new NoUnneededBracesFixer(); + + parent::__construct(); + } + public function getDefinition(): FixerDefinitionInterface { + $fixerDefinition = $this->noUnneededBracesFixer->getDefinition(); + return new FixerDefinition( 'Removes unneeded curly braces that are superfluous and aren\'t part of a control structure\'s body.', - [ - new CodeSample( - ' true] - ), - ] + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription() ); } @@ -66,36 +69,24 @@ function Bar(){} */ public function getPriority(): int { - return 40; + return $this->noUnneededBracesFixer->getPriority(); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isTokenKindFound('}'); + return [ + $this->noUnneededBracesFixer->getName(), + ]; } /** - * {@inheritdoc} + * @param _AutogeneratedInputConfiguration $configuration */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function configurePreNormalisation(array $configuration): void { - foreach ($this->findCurlyBraceOpen($tokens) as $index) { - if ($this->isOverComplete($tokens, $index)) { - $this->clearOverCompleteBraces($tokens, $index, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)); - } - } - - if (true === $this->configuration['namespaces']) { - $this->clearIfIsOverCompleteNamespaceBlock($tokens); - } + $this->noUnneededBracesFixer->configure($configuration); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -106,64 +97,10 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * @param int $openIndex index of `{` token - * @param int $closeIndex index of `}` token - */ - private function clearOverCompleteBraces(Tokens $tokens, int $openIndex, int $closeIndex): void - { - $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); - $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); - } - - private function findCurlyBraceOpen(Tokens $tokens): iterable - { - for ($i = \count($tokens) - 1; $i > 0; --$i) { - if ($tokens[$i]->equals('{')) { - yield $i; - } - } - } - - /** - * @param int $index index of `{` token - */ - private function isOverComplete(Tokens $tokens, int $index): bool + protected function createProxyFixers(): array { - static $include = ['{', '}', [T_OPEN_TAG], ':', ';']; - - return $tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny($include); - } - - private function clearIfIsOverCompleteNamespaceBlock(Tokens $tokens): void - { - if (1 !== $tokens->countTokenKind(T_NAMESPACE)) { - return; // fast check, we never fix if multiple namespaces are defined - } - - $index = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]); - - do { - $index = $tokens->getNextMeaningfulToken($index); - } while ($tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])); - - if (!$tokens[$index]->equals('{')) { - return; // `;` - } - - $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); - $afterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); - - if (null !== $afterCloseIndex && (!$tokens[$afterCloseIndex]->isGivenKind(T_CLOSE_TAG) || null !== $tokens->getNextMeaningfulToken($afterCloseIndex))) { - return; - } - - // clear up - $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); - $tokens[$index] = new Token(';'); - - if ($tokens[$index - 1]->isWhitespace(" \t") && !$tokens[$index - 2]->isComment()) { - $tokens->clearTokenAndMergeSurroundingWhitespace($index - 1); - } + return [ + $this->noUnneededBracesFixer, + ]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php index 58a9a3cb40..9b2d1cce1e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/NoUselessElseFixer.php @@ -22,17 +22,11 @@ final class NoUselessElseFixer extends AbstractNoUselessElseFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_ELSE); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -46,17 +40,14 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BracesFixer, CombineConsecutiveUnsetsFixer, NoBreakCommentFixer, NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoUselessReturnFixer, NoWhitespaceInBlankLineFixer, SimplifiedIfReturnFixer. - * Must run after NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUnneededCurlyBracesFixer. + * Must run before BlankLineBeforeStatementFixer, BracesFixer, CombineConsecutiveUnsetsFixer, NoBreakCommentFixer, NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoUselessReturnFixer, NoWhitespaceInBlankLineFixer, SimplifiedIfReturnFixer, StatementIndentationFixer. + * Must run after NoAlternativeSyntaxFixer, NoEmptyStatementFixer, NoUnneededBracesFixer, NoUnneededCurlyBracesFixer. */ public function getPriority(): int { return parent::getPriority(); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php index 30d02673e6..a363370032 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SimplifiedIfReturnFixer.php @@ -27,7 +27,7 @@ final class SimplifiedIfReturnFixer extends AbstractFixer { /** - * @var array[] + * @var list}> */ private array $sequences = [ [ @@ -60,9 +60,6 @@ final class SimplifiedIfReturnFixer extends AbstractFixer ], ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -75,24 +72,18 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before MultilineWhitespaceBeforeSemicolonsFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer. - * Must run after NoSuperfluousElseifFixer, NoUnneededCurlyBracesFixer, NoUselessElseFixer, SemicolonAfterInstructionFixer. + * Must run after NoSuperfluousElseifFixer, NoUnneededBracesFixer, NoUnneededCurlyBracesFixer, NoUselessElseFixer, SemicolonAfterInstructionFixer. */ public function getPriority(): int { return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_IF, T_RETURN, T_STRING]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($ifIndex = $tokens->count() - 1; 0 <= $ifIndex; --$ifIndex) { @@ -115,7 +106,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - $firstSequenceIndex = key($sequenceFound); + $firstSequenceIndex = array_key_first($sequenceFound); if ($firstSequenceIndex !== $firstCandidateIndex) { continue; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php index bbc6516afe..f5e2210fd7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php @@ -28,9 +28,6 @@ */ final class SwitchCaseSemicolonToColonFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -60,17 +57,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_SWITCH); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { /** @var SwitchAnalysis $analysis */ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php index a48a146247..5616e1f3d8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchCaseSpaceFixer.php @@ -29,9 +29,6 @@ */ final class SwitchCaseSpaceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,17 +48,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_SWITCH); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { /** @var SwitchAnalysis $analysis */ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php index 22ce3c1791..b42fc3a990 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/SwitchContinueToBreakFixer.php @@ -25,13 +25,10 @@ final class SwitchContinueToBreakFixer extends AbstractFixer { /** - * @var int[] + * @var list */ private array $switchLevels = []; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -77,17 +74,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_SWITCH, T_CONTINUE]) && !$tokens->hasAlternativeSyntax(); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $count = \count($tokens); @@ -213,7 +204,7 @@ private function fixContinueWhenActsAsBreak(Tokens $tokens, int $continueIndex, $jump = bindec($jump); // binary - 0b1 } elseif (\strlen($jump) > 1 && '0' === $jump[0]) { $jump = octdec($jump); // octal 01 - } elseif (1 === Preg::match('#^\d+$#', $jump)) { // positive int + } elseif (Preg::match('#^\d+$#', $jump)) { // positive int $jump = (float) $jump; // cast to float, might be a number bigger than PHP max. int value } else { return $afterFollowingContinueIndex; // cannot process value, ignore diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php index 51099dff0a..2f95dd18b7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/TrailingCommaInMultilineFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -36,9 +37,23 @@ * @author Sebastiaan Stok * @author Dariusz Rumiński * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * after_heredoc?: bool, + * elements?: list<'arguments'|'arrays'|'match'|'parameters'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * after_heredoc: bool, + * elements: list<'arguments'|'arrays'|'match'|'parameters'> + * } */ final class TrailingCommaInMultilineFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -54,74 +69,57 @@ final class TrailingCommaInMultilineFixer extends AbstractFixer implements Confi */ public const ELEMENTS_PARAMETERS = 'parameters'; - /** - * {@inheritdoc} - */ + private const MATCH_EXPRESSIONS = 'match'; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Multi-line arrays, arguments list and parameters list must have a trailing comma.', + 'Multi-line arrays, arguments list, parameters list and `match` expressions must have a trailing comma.', [ new CodeSample(" true] ), - new VersionSpecificCodeSample(" [self::ELEMENTS_ARGUMENTS]]), - new VersionSpecificCodeSample(" [self::ELEMENTS_PARAMETERS]]), + new CodeSample(" [self::ELEMENTS_ARGUMENTS]]), + new VersionSpecificCodeSample(" [self::ELEMENTS_PARAMETERS]]), ] ); } - /** - * {@inheritdoc} - * - * Must run after NoMultilineWhitespaceAroundDoubleArrowFixer. - */ - public function getPriority(): int - { - return 0; - } - - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN, '(']); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('after_heredoc', 'Whether a trailing comma should also be placed after heredoc end.')) ->setAllowedTypes(['bool']) ->setDefault(false) - ->setNormalizer(static function (Options $options, $value) { - return $value; - }) ->getOption(), - (new FixerOptionBuilder('elements', sprintf('Where to fix multiline trailing comma (PHP >= 7.3 required for `%s`, PHP >= 8.0 for `%s`).', self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS))) - ->setAllowedTypes(['array']) - ->setAllowedValues([new AllowedValueSubset([self::ELEMENTS_ARRAYS, self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS])]) + (new FixerOptionBuilder('elements', \sprintf('Where to fix multiline trailing comma (PHP >= 8.0 for `%s` and `%s`).', self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS))) // @TODO: remove text when PHP 8.0+ is required + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset([self::ELEMENTS_ARRAYS, self::ELEMENTS_ARGUMENTS, self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS])]) ->setDefault([self::ELEMENTS_ARRAYS]) - ->setNormalizer(static function (Options $options, $value) { - if (\PHP_VERSION_ID < 80000 && \in_array(self::ELEMENTS_PARAMETERS, $value, true)) { - throw new InvalidOptionsForEnvException(sprintf('"%s" option can only be enabled with PHP 8.0+.', self::ELEMENTS_PARAMETERS)); + ->setNormalizer(static function (Options $options, array $value) { + if (\PHP_VERSION_ID < 8_00_00) { // @TODO: drop condition when PHP 8.0+ is required + foreach ([self::ELEMENTS_PARAMETERS, self::MATCH_EXPRESSIONS] as $option) { + if (\in_array($option, $value, true)) { + throw new InvalidOptionsForEnvException(\sprintf('"%s" option can only be enabled with PHP 8.0+.', $option)); + } + } } return $value; @@ -130,14 +128,12 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $fixArrays = \in_array(self::ELEMENTS_ARRAYS, $this->configuration['elements'], true); $fixArguments = \in_array(self::ELEMENTS_ARGUMENTS, $this->configuration['elements'], true); - $fixParameters = \in_array(self::ELEMENTS_PARAMETERS, $this->configuration['elements'], true); + $fixParameters = \PHP_VERSION_ID >= 8_00_00 && \in_array(self::ELEMENTS_PARAMETERS, $this->configuration['elements'], true); // @TODO: drop condition when PHP 8.0+ is required + $fixMatch = \PHP_VERSION_ID >= 8_00_00 && \in_array(self::MATCH_EXPRESSIONS, $this->configuration['elements'], true); // @TODO: drop condition when PHP 8.0+ is required for ($index = $tokens->count() - 1; $index >= 0; --$index) { $prevIndex = $tokens->getPrevMeaningfulToken($index); @@ -161,7 +157,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); if ($fixArguments - && $tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE]]) + && $tokens[$prevIndex]->equalsAny([']', [T_CLASS], [T_STRING], [T_VARIABLE], [T_STATIC], [T_ISSET], [T_UNSET], [T_LIST]]) && !$tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) ) { $this->fixBlock($tokens, $index); @@ -172,12 +168,17 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void if ( $fixParameters && ( - $tokens[$prevIndex]->isGivenKind(T_STRING) && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) + $tokens[$prevIndex]->isGivenKind(T_STRING) + && $tokens[$prevPrevIndex]->isGivenKind(T_FUNCTION) || $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]) ) ) { $this->fixBlock($tokens, $index); } + + if ($fixMatch && $tokens[$prevIndex]->isGivenKind(T_MATCH)) { + $this->fixMatch($tokens, $index); + } } } @@ -193,6 +194,9 @@ private function fixBlock(Tokens $tokens, int $startIndex): void $endIndex = $tokens->findBlockEnd($blockType['type'], $startIndex); $beforeEndIndex = $tokens->getPrevMeaningfulToken($endIndex); + if (!$tokens->isPartialCodeMultiline($beforeEndIndex, $endIndex)) { + return; + } $beforeEndToken = $tokens[$beforeEndIndex]; // if there is some item between braces then add `,` after it @@ -209,4 +213,37 @@ private function fixBlock(Tokens $tokens, int $startIndex): void } } } + + private function fixMatch(Tokens $tokens, int $index): void + { + $index = $tokens->getNextTokenOfKind($index, ['{']); + $closeIndex = $index; + $isMultiline = false; + $depth = 1; + + do { + ++$closeIndex; + + if ($tokens[$closeIndex]->equals('{')) { + ++$depth; + } elseif ($tokens[$closeIndex]->equals('}')) { + --$depth; + } elseif (!$isMultiline && str_contains($tokens[$closeIndex]->getContent(), "\n")) { + $isMultiline = true; + } + } while ($depth > 0); + + if (!$isMultiline) { + return; + } + + $previousIndex = $tokens->getPrevMeaningfulToken($closeIndex); + if (!$tokens->isPartialCodeMultiline($previousIndex, $closeIndex)) { + return; + } + + if (!$tokens[$previousIndex]->equals(',')) { + $tokens->insertAt($previousIndex + 1, new Token(',')); + } + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php index 348d926f47..6046f8f1de 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ControlStructure/YodaStyleFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -30,9 +31,27 @@ /** * @author Bram Gotink * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * always_move_variable?: bool, + * equal?: bool|null, + * identical?: bool|null, + * less_and_greater?: bool|null + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * always_move_variable: bool, + * equal: bool|null, + * identical: bool|null, + * less_and_greater: bool|null + * } */ final class YodaStyleFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var array */ @@ -44,23 +63,10 @@ final class YodaStyleFixer extends AbstractFixer implements ConfigurableFixerInt private $candidateTypesConfiguration; /** - * @var array + * @var list */ private $candidateTypes; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->resolveConfiguration(); - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -120,25 +126,21 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound($this->candidateTypes); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->resolveConfiguration(); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $this->fixTokens($tokens); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -352,7 +354,7 @@ private function fixTokensCompare( private function fixTokensComparePart(Tokens $tokens, int $start, int $end): Tokens { $newTokens = $tokens->generatePartialCode($start, $end); - $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('fixTokens(Tokens::fromCode(\sprintf('clearAt(\count($newTokens) - 1); $newTokens->clearAt(0); $newTokens->clearEmptyTokens(); @@ -360,15 +362,19 @@ private function fixTokensComparePart(Tokens $tokens, int $start, int $end): Tok return $newTokens; } + /** + * @return null|array{left: array{start: int, end: int}, right: array{start: int, end: int}} + */ private function getCompareFixableInfo(Tokens $tokens, int $index, bool $yoda): ?array { - $left = $this->getLeftSideCompareFixableInfo($tokens, $index); $right = $this->getRightSideCompareFixableInfo($tokens, $index); if (!$yoda && $this->isOfLowerPrecedenceAssignment($tokens[$tokens->getNextMeaningfulToken($right['end'])])) { return null; } + $left = $this->getLeftSideCompareFixableInfo($tokens, $index); + if ($this->isListStatement($tokens, $left['start'], $left['end']) || $this->isListStatement($tokens, $right['start'], $right['end'])) { return null; // do not fix lists assignment inside statements } @@ -378,7 +384,7 @@ private function getCompareFixableInfo(Tokens $tokens, int $index, bool $yoda): $leftSideIsVariable = $this->isVariable($tokens, $left['start'], $left['end'], $strict); $rightSideIsVariable = $this->isVariable($tokens, $right['start'], $right['end'], $strict); - if (!($leftSideIsVariable ^ $rightSideIsVariable)) { + if (!($leftSideIsVariable xor $rightSideIsVariable)) { return null; // both are (not) variables, do not touch } @@ -389,10 +395,12 @@ private function getCompareFixableInfo(Tokens $tokens, int $index, bool $yoda): return ($yoda && !$leftSideIsVariable) || (!$yoda && !$rightSideIsVariable) ? null - : ['left' => $left, 'right' => $right] - ; + : ['left' => $left, 'right' => $right]; } + /** + * @return array{start: int, end: int} + */ private function getLeftSideCompareFixableInfo(Tokens $tokens, int $index): array { return [ @@ -401,6 +409,9 @@ private function getLeftSideCompareFixableInfo(Tokens $tokens, int $index): arra ]; } + /** + * @return array{start: int, end: int} + */ private function getRightSideCompareFixableInfo(Tokens $tokens, int $index): array { return [ @@ -450,6 +461,11 @@ private function isOfLowerPrecedence(Token $token): bool T_THROW, // throw T_COALESCE, T_YIELD, // yield + T_YIELD_FROM, + T_REQUIRE, + T_REQUIRE_ONCE, + T_INCLUDE, + T_INCLUDE_ONCE, ]; } @@ -673,7 +689,7 @@ private function isConstant(Tokens $tokens, int $index, int $end): bool return false; } - if ($token->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { + if ($token->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $expectArrayOnly = true; continue; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php index 6d7d7e840d..eded161489 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DeprecatedFixerInterface.php @@ -22,7 +22,7 @@ interface DeprecatedFixerInterface extends FixerInterface /** * Returns names of fixers to use instead, if any. * - * @return string[] + * @return list */ public function getSuccessorsNames(): array; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php index f2ead59953..d3158f54a9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationArrayAssignmentFixer.php @@ -14,9 +14,11 @@ namespace PhpCsFixer\Fixer\DoctrineAnnotation; -use Doctrine\Common\Annotations\DocLexer; use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,12 +28,23 @@ /** * Forces the configured operator for assignment in arrays in Doctrine Annotations. + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * ignored_tags?: list, + * operator?: ':'|'=' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * ignored_tags: list, + * operator: ':'|'=' + * } */ -final class DoctrineAnnotationArrayAssignmentFixer extends AbstractDoctrineAnnotationFixer +final class DoctrineAnnotationArrayAssignmentFixer extends AbstractDoctrineAnnotationFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -58,15 +71,11 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $options = parent::createConfigurationDefinition()->getOptions(); - $operator = new FixerOptionBuilder('operator', 'The operator to use.'); - $options[] = $operator + $options[] = (new FixerOptionBuilder('operator', 'The operator to use.')) ->setAllowedValues(['=', ':']) ->setDefault('=') ->getOption() @@ -75,9 +84,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn return new FixerConfigurationResolver($options); } - /** - * {@inheritdoc} - */ protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void { $scopes = []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php index 8ecbda4c68..928254a2cf 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php @@ -14,10 +14,12 @@ namespace PhpCsFixer\Fixer\DoctrineAnnotation; -use Doctrine\Common\Annotations\DocLexer; use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; use PhpCsFixer\Doctrine\Annotation\Token; use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,12 +29,23 @@ /** * Adds braces to Doctrine annotations when missing. + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * ignored_tags?: list, + * syntax?: 'with_braces'|'without_braces' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * ignored_tags: list, + * syntax: 'with_braces'|'without_braces' + * } */ -final class DoctrineAnnotationBracesFixer extends AbstractDoctrineAnnotationFixer +final class DoctrineAnnotationBracesFixer extends AbstractDoctrineAnnotationFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -49,25 +62,17 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - return new FixerConfigurationResolver(array_merge( - parent::createConfigurationDefinition()->getOptions(), - [ - (new FixerOptionBuilder('syntax', 'Whether to add or remove braces.')) - ->setAllowedValues(['with_braces', 'without_braces']) - ->setDefault('without_braces') - ->getOption(), - ] - )); + return new FixerConfigurationResolver([ + ...parent::createConfigurationDefinition()->getOptions(), + (new FixerOptionBuilder('syntax', 'Whether to add or remove braces.')) + ->setAllowedValues(['with_braces', 'without_braces']) + ->setDefault('without_braces') + ->getOption(), + ]); } - /** - * {@inheritdoc} - */ protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void { if ('without_braces' === $this->configuration['syntax']) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php index 8da8cc0a3c..c9c01e01f1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php @@ -14,9 +14,11 @@ namespace PhpCsFixer\Fixer\DoctrineAnnotation; -use Doctrine\Common\Annotations\DocLexer; use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -25,11 +27,23 @@ use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; -final class DoctrineAnnotationIndentationFixer extends AbstractDoctrineAnnotationFixer +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * ignored_tags?: list, + * indent_mixed_lines?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * ignored_tags: list, + * indent_mixed_lines: bool + * } + */ +final class DoctrineAnnotationIndentationFixer extends AbstractDoctrineAnnotationFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,25 +58,17 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - return new FixerConfigurationResolver(array_merge( - parent::createConfigurationDefinition()->getOptions(), - [ - (new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.')) - ->setAllowedTypes(['bool']) - ->setDefault(false) - ->getOption(), - ] - )); + return new FixerConfigurationResolver([ + ...parent::createConfigurationDefinition()->getOptions(), + (new FixerOptionBuilder('indent_mixed_lines', 'Whether to indent lines that have content before closing parenthesis.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); } - /** - * {@inheritdoc} - */ protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void { $annotationPositions = []; @@ -116,7 +122,7 @@ protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void } /** - * @return int[] + * @return array{int, int} */ private function getLineBracesCount(Tokens $tokens, int $index): array { @@ -168,7 +174,7 @@ private function isClosingLineWithMeaningfulContent(Tokens $tokens, int $index): } /** - * @param array> $annotationPositions Pairs of begin and end indices of main annotations + * @param list $annotationPositions Pairs of begin and end indices of main annotations */ private function indentationCanBeFixed(Tokens $tokens, int $newLineTokenIndex, array $annotationPositions): bool { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php index bc04e752ac..e39fe008e9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php @@ -14,10 +14,12 @@ namespace PhpCsFixer\Fixer\DoctrineAnnotation; -use Doctrine\Common\Annotations\DocLexer; use PhpCsFixer\AbstractDoctrineAnnotationFixer; +use PhpCsFixer\Doctrine\Annotation\DocLexer; use PhpCsFixer\Doctrine\Annotation\Token; use PhpCsFixer\Doctrine\Annotation\Tokens; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,12 +30,37 @@ /** * Fixes spaces around commas and assignment operators in Doctrine annotations. + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * after_argument_assignments?: bool|null, + * after_array_assignments_colon?: bool|null, + * after_array_assignments_equals?: bool|null, + * around_commas?: bool, + * around_parentheses?: bool, + * before_argument_assignments?: bool|null, + * before_array_assignments_colon?: bool|null, + * before_array_assignments_equals?: bool|null, + * ignored_tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * after_argument_assignments: bool|null, + * after_array_assignments_colon: bool|null, + * after_array_assignments_equals: bool|null, + * around_commas: bool, + * around_parentheses: bool, + * before_argument_assignments: bool|null, + * before_array_assignments_colon: bool|null, + * before_array_assignments_equals: bool|null, + * ignored_tags: list + * } */ -final class DoctrineAnnotationSpacesFixer extends AbstractDoctrineAnnotationFixer +final class DoctrineAnnotationSpacesFixer extends AbstractDoctrineAnnotationFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -61,53 +88,45 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - return new FixerConfigurationResolver(array_merge( - parent::createConfigurationDefinition()->getOptions(), - [ - (new FixerOptionBuilder('around_parentheses', 'Whether to fix spaces around parentheses.')) - ->setAllowedTypes(['bool']) - ->setDefault(true) - ->getOption(), - (new FixerOptionBuilder('around_commas', 'Whether to fix spaces around commas.')) - ->setAllowedTypes(['bool']) - ->setDefault(true) - ->getOption(), - (new FixerOptionBuilder('before_argument_assignments', 'Whether to add, remove or ignore spaces before argument assignment operator.')) - ->setAllowedTypes(['null', 'bool']) - ->setDefault(false) - ->getOption(), - (new FixerOptionBuilder('after_argument_assignments', 'Whether to add, remove or ignore spaces after argument assignment operator.')) - ->setAllowedTypes(['null', 'bool']) - ->setDefault(false) - ->getOption(), - (new FixerOptionBuilder('before_array_assignments_equals', 'Whether to add, remove or ignore spaces before array `=` assignment operator.')) - ->setAllowedTypes(['null', 'bool']) - ->setDefault(true) - ->getOption(), - (new FixerOptionBuilder('after_array_assignments_equals', 'Whether to add, remove or ignore spaces after array assignment `=` operator.')) - ->setAllowedTypes(['null', 'bool']) - ->setDefault(true) - ->getOption(), - (new FixerOptionBuilder('before_array_assignments_colon', 'Whether to add, remove or ignore spaces before array `:` assignment operator.')) - ->setAllowedTypes(['null', 'bool']) - ->setDefault(true) - ->getOption(), - (new FixerOptionBuilder('after_array_assignments_colon', 'Whether to add, remove or ignore spaces after array assignment `:` operator.')) - ->setAllowedTypes(['null', 'bool']) - ->setDefault(true) - ->getOption(), - ] - )); + return new FixerConfigurationResolver([ + ...parent::createConfigurationDefinition()->getOptions(), + (new FixerOptionBuilder('around_parentheses', 'Whether to fix spaces around parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('around_commas', 'Whether to fix spaces around commas.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('before_argument_assignments', 'Whether to add, remove or ignore spaces before argument assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('after_argument_assignments', 'Whether to add, remove or ignore spaces after argument assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder('before_array_assignments_equals', 'Whether to add, remove or ignore spaces before array `=` assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('after_array_assignments_equals', 'Whether to add, remove or ignore spaces after array assignment `=` operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('before_array_assignments_colon', 'Whether to add, remove or ignore spaces before array `:` assignment operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('after_array_assignments_colon', 'Whether to add, remove or ignore spaces after array assignment `:` operator.')) + ->setAllowedTypes(['null', 'bool']) + ->setDefault(true) + ->getOption(), + ]); } - /** - * {@inheritdoc} - */ protected function fixAnnotations(Tokens $doctrineAnnotationTokens): void { if (true === $this->configuration['around_parentheses']) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ExperimentalFixerInterface.php similarity index 73% rename from vendor/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php rename to vendor/friendsofphp/php-cs-fixer/src/Fixer/ExperimentalFixerInterface.php index 80726c380d..9ade1875d5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/ProcessOutputInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ExperimentalFixerInterface.php @@ -12,12 +12,9 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\Console\Output; +namespace PhpCsFixer\Fixer; /** * @internal */ -interface ProcessOutputInterface -{ - public function printLegend(): void; -} +interface ExperimentalFixerInterface extends FixerInterface {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php index 69a5891ab8..a96f73fe3d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php @@ -27,9 +27,6 @@ */ final class CombineNestedDirnameFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,17 +41,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -63,7 +54,7 @@ public function isRisky(): bool /** * {@inheritdoc} * - * Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer. + * Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. * Must run after DirConstantFixer. */ public function getPriority(): int @@ -71,19 +62,16 @@ public function getPriority(): int return 35; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; 0 <= $index; --$index) { $dirnameInfo = $this->getDirnameInfo($tokens, $index); - if (!$dirnameInfo) { + if (false === $dirnameInfo) { continue; } - $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indices'][0]); if (!$tokens[$prev]->equals('(')) { continue; @@ -95,7 +83,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) { $dirnameInfoArray[] = $dirnameInfo; - $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indices'][0]); if (!$tokens[$prev]->equals('(')) { break; @@ -117,7 +105,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void * @param int $index Index of `dirname` * @param null|int $firstArgumentEndIndex Index of last token of first argument of `dirname` call * - * @return array|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise + * @return array{indices: list, secondArgument?: int, levels: int, end: int}|false `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise */ private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentEndIndex = null) { @@ -129,18 +117,18 @@ private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentE return false; } - $info = ['indexes' => []]; + $info = ['indices' => []]; $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { - $info['indexes'][] = $prev; + $info['indices'][] = $prev; } - $info['indexes'][] = $index; + $info['indices'][] = $index; // opening parenthesis "(" $next = $tokens->getNextMeaningfulToken($index); - $info['indexes'][] = $next; + $info['indices'][] = $next; if (null !== $firstArgumentEndIndex) { $next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex); @@ -162,11 +150,11 @@ private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentE } } - $info['indexes'][] = $next; + $info['indices'][] = $next; if ($tokens[$next]->equals(',')) { $next = $tokens->getNextMeaningfulToken($next); - $info['indexes'][] = $next; + $info['indices'][] = $next; } if ($tokens[$next]->equals(')')) { @@ -186,7 +174,7 @@ private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentE $next = $tokens->getNextMeaningfulToken($next); if ($tokens[$next]->equals(',')) { - $info['indexes'][] = $next; + $info['indices'][] = $next; $next = $tokens->getNextMeaningfulToken($next); } @@ -194,12 +182,15 @@ private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentE return false; } - $info['indexes'][] = $next; + $info['indices'][] = $next; $info['end'] = $next; return $info; } + /** + * @param non-empty-list, secondArgument?: int, levels: int, end: int}> $dirnameInfoArray + */ private function combineDirnames(Tokens $tokens, array $dirnameInfoArray): void { $outerDirnameInfo = array_pop($dirnameInfoArray); @@ -208,7 +199,7 @@ private function combineDirnames(Tokens $tokens, array $dirnameInfoArray): void foreach ($dirnameInfoArray as $dirnameInfo) { $levels += $dirnameInfo['levels']; - foreach ($dirnameInfo['indexes'] as $index) { + foreach ($dirnameInfo['indices'] as $index) { $tokens->removeLeadingWhitespace($index); $tokens->clearTokenAndMergeSurroundingWhitespace($index); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php index 44bc188600..55ff455c83 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php @@ -19,7 +19,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -35,24 +34,39 @@ public function getDefinition(): FixerDefinitionInterface ], "Consider this code: `DateTime::createFromFormat('Y-m-d', '2022-02-11')`. - What value will be returned? '2022-01-11 00:00:00.0'? No, actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'. - Change 'Y-m-d' to '!Y-m-d', return value will be '2022-01-11 00:00:00.0'. - So, adding `!` to format string will make return value more intuitive." + What value will be returned? '2022-02-11 00:00:00.0'? No, actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'. + Change 'Y-m-d' to '!Y-m-d', return value will be '2022-02-11 00:00:00.0'. + So, adding `!` to format string will make return value more intuitive.", + 'Risky when depending on the actual timings being used even when not explicit set in format.' ); } + /** + * {@inheritdoc} + * + * Must run after NoUselessConcatOperatorFixer. + */ + public function getPriority(): int + { + return 0; + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOUBLE_COLON); } + public function isRisky(): bool + { + return true; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); - $namespacesAnalyzer = new NamespacesAnalyzer(); $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); - foreach ($namespacesAnalyzer->getDeclarations($tokens) as $namespace) { + foreach ($tokens->getNamespaceDeclarations() as $namespace) { $scopeStartIndex = $namespace->getScopeStartIndex(); $useDeclarations = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace); @@ -73,7 +87,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $classNameIndex = $tokens->getPrevMeaningfulToken($index); - if (!$tokens[$classNameIndex]->equals([T_STRING, 'DateTime'], false)) { + if (!$tokens[$classNameIndex]->equalsAny([[T_STRING, \DateTime::class], [T_STRING, \DateTimeImmutable::class]], false)) { continue; } @@ -87,8 +101,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } else { foreach ($useDeclarations as $useDeclaration) { - if ('datetime' === strtolower($useDeclaration->getShortName()) && 'datetime' !== strtolower($useDeclaration->getFullName())) { - continue 2; + foreach (['datetime', 'datetimeimmutable'] as $name) { + if ($name === strtolower($useDeclaration->getShortName()) && $name !== strtolower($useDeclaration->getFullName())) { + continue 3; + } } } } @@ -104,16 +120,25 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $format = $tokens[$argumentIndex]->getContent(); - if ('!' === substr($format, 1, 1)) { + if (\strlen($format) < 3) { + continue; + } + + $offset = 'b' === $format[0] || 'B' === $format[0] ? 2 : 1; + + if ('!' === $format[$offset]) { continue; } $tokens->clearAt($argumentIndex); - $tokens->insertAt($argumentIndex, new Token([T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', 1, 0)])); + $tokens->insertAt($argumentIndex, new Token([T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', $offset, 0)])); } } } + /** + * @param array $arguments + */ private function getFirstArgumentTokenIndex(Tokens $tokens, array $arguments): ?int { if (2 !== \count($arguments)) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php index 15b0174fab..3bf72e223f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php @@ -24,9 +24,6 @@ final class FopenFlagOrderFixer extends AbstractFopenFlagFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -88,9 +85,9 @@ protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, in } /** - * @param string[] $flags + * @param list $flags * - * @return string[] + * @return list */ private function sortFlags(array $flags): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php index 55412a1886..b363dc8711 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FopenFlagsFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFopenFlagFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -25,11 +26,21 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * b_mode?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * b_mode: bool + * } + */ final class FopenFlagsFixer extends AbstractFopenFlagFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -43,9 +54,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php index 9005f5d9bd..2f20063046 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php @@ -16,14 +16,13 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; @@ -32,9 +31,25 @@ * Fixer for rules defined in PSR2 generally (¶1 and ¶6). * * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * closure_fn_spacing?: 'none'|'one', + * closure_function_spacing?: 'none'|'one', + * trailing_comma_single_line?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * closure_fn_spacing: 'none'|'one', + * closure_function_spacing: 'none'|'one', + * trailing_comma_single_line: bool + * } */ final class FunctionDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -49,17 +64,11 @@ final class FunctionDeclarationFixer extends AbstractFixer implements Configurab private string $singleLineWhitespaceOptions = " \t"; - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -88,12 +97,11 @@ function foo ($bar, $baz) ', ['closure_function_spacing' => self::SPACING_NONE] ), - new VersionSpecificCodeSample( + new CodeSample( ' null; ', - new VersionSpecification(70400), - ['closure_function_spacing' => self::SPACING_NONE] + ['closure_fn_spacing' => self::SPACING_NONE] ), ] ); @@ -103,16 +111,13 @@ function foo ($bar, $baz) * {@inheritdoc} * * Must run before MethodArgumentSpaceFixer. - * Must run after SingleSpaceAfterConstructFixer. + * Must run after SingleSpaceAfterConstructFixer, SingleSpaceAroundConstructFixer, UseArrowFunctionsFixer. */ public function getPriority(): int { return 31; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -195,7 +200,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $tokens->clearAt($startParenthesisIndex - 1); } - if ($isLambda && self::SPACING_NONE === $this->configuration['closure_function_spacing']) { + $option = $token->isGivenKind(T_FN) ? 'closure_fn_spacing' : 'closure_function_spacing'; + + if ($isLambda && self::SPACING_NONE === $this->configuration[$option]) { // optionally remove whitespace after T_FUNCTION of a closure // eg: `function () {}` => `function() {}` if ($tokens[$index + 1]->isWhitespace()) { @@ -219,9 +226,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -229,6 +233,10 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setDefault(self::SPACING_ONE) ->setAllowedValues(self::SUPPORTED_SPACINGS) ->getOption(), + (new FixerOptionBuilder('closure_fn_spacing', 'Spacing to use before open parenthesis for short arrow functions.')) + ->setDefault(self::SPACING_ONE) // @TODO change to SPACING_NONE on next major 4.0 + ->setAllowedValues(self::SUPPORTED_SPACINGS) + ->getOption(), (new FixerOptionBuilder('trailing_comma_single_line', 'Whether trailing commas are allowed in single line signatures.')) ->setAllowedTypes(['bool']) ->setDefault(false) diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php index f1f16cc9a7..132f7c1340 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/FunctionTypehintSpaceFixer.php @@ -14,22 +14,21 @@ namespace PhpCsFixer\Fixer\FunctionNotation; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\Whitespace\TypeDeclarationSpacesFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; -use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Tokens; /** * @author Dariusz Rumiński + * + * @deprecated */ -final class FunctionTypehintSpaceFixer extends AbstractFixer +final class FunctionTypehintSpaceFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -41,39 +40,21 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + public function getSuccessorsNames(): array { - $functionsAnalyzer = new FunctionsAnalyzer(); - - for ($index = $tokens->count() - 1; $index >= 0; --$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind([T_FUNCTION, T_FN])) { - continue; - } - - $arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index); - - foreach (array_reverse($arguments) as $argument) { - $type = $argument->getTypeAnalysis(); + return array_keys($this->proxyFixers); + } - if (!$type instanceof TypeAnalysis) { - continue; - } + protected function createProxyFixers(): array + { + $fixer = new TypeDeclarationSpacesFixer(); + $fixer->configure(['elements' => ['function']]); - $tokens->ensureWhitespaceAtIndex($type->getEndIndex() + 1, 0, ' '); - } - } + return [$fixer]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php index 166482d610..4561c393c1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ImplodeCallFixer.php @@ -28,9 +28,6 @@ */ final class ImplodeCallFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,17 +41,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); @@ -71,9 +62,6 @@ public function getPriority(): int return 37; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -90,7 +78,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $argumentsIndices = $this->getArgumentIndices($tokens, $index); if (1 === \count($argumentsIndices)) { - $firstArgumentIndex = key($argumentsIndices); + $firstArgumentIndex = array_key_first($argumentsIndices); $tokens->insertAt($firstArgumentIndex, [ new Token([T_CONSTANT_ENCAPSED_STRING, "''"]), new Token(','), @@ -115,7 +103,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // collect tokens from first argument $firstArgumentEndIndex = $argumentsIndices[key($argumentsIndices)]; $newSecondArgumentTokens = []; - for ($i = key($argumentsIndices); $i <= $firstArgumentEndIndex; ++$i) { + for ($i = array_key_first($argumentsIndices); $i <= $firstArgumentEndIndex; ++$i) { $newSecondArgumentTokens[] = clone $tokens[$i]; $tokens->clearAt($i); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php index ca24ae798e..e026637c56 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/LambdaNotUsedImportFixer.php @@ -41,9 +41,6 @@ final class LambdaNotUsedImportFixer extends AbstractFixer */ private $tokensAnalyzer; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -55,16 +52,13 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before NoSpacesInsideParenthesisFixer. + * Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. */ public function getPriority(): int { - return 3; + return 31; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_FUNCTION, CT::T_USE_LAMBDA]); @@ -113,6 +107,11 @@ private function fixLambda(Tokens $tokens, int $lambdaUseIndex): void $this->clearImports($tokens, array_reverse($notUsedImports)); } + /** + * @param array $imports + * + * @return array + */ private function findNotUsedLambdaImports(Tokens $tokens, array $imports, int $lambdaUseCloseBraceIndex): array { static $riskyKinds = [ @@ -237,6 +236,12 @@ private function findNotUsedLambdaImports(Tokens $tokens, array $imports, int $l return $imports; } + /** + * @param array $imports + * @param array $arguments + * + * @return array + */ private function countImportsUsedAsArgument(Tokens $tokens, array $imports, array $arguments): array { foreach ($arguments as $start => $end) { @@ -280,6 +285,11 @@ private function getLambdaUseIndex(Tokens $tokens, int $index) return $lambdaUseIndex; } + /** + * @param array $arguments + * + * @return array + */ private function filterArguments(Tokens $tokens, array $arguments): array { $imports = []; @@ -308,6 +318,9 @@ private function filterArguments(Tokens $tokens, array $arguments): array return $imports; } + /** + * @param array $imports + */ private function clearImports(Tokens $tokens, array $imports): void { foreach ($imports as $removeIndex) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php index 2579e046e5..612fa4b7e2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -23,24 +24,34 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -use Symfony\Component\OptionsResolver\Options; /** - * Fixer for rules defined in PSR2 ¶4.4, ¶4.6. - * * @author Kuanhung Chen + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * after_heredoc?: bool, + * attribute_placement?: 'ignore'|'same_line'|'standalone', + * keep_multiple_spaces_after_comma?: bool, + * on_multiline?: 'ensure_fully_multiline'|'ensure_single_line'|'ignore' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * after_heredoc: bool, + * attribute_placement: 'ignore'|'same_line'|'standalone', + * keep_multiple_spaces_after_comma: bool, + * on_multiline: 'ensure_fully_multiline'|'ensure_single_line'|'ignore' + * } */ final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -80,59 +91,63 @@ public function getDefinition(): FixerDefinitionInterface 'keep_multiple_spaces_after_comma' => false, ] ), - new VersionSpecificCodeSample( + new CodeSample( + " 'ensure_fully_multiline', + 'attribute_placement' => 'ignore', + ] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'attribute_placement' => 'same_line', + ] + ), + new CodeSample( + " 'ensure_fully_multiline', + 'attribute_placement' => 'standalone', + ] + ), + new CodeSample( <<<'SAMPLE' - true] ), - ] + ], + 'This fixer covers rules defined in PSR2 ¶4.4, ¶4.6.' ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound('('); } - public function configure(array $configuration): void - { - parent::configure($configuration); - - if (isset($configuration['ensure_fully_multiline'])) { - $this->configuration['on_multiline'] = $this->configuration['ensure_fully_multiline'] - ? 'ensure_fully_multiline' - : 'ignore'; - } - } - /** * {@inheritdoc} * - * Must run before ArrayIndentationFixer. - * Must run after BracesFixer, CombineNestedDirnameFixer, FunctionDeclarationFixer, ImplodeCallFixer, MethodChainingIndentationFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUselessSprintfFixer, PowToExponentiationFixer, StrictParamFixer. + * Must run before ArrayIndentationFixer, StatementIndentationFixer. + * Must run after CombineNestedDirnameFixer, FunctionDeclarationFixer, ImplodeCallFixer, LambdaNotUsedImportFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUselessSprintfFixer, PowToExponentiationFixer, StrictParamFixer. */ public function getPriority(): int { return 30; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $expectedTokens = [T_LIST, T_FUNCTION, CT::T_USE_LAMBDA, T_FN, T_CLASS]; @@ -165,9 +180,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -185,9 +197,13 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn (new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.')) ->setAllowedTypes(['bool']) ->setDefault(false) - ->setNormalizer(static function (Options $options, $value) { - return $value; - }) + ->getOption(), + (new FixerOptionBuilder( + 'attribute_placement', + 'Defines how to handle argument attributes when function definition is multiline.' + )) + ->setAllowedValues(['ignore', 'same_line', 'standalone']) + ->setDefault('standalone') ->getOption(), ]); } @@ -251,8 +267,6 @@ private function fixFunction(Tokens $tokens, int $startFunctionIndex): bool $this->fixSpace($tokens, $index); if (!$isMultiline && $this->isNewline($tokens[$index + 1])) { $isMultiline = true; - - break; } } } @@ -306,7 +320,7 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction do { $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind( $searchIndex, - [[T_WHITESPACE]] + [[T_ENCAPSED_AND_WHITESPACE], [T_WHITESPACE]], ); $searchIndex = $prevWhitespaceTokenIndex; @@ -316,13 +330,14 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction if (null === $prevWhitespaceTokenIndex) { $existingIndentation = ''; + } elseif (!$tokens[$prevWhitespaceTokenIndex]->isGivenKind(T_WHITESPACE)) { + return; } else { $existingIndentation = $tokens[$prevWhitespaceTokenIndex]->getContent(); $lastLineIndex = strrpos($existingIndentation, "\n"); $existingIndentation = false === $lastLineIndex ? $existingIndentation - : substr($existingIndentation, $lastLineIndex + 1) - ; + : substr($existingIndentation, $lastLineIndex + 1); } $indentation = $existingIndentation.$this->whitespacesConfig->getIndent(); @@ -361,7 +376,23 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction continue; } - if ($token->equals(',') && !$tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')) { + if ($tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')) { + continue; + } + + if ($token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + if ('standalone' === $this->configuration['attribute_placement']) { + $this->fixNewline($tokens, $index, $indentation); + } elseif ('same_line' === $this->configuration['attribute_placement']) { + $this->ensureSingleLine($tokens, $index + 1); + $tokens->ensureWhitespaceAtIndex($index + 1, 0, ' '); + } + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + + continue; + } + + if ($token->equals(',')) { $this->fixNewline($tokens, $index, $indentation); } } @@ -370,7 +401,7 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction } /** - * Method to insert newline after comma or opening parenthesis. + * Method to insert newline after comma, attribute or opening parenthesis. * * @param int $index index of a comma * @param string $indentation the indentation that should be used @@ -385,6 +416,10 @@ private function fixNewline(Tokens $tokens, int $index, string $indentation, boo if ($tokens[$index + 2]->isComment()) { $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index + 2); if (!$this->isNewline($tokens[$nextMeaningfulTokenIndex - 1])) { + if ($tokens[$nextMeaningfulTokenIndex - 1]->isWhitespace()) { + $tokens->clearAt($nextMeaningfulTokenIndex - 1); + } + $tokens->ensureWhitespaceAtIndex($nextMeaningfulTokenIndex, 0, $this->whitespacesConfig->getLineEnding().$indentation); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php index 7ccbbfa70a..4feb68b8af 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -24,16 +25,34 @@ use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Andreas Möller + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * exclude?: list, + * include?: list, + * scope?: 'all'|'namespaced', + * strict?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * exclude: list, + * include: list, + * scope: 'all'|'namespaced', + * strict: bool + * } */ final class NativeFunctionInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -61,16 +80,6 @@ final class NativeFunctionInvocationFixer extends AbstractFixer implements Confi */ private $functionFilter; - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->functionFilter = $this->getFunctionFilter(); - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -174,25 +183,21 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->functionFilter = $this->getFunctionFilter(); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if ('all' === $this->configuration['scope']) { @@ -201,7 +206,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void return; } - $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); + $namespaces = $tokens->getNamespaceDeclarations(); // 'scope' is 'namespaced' here /** @var NamespaceAnalysis $namespace */ @@ -210,18 +215,15 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('exclude', 'List of functions to ignore.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([static function (array $value): bool { foreach ($value as $functionName) { - if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { - throw new InvalidOptionsException(sprintf( + if ('' === trim($functionName) || trim($functionName) !== $functionName) { + throw new InvalidOptionsException(\sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', get_debug_type($functionName) )); @@ -233,11 +235,11 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setDefault([]) ->getOption(), (new FixerOptionBuilder('include', 'List of function names or sets to fix. Defined sets are `@internal` (all native functions), `@all` (all global functions) and `@compiler_optimized` (functions that are specially optimized by Zend).')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([static function (array $value): bool { foreach ($value as $functionName) { - if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { - throw new InvalidOptionsException(sprintf( + if ('' === trim($functionName) || trim($functionName) !== $functionName) { + throw new InvalidOptionsException(\sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', get_debug_type($functionName) )); @@ -250,7 +252,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]; if (str_starts_with($functionName, '@') && !\in_array($functionName, $sets, true)) { - throw new InvalidOptionsException(sprintf('Unknown set "%s", known sets are "%s".', $functionName, implode('", "', $sets))); + throw new InvalidOptionsException(\sprintf('Unknown set "%s", known sets are %s.', $functionName, Utils::naturalLanguageJoin($sets))); } } @@ -309,14 +311,10 @@ private function getFunctionFilter(): callable if (\in_array(self::SET_ALL, $this->configuration['include'], true)) { if (\count($exclude) > 0) { - return static function (string $functionName) use ($exclude): bool { - return !isset($exclude[strtolower($functionName)]); - }; + return static fn (string $functionName): bool => !isset($exclude[strtolower($functionName)]); } - return static function (): bool { - return true; - }; + return static fn (): bool => true; } $include = []; @@ -334,14 +332,10 @@ private function getFunctionFilter(): callable } if (\count($exclude) > 0) { - return static function (string $functionName) use ($include, $exclude): bool { - return isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]); - }; + return static fn (string $functionName): bool => isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]); } - return static function (string $functionName) use ($include): bool { - return isset($include[strtolower($functionName)]); - }; + return static fn (string $functionName): bool => isset($include[strtolower($functionName)]); } /** @@ -384,6 +378,7 @@ private function getAllCompilerOptimizedFunctionsNormalized(): array 'is_string', 'ord', 'sizeof', + 'sprintf', 'strlen', 'strval', // @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c @@ -408,17 +403,18 @@ private function getAllInternalFunctionsNormalized(): array } /** - * @param string[] $functionNames + * @param list $functionNames * * @return array all function names lower cased */ private function normalizeFunctionNames(array $functionNames): array { - foreach ($functionNames as $index => $functionName) { - $functionNames[strtolower($functionName)] = true; - unset($functionNames[$index]); + $result = []; + + foreach ($functionNames as $functionName) { + $result[strtolower($functionName)] = true; } - return $functionNames; + return $result; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php index 24ef8469ae..a63e99141b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoSpacesAfterFunctionNameFixer.php @@ -29,14 +29,11 @@ */ final class NoSpacesAfterFunctionNameFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis.', - [new CodeSample("isAnyTokenKindsFound(array_merge($this->getFunctionyTokenKinds(), [T_STRING])); + return $tokens->isAnyTokenKindsFound([T_STRING, ...$this->getFunctionyTokenKinds()]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionyTokens = $this->getFunctionyTokenKinds(); @@ -82,7 +73,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $nextNonWhiteSpace = $tokens->getNextMeaningfulToken($endParenthesisIndex); if ( null !== $nextNonWhiteSpace - && $tokens[$nextNonWhiteSpace]->equals('?') + && !$tokens[$nextNonWhiteSpace]->equals(';') && $tokens[$lastTokenIndex]->isGivenKind($languageConstructionTokens) ) { continue; @@ -125,24 +116,22 @@ private function fixFunctionCall(Tokens $tokens, int $index): void } /** - * @return array + * @return list */ private function getBraceAfterVariableKinds(): array { - static $tokens = [ + return [ ')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; - - return $tokens; } /** * Gets the token kinds which can work as function calls. * - * @return int[] Token names + * @return list Token names */ private function getFunctionyTokenKinds(): array { @@ -169,7 +158,7 @@ private function getFunctionyTokenKinds(): array /** * Gets the token kinds of actually language construction. * - * @return int[] + * @return list */ private function getLanguageConstructionTokenKinds(): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php index 7d3f6d453f..b0be871da8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoTrailingCommaInSinglelineFunctionCallFixer.php @@ -14,19 +14,18 @@ namespace PhpCsFixer\Fixer\FunctionNotation; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; -use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Tokens; -final class NoTrailingCommaInSinglelineFunctionCallFixer extends AbstractFixer +/** + * @deprecated + */ +final class NoTrailingCommaInSinglelineFunctionCallFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,68 +44,16 @@ public function getPriority(): int return 3; } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isAnyTokenKindsFound([T_STRING, T_VARIABLE, T_CLASS, T_UNSET, T_ISSET, T_LIST]); + return array_keys($this->proxyFixers); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function createProxyFixers(): array { - for ($index = \count($tokens) - 1; $index > 0; --$index) { - if (!$tokens[$index]->equals(')')) { - continue; - } - - $trailingCommaIndex = $tokens->getPrevMeaningfulToken($index); + $fixer = new NoTrailingCommaInSinglelineFixer(); + $fixer->configure(['elements' => ['arguments', 'array_destructuring']]); - if (!$tokens[$trailingCommaIndex]->equals(',')) { - continue; - } - - $callIndex = $tokens->getPrevMeaningfulToken( // get before "parenthesis open index" - $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) - ); - - if ($tokens[$callIndex]->isGivenKind([T_VARIABLE, T_CLASS, T_UNSET, T_ISSET, T_LIST])) { - $this->clearCommaIfNeeded($tokens, $callIndex, $index, $trailingCommaIndex); - - continue; - } - - if ($tokens[$callIndex]->isGivenKind(T_STRING)) { - if (!AttributeAnalyzer::isAttribute($tokens, $callIndex)) { - $this->clearCommaIfNeeded($tokens, $callIndex, $index, $trailingCommaIndex); - } - - continue; - } - - if ($tokens[$callIndex]->equalsAny([')', ']', [CT::T_DYNAMIC_VAR_BRACE_CLOSE], [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]])) { - $block = Tokens::detectBlockType($tokens[$callIndex]); - if ( - Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type'] - || Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type'] - || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type'] - || Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type'] - ) { - $this->clearCommaIfNeeded($tokens, $callIndex, $index, $trailingCommaIndex); - - // continue; implicit - } - } - } - } - - private function clearCommaIfNeeded(Tokens $tokens, int $startIndex, int $endIndex, int $commaIndex): void - { - if (!$tokens->isPartialCodeMultiline($startIndex, $endIndex)) { - $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); - } + return [$fixer]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php index a41ca00978..108901da32 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php @@ -28,9 +28,6 @@ */ final class NoUnreachableDefaultArgumentValueFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -57,25 +54,16 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionKinds = [T_FUNCTION, T_FN]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php index d5af443522..125bbab6e8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NoUselessSprintfFixer.php @@ -24,9 +24,6 @@ final class NoUselessSprintfFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -41,17 +38,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -60,16 +51,13 @@ public function isRisky(): bool /** * {@inheritdoc} * - * Must run before MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoEmptyStatementFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer. + * Must run before MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoEmptyStatementFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. */ public function getPriority(): int { return 42; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionAnalyzer = new FunctionsAnalyzer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php index a6c3f7e20c..b21a9ea531 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/NullableTypeDeclarationForDefaultNullValueFixer.php @@ -16,30 +16,43 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** - * @author HypeMC + * @author HypeMC + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * use_nullable_type_declaration?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * use_nullable_type_declaration: bool + * } */ final class NullableTypeDeclarationForDefaultNullValueFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Adds or removes `?` before type declarations for parameters with a default `null` value.', + 'Adds or removes `?` before single type declarations or `|null` at the end of union types when parameters have a default `null` value.', [ new CodeSample( " false] ), + new VersionSpecificCodeSample( + " false] + ), + new VersionSpecificCodeSample( + " false] + ), ], 'Rule is applied only in a PHP 7.1+ environment.' ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_VARIABLE) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); @@ -64,29 +92,24 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} * - * Must run before NoUnreachableDefaultArgumentValueFixer. + * Must run before NoUnreachableDefaultArgumentValueFixer, NullableTypeDeclarationFixer, OrderedTypesFixer. */ public function getPriority(): int { - return 1; + return 3; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('use_nullable_type_declaration', 'Whether to add or remove `?` before type declarations for parameters with a default `null` value.')) + (new FixerOptionBuilder('use_nullable_type_declaration', 'Whether to add or remove `?` or `|null` to parameters with a default `null` value.')) ->setAllowedTypes(['bool']) ->setDefault(true) + ->setDeprecationMessage('Behaviour will follow default one.') // @TODO remove the option on next major 4.0 ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -105,7 +128,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @param ArgumentAnalysis[] $arguments + * @param array $arguments */ private function fixFunctionParameters(Tokens $tokens, array $arguments): void { @@ -124,8 +147,8 @@ private function fixFunctionParameters(Tokens $tokens, array $arguments): void // Skip, if the parameter // - doesn't have a type declaration !$argumentInfo->hasTypeAnalysis() - // type is a union - || str_contains($argumentInfo->getTypeAnalysis()->getName(), '|') + // - has a mixed or standalone null type + || \in_array(strtolower($argumentInfo->getTypeAnalysis()->getName()), ['mixed', 'null'], true) // - a default value is not null we can continue || !$argumentInfo->hasDefault() || 'null' !== strtolower($argumentInfo->getDefault()) ) { @@ -134,7 +157,7 @@ private function fixFunctionParameters(Tokens $tokens, array $arguments): void $argumentTypeInfo = $argumentInfo->getTypeAnalysis(); - if (\PHP_VERSION_ID >= 80000 && false === $this->configuration['use_nullable_type_declaration']) { + if (\PHP_VERSION_ID >= 8_00_00 && false === $this->configuration['use_nullable_type_declaration']) { $visibility = $tokens[$tokens->getPrevMeaningfulToken($argumentTypeInfo->getStartIndex())]; if ($visibility->isGivenKind($constructorPropertyModifiers)) { @@ -142,16 +165,83 @@ private function fixFunctionParameters(Tokens $tokens, array $arguments): void } } - if (true === $this->configuration['use_nullable_type_declaration']) { - if (!$argumentTypeInfo->isNullable() && 'mixed' !== $argumentTypeInfo->getName()) { - $tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_NULLABLE_TYPE, '?'])); + $typeAnalysisName = $argumentTypeInfo->getName(); + if (str_contains($typeAnalysisName, '|') || str_contains($typeAnalysisName, '&')) { + $this->fixUnionTypeParameter($tokens, $argumentTypeInfo); + } else { + $this->fixSingleTypeParameter($tokens, $argumentTypeInfo); + } + } + } + + private function fixSingleTypeParameter(Tokens $tokens, TypeAnalysis $argumentTypeInfo): void + { + if (true === $this->configuration['use_nullable_type_declaration']) { + if (!$argumentTypeInfo->isNullable()) { + $tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_NULLABLE_TYPE, '?'])); + } + } elseif ($argumentTypeInfo->isNullable()) { + $tokens->removeTrailingWhitespace($startIndex = $argumentTypeInfo->getStartIndex()); + $tokens->clearTokenAndMergeSurroundingWhitespace($startIndex); + } + } + + private function fixUnionTypeParameter(Tokens $tokens, TypeAnalysis $argumentTypeInfo): void + { + if (true === $this->configuration['use_nullable_type_declaration']) { + if ($argumentTypeInfo->isNullable()) { + return; + } + + $typeAnalysisName = $argumentTypeInfo->getName(); + $endIndex = $argumentTypeInfo->getEndIndex(); + + if (str_contains($typeAnalysisName, '&') && !str_contains($typeAnalysisName, '|')) { + $endIndex += 2; + $tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '('])); + $tokens->insertAt($endIndex, new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')'])); + } + + $tokens->insertAt($endIndex + 1, [ + new Token([CT::T_TYPE_ALTERNATION, '|']), + new Token([T_STRING, 'null']), + ]); + } elseif ($argumentTypeInfo->isNullable()) { + $startIndex = $argumentTypeInfo->getStartIndex(); + + $index = $tokens->getNextTokenOfKind($startIndex - 1, [[T_STRING, 'null']], false); + + if ($index === $startIndex) { + $tokens->removeTrailingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $index = $tokens->getNextMeaningfulToken($index); + if ($tokens[$index]->equals([CT::T_TYPE_ALTERNATION, '|'])) { + $tokens->removeTrailingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } else { - if ($argumentTypeInfo->isNullable()) { - $tokens->removeTrailingWhitespace($argumentTypeInfo->getStartIndex()); - $tokens->clearTokenAndMergeSurroundingWhitespace($argumentTypeInfo->getStartIndex()); + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $index = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$index]->equals([CT::T_TYPE_ALTERNATION, '|'])) { + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } + + $typeAnalysisName = $argumentTypeInfo->getName(); + + if (str_contains($typeAnalysisName, '&') && 1 === substr_count($typeAnalysisName, '|')) { + $index = $tokens->getNextTokenOfKind($startIndex - 1, [[CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN]]); + $tokens->removeTrailingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + $index = $tokens->getPrevTokenOfKind($argumentTypeInfo->getEndIndex() + 1, [[CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]]); + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php index 488df0d76e..182e07836d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php @@ -16,6 +16,8 @@ use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -24,11 +26,24 @@ /** * @author Jan Gantzert + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * scalar_types?: bool, + * union_types?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * scalar_types: bool, + * union_types: bool + * } */ -final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer +final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ConfigurableFixerInterface, ExperimentalFixerInterface { + private const TYPE_CHECK_TEMPLATE = ' */ private const EXCLUDE_FUNC_NAMES = [ [T_STRING, '__clone'], @@ -39,19 +54,15 @@ final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer * @var array */ private const SKIPPED_TYPES = [ - 'mixed' => true, 'resource' => true, 'static' => true, 'void' => true, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'EXPERIMENTAL: Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', + 'Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', [ new CodeSample( ' false] ), + new CodeSample( + ' false] + ), ], null, - 'This rule is EXPERIMENTAL and [1] is not covered with backward compatibility promise. [2] `@param` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. [3] Manual actions are required if inherited signatures are not properly documented.' + 'The `@param` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. Manual actions are required if inherited signatures are not properly documented.' ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isTokenKindFound(T_FUNCTION); + return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); } /** @@ -104,13 +122,10 @@ protected function isSkippedType(string $type): bool return isset(self::SKIPPED_TYPES[$type]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; 0 < $index; --$index) { - if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + if (!$tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { continue; } @@ -126,13 +141,34 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } foreach ($this->getAnnotationsFromDocComment('param', $tokens, $docCommentIndex) as $paramTypeAnnotation) { - $typeInfo = $this->getCommonTypeFromAnnotation($paramTypeAnnotation, false); + $typesExpression = $paramTypeAnnotation->getTypeExpression(); + + if (null === $typesExpression) { + continue; + } + + $typeInfo = $this->getCommonTypeInfo($typesExpression, false); + $unionTypes = null; if (null === $typeInfo) { + $unionTypes = $this->getUnionTypes($typesExpression, false); + } + + if (null === $typeInfo && null === $unionTypes) { continue; } - [$paramType, $isNullable] = $typeInfo; + if (null !== $typeInfo) { + $paramType = $typeInfo['commonType']; + $isNullable = $typeInfo['isNullable']; + } elseif (null !== $unionTypes) { + $paramType = $unionTypes; + $isNullable = false; + } + + if (!isset($paramType, $isNullable)) { + continue; + } $startIndex = $tokens->getNextTokenOfKind($index, ['(']); $variableIndex = $this->findCorrectVariable($tokens, $startIndex, $paramTypeAnnotation); @@ -151,7 +187,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - if (!$this->isValidSyntax(sprintf('isValidSyntax(\sprintf(self::TYPE_CHECK_TEMPLATE, $paramType))) { continue; } @@ -163,6 +199,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + protected function createTokensFromRawType(string $type): Tokens + { + $typeTokens = Tokens::fromCode(\sprintf(self::TYPE_CHECK_TEMPLATE, $type)); + $typeTokens->clearRange(0, 4); + $typeTokens->clearRange(\count($typeTokens) - 6, \count($typeTokens) - 1); + $typeTokens->clearEmptyTokens(); + + return $typeTokens; + } + private function findCorrectVariable(Tokens $tokens, int $startIndex, Annotation $paramTypeAnnotation): ?int { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php index db3c812ac0..8b434ac2b3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php @@ -16,33 +16,46 @@ use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -final class PhpdocToPropertyTypeFixer extends AbstractPhpdocToTypeDeclarationFixer +/** + * @phpstan-import-type _CommonTypeInfo from AbstractPhpdocToTypeDeclarationFixer + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * scalar_types?: bool, + * union_types?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * scalar_types: bool, + * union_types: bool + * } + */ +final class PhpdocToPropertyTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ConfigurableFixerInterface, ExperimentalFixerInterface { + private const TYPE_CHECK_TEMPLATE = ' */ private array $skippedTypes = [ - 'mixed' => true, 'resource' => true, 'null' => true, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'EXPERIMENTAL: Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. Requires PHP >= 7.4.', + 'Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. Requires PHP >= 7.4.', [ - new VersionSpecificCodeSample( + new CodeSample( ' false] ), + new CodeSample( + ' false] + ), ], null, - 'This rule is EXPERIMENTAL and [1] is not covered with backward compatibility promise. [2] `@var` annotation is mandatory for the fixer to make changes, signatures of properties without it (no docblock) will not be fixed. [3] Manual actions might be required for newly typed properties that are read before initialization.' + 'The `@var` annotation is mandatory for the fixer to make changes, signatures of properties without it (no docblock) will not be fixed. Manual actions might be required for newly typed properties that are read before initialization.' ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); @@ -95,9 +114,6 @@ protected function isSkippedType(string $type): bool return isset($this->skippedTypes[$type]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; 0 < $index; --$index) { @@ -107,6 +123,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + protected function createTokensFromRawType(string $type): Tokens + { + $typeTokens = Tokens::fromCode(\sprintf(self::TYPE_CHECK_TEMPLATE, $type)); + $typeTokens->clearRange(0, 8); + $typeTokens->clearRange(\count($typeTokens) - 5, \count($typeTokens) - 1); + $typeTokens->clearEmptyTokens(); + + return $typeTokens; + } + private function fixClass(Tokens $tokens, int $index): void { $index = $tokens->getNextTokenOfKind($index, ['{']); @@ -143,12 +169,17 @@ private function fixClass(Tokens $tokens, int $index): void continue; } - [$propertyType, $isNullable] = $typeInfo; + $propertyType = $typeInfo['commonType']; + $isNullable = $typeInfo['isNullable']; if (\in_array($propertyType, ['callable', 'never', 'void'], true)) { continue; } + if (!$this->isValidSyntax(\sprintf(self::TYPE_CHECK_TEMPLATE, $propertyType))) { + continue; + } + $newTokens = array_merge( $this->createTypeDeclarationTokens($propertyType, $isNullable), [new Token([T_WHITESPACE, ' '])] @@ -195,7 +226,9 @@ private function findNextUntypedPropertiesDeclaration(Tokens $tokens, int $index /** * @param array $propertyIndices - * @param Annotation[] $annotations + * @param list $annotations + * + * @return ?_CommonTypeInfo */ private function resolveApplicableType(array $propertyIndices, array $annotations): ?array { @@ -209,18 +242,35 @@ private function resolveApplicableType(array $propertyIndices, array $annotation continue; } - $propertyName = key($propertyIndices); + $propertyName = array_key_first($propertyIndices); } if (!isset($propertyIndices[$propertyName])) { continue; } - $typeInfo = $this->getCommonTypeFromAnnotation($annotation, false); + $typesExpression = $annotation->getTypeExpression(); + + if (null === $typesExpression) { + continue; + } + + $typeInfo = $this->getCommonTypeInfo($typesExpression, false); + $unionTypes = null; + + if (null === $typeInfo) { + $unionTypes = $this->getUnionTypes($typesExpression, false); + } + + if (null === $typeInfo && null === $unionTypes) { + continue; + } + + if (null !== $unionTypes) { + $typeInfo = ['commonType' => $unionTypes, 'isNullable' => false]; + } - if (!isset($propertyTypes[$propertyName])) { - $propertyTypes[$propertyName] = []; - } elseif ($typeInfo !== $propertyTypes[$propertyName]) { + if (\array_key_exists($propertyName, $propertyTypes) && $typeInfo !== $propertyTypes[$propertyName]) { return null; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php index b11d07e7b5..960b372795 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php @@ -15,6 +15,8 @@ namespace PhpCsFixer\Fixer\FunctionNotation; use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -26,9 +28,22 @@ /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * scalar_types?: bool, + * union_types?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * scalar_types: bool, + * union_types: bool + * } */ -final class PhpdocToReturnTypeFixer extends AbstractPhpdocToTypeDeclarationFixer +final class PhpdocToReturnTypeFixer extends AbstractPhpdocToTypeDeclarationFixer implements ConfigurableFixerInterface, ExperimentalFixerInterface { + private const TYPE_CHECK_TEMPLATE = '> */ @@ -42,18 +57,14 @@ final class PhpdocToReturnTypeFixer extends AbstractPhpdocToTypeDeclarationFixer * @var array */ private array $skippedTypes = [ - 'mixed' => true, 'resource' => true, 'null' => true, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.', + 'Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature.', [ new CodeSample( ' false] ), + new CodeSample( + ' false] + ), new VersionSpecificCodeSample( 'isAnyTokenKindsFound([T_FUNCTION, T_FN]); @@ -116,7 +129,7 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} * - * Must run before FullyQualifiedStrictTypesFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, ReturnTypeDeclarationFixer. + * Must run before FullyQualifiedStrictTypesFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, ReturnToYieldFromFixer, ReturnTypeDeclarationFixer. * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int @@ -129,15 +142,8 @@ protected function isSkippedType(string $type): bool return isset($this->skippedTypes[$type]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - if (\PHP_VERSION_ID >= 80000) { - unset($this->skippedTypes['mixed']); - } - for ($index = $tokens->count() - 1; 0 < $index; --$index) { if (!$tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { continue; @@ -154,33 +160,57 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - $returnTypeAnnotation = $this->getAnnotationsFromDocComment('return', $tokens, $docCommentIndex); - if (1 !== \count($returnTypeAnnotation)) { + $returnTypeAnnotations = $this->getAnnotationsFromDocComment('return', $tokens, $docCommentIndex); + if (1 !== \count($returnTypeAnnotations)) { continue; } - $typeInfo = $this->getCommonTypeFromAnnotation(current($returnTypeAnnotation), true); + $returnTypeAnnotation = $returnTypeAnnotations[0]; - if (null === $typeInfo) { + $typesExpression = $returnTypeAnnotation->getTypeExpression(); + + if (null === $typesExpression) { continue; } - [$returnType, $isNullable] = $typeInfo; + $typeInfo = $this->getCommonTypeInfo($typesExpression, true); + $unionTypes = null; - $startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); + if (null === $typeInfo) { + $unionTypes = $this->getUnionTypes($typesExpression, true); + } - if ($this->hasReturnTypeHint($tokens, $startIndex)) { + if (null === $typeInfo && null === $unionTypes) { continue; } - if (!$this->isValidSyntax(sprintf('getPrevTokenOfKind($startIndex, [')']); + $paramsStartIndex = $tokens->getNextTokenOfKind($index, ['(']); + $paramsEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $paramsStartIndex); + + $bodyStartIndex = $tokens->getNextTokenOfKind($paramsEndIndex, ['{', ';', [T_DOUBLE_ARROW]]); + + if ($this->hasReturnTypeHint($tokens, $bodyStartIndex)) { + continue; + } + + if (!$this->isValidSyntax(\sprintf(self::TYPE_CHECK_TEMPLATE, $returnType))) { + continue; + } $tokens->insertAt( - $endFuncIndex + 1, + $paramsEndIndex + 1, array_merge( [ new Token([CT::T_TYPE_COLON, ':']), @@ -192,6 +222,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + protected function createTokensFromRawType(string $type): Tokens + { + $typeTokens = Tokens::fromCode(\sprintf(self::TYPE_CHECK_TEMPLATE, $type)); + $typeTokens->clearRange(0, 7); + $typeTokens->clearRange(\count($typeTokens) - 3, \count($typeTokens) - 1); + $typeTokens->clearEmptyTokens(); + + return $typeTokens; + } + /** * Determine whether the function already has a return type hint. * diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php index 0ff38af0d9..7538ff7ab1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/RegularCallableCallFixer.php @@ -28,9 +28,6 @@ */ final class RegularCallableCallFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -62,15 +59,13 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before NativeFunctionInvocationFixer. + * Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer. */ public function getPriority(): int { return 2; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); @@ -81,9 +76,6 @@ public function isRisky(): bool return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -110,6 +102,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + /** + * @param non-empty-array $arguments + */ private function processCall(Tokens $tokens, int $index, array $arguments): void { $firstArgIndex = $tokens->getNextMeaningfulToken( @@ -121,10 +116,17 @@ private function processCall(Tokens $tokens, int $index, array $arguments): void if ($firstArgToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { $afterFirstArgIndex = $tokens->getNextMeaningfulToken($firstArgIndex); + if (!$tokens[$afterFirstArgIndex]->equalsAny([',', ')'])) { return; // first argument is an expression like `call_user_func("foo"."bar", ...)`, not supported! } + $firstArgTokenContent = $firstArgToken->getContent(); + + if (!$this->isValidFunctionInvoke($firstArgTokenContent)) { + return; + } + $newCallTokens = Tokens::fromCode('getContent()), 1, -1).'();'); $newCallTokensSize = $newCallTokens->count(); $newCallTokens->clearAt(0); @@ -132,7 +134,13 @@ private function processCall(Tokens $tokens, int $index, array $arguments): void $newCallTokens->clearEmptyTokens(); $this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgIndex); - } elseif ($firstArgToken->isGivenKind([T_FUNCTION, T_STATIC])) { + } elseif ( + $firstArgToken->isGivenKind(T_FUNCTION) + || ( + $firstArgToken->isGivenKind(T_STATIC) + && $tokens[$tokens->getNextMeaningfulToken($firstArgIndex)]->isGivenKind(T_FUNCTION) + ) + ) { $firstArgEndIndex = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_CURLY_BRACE, $tokens->getNextTokenOfKind($firstArgIndex, ['{']) @@ -230,4 +238,19 @@ private function getTokensSubcollection(Tokens $tokens, int $indexStart, int $in return $subCollection; } + + private function isValidFunctionInvoke(string $name): bool + { + if (\strlen($name) < 3 || 'b' === $name[0] || 'B' === $name[0]) { + return false; + } + + $name = substr($name, 1, -1); + + if ($name !== trim($name)) { + return false; + } + + return true; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php index fe3015929f..1b54e2ff32 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/ReturnTypeDeclarationFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,16 +29,25 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * space_before?: 'none'|'one' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * space_before: 'none'|'one' + * } */ final class ReturnTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'There should be one or no space before colon, and one space after it in return type declarations, according to configuration.', + 'Adjust spacing around colon in return type declarations and backed enum types.', [ new CodeSample( "isTokenKindFound(CT::T_TYPE_COLON); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $oneSpaceBefore = 'one' === $this->configuration['space_before']; @@ -116,9 +120,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php index 8402582d51..3ee0d51ae8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/SingleLineThrowFixer.php @@ -29,11 +29,8 @@ final class SingleLineThrowFixer extends AbstractFixer { private const REMOVE_WHITESPACE_AFTER_TOKENS = ['[']; private const REMOVE_WHITESPACE_AROUND_TOKENS = ['(', [T_DOUBLE_COLON]]; - private const REMOVE_WHITESPACE_BEFORE_TOKENS = [')', ']', ',', ';']; + private const REMOVE_WHITESPACE_BEFORE_TOKENS = [')', ']', ',', ';']; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,9 +41,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_THROW); @@ -62,9 +56,6 @@ public function getPriority(): int return 36; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { @@ -74,11 +65,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $endCandidateIndex = $tokens->getNextMeaningfulToken($index); - while (!$tokens[$endCandidateIndex]->equalsAny([')', ']', ',', ';'])) { + while (!$tokens[$endCandidateIndex]->equalsAny([')', ']', ',', ';', [T_CLOSE_TAG]])) { $blockType = Tokens::detectBlockType($tokens[$endCandidateIndex]); if (null !== $blockType) { - if (Tokens::BLOCK_TYPE_CURLY_BRACE === $blockType['type']) { + if (Tokens::BLOCK_TYPE_CURLY_BRACE === $blockType['type'] || !$blockType['isStart']) { break; } @@ -104,7 +95,7 @@ private function trimNewLines(Tokens $tokens, int $startIndex, int $endIndex): v } elseif (str_starts_with($content, '#')) { $content = '/*'.substr($content, 1).' */'; $tokens->clearAt($index + 1); - } elseif (0 !== Preg::match('/\R/', $content)) { + } elseif (Preg::match('/\R/', $content)) { $content = Preg::replace('/\R/', ' ', $content); } @@ -117,7 +108,7 @@ private function trimNewLines(Tokens $tokens, int $startIndex, int $endIndex): v continue; } - if (0 === Preg::match('/\R/', $content)) { + if (!Preg::match('/\R/', $content)) { continue; } @@ -149,7 +140,7 @@ private function isPreviousTokenToClear(Token $token): bool static $tokens = null; if (null === $tokens) { - $tokens = array_merge(self::REMOVE_WHITESPACE_AFTER_TOKENS, self::REMOVE_WHITESPACE_AROUND_TOKENS); + $tokens = [...self::REMOVE_WHITESPACE_AFTER_TOKENS, ...self::REMOVE_WHITESPACE_AROUND_TOKENS]; } return $token->equalsAny($tokens) || $token->isObjectOperator(); @@ -160,7 +151,7 @@ private function isNextTokenToClear(Token $token): bool static $tokens = null; if (null === $tokens) { - $tokens = array_merge(self::REMOVE_WHITESPACE_AROUND_TOKENS, self::REMOVE_WHITESPACE_BEFORE_TOKENS); + $tokens = [...self::REMOVE_WHITESPACE_AROUND_TOKENS, ...self::REMOVE_WHITESPACE_BEFORE_TOKENS]; } return $token->equalsAny($tokens) || $token->isObjectOperator(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php index 211f7c946a..81763f7f6b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/StaticLambdaFixer.php @@ -25,38 +25,26 @@ final class StaticLambdaFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Lambdas not (indirect) referencing `$this` must be declared `static`.', + 'Lambdas not (indirectly) referencing `$this` must be declared `static`.', [new CodeSample("bindTo` on lambdas without referencing to `$this`.' ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $analyzer = new TokensAnalyzer($tokens); @@ -83,7 +71,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex); } else { // T_FN $lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, [[T_DOUBLE_ARROW]]); - $lambdaEndIndex = $this->findExpressionEnd($tokens, $lambdaOpenIndex); + $lambdaEndIndex = $analyzer->getLastTokenIndexOfArrowFunction($index); } if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) { @@ -103,38 +91,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - private function findExpressionEnd(Tokens $tokens, int $index): int - { - $nextIndex = $tokens->getNextMeaningfulToken($index); - - while (null !== $nextIndex) { - /** @var Token $nextToken */ - $nextToken = $tokens[$nextIndex]; - - if ($nextToken->equalsAny([',', ';', [T_CLOSE_TAG]])) { - break; - } - - /** @var null|array{isStart: bool, type: int} $blockType */ - $blockType = Tokens::detectBlockType($nextToken); - - if (null !== $blockType && $blockType['isStart']) { - $nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex); - } - - $index = $nextIndex; - $nextIndex = $tokens->getNextMeaningfulToken($index); - } - - return $index; - } - /** * Returns 'true' if there is a possible reference to '$this' within the given tokens index range. */ private function hasPossibleReferenceToThis(Tokens $tokens, int $startIndex, int $endIndex): bool { - for ($i = $startIndex; $i < $endIndex; ++$i) { + for ($i = $startIndex; $i <= $endIndex; ++$i) { if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) { return true; // directly accessing '$this' } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php index 2df90852a9..5cafd0a308 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/UseArrowFunctionsFixer.php @@ -15,10 +15,9 @@ namespace PhpCsFixer\Fixer\FunctionNotation; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -29,24 +28,20 @@ */ final class UseArrowFunctionsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Anonymous functions with one-liner return statement must use arrow functions.', [ - new VersionSpecificCodeSample( + new CodeSample( <<<'SAMPLE' -isAllTokenKindsFound([T_FUNCTION, T_RETURN]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -72,7 +61,14 @@ public function isRisky(): bool /** * {@inheritdoc} + * + * Must run before FunctionDeclarationFixer. */ + public function getPriority(): int + { + return 32; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $analyzer = new TokensAnalyzer($tokens); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php index 61d4a8ea53..150931dd40 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/FunctionNotation/VoidReturnFixer.php @@ -30,9 +30,6 @@ */ final class VoidReturnFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -58,25 +55,16 @@ public function getPriority(): int return 5; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_FUNCTION); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // These cause syntax errors. @@ -232,20 +220,30 @@ private function fixFunctionDefinition(Tokens $tokens, int $index): void * * @param int $index The index of the function token * - * @return Annotation[] + * @return list */ private function findReturnAnnotations(Tokens $tokens, int $index): array { - do { - $index = $tokens->getPrevNonWhitespace($index); - } while ($tokens[$index]->isGivenKind([ + $previousTokens = [ T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, - ])); + ]; + + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $previousTokens[] = T_ATTRIBUTE; + } + + do { + $index = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]); + } + } while ($tokens[$index]->isGivenKind($previousTokens)); if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { return []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php index 55dc6471e2..c5f5d6ae7e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php @@ -15,39 +15,128 @@ namespace PhpCsFixer\Fixer\Import; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author VeeWee + * @author Tomas Jadrny + * @author Greg Korba + * @author SpacePossum + * @author Michael Vorisek + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * import_symbols?: bool, + * leading_backslash_in_global_namespace?: bool, + * phpdoc_tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * import_symbols: bool, + * leading_backslash_in_global_namespace: bool, + * phpdoc_tags: list + * } */ -final class FullyQualifiedStrictTypesFixer extends AbstractFixer +final class FullyQualifiedStrictTypesFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private const REGEX_CLASS = '(?:\\\?+'.TypeExpression::REGEX_IDENTIFIER + .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)'; + /** - * {@inheritdoc} + * @var array{ + * const?: list, + * class?: list, + * function?: list + * }|null + */ + private ?array $discoveredSymbols; + + /** + * @var array{ + * const?: array, + * class?: array, + * function?: array + * } + */ + private array $symbolsForImport = []; + + /** + * @var array, array> */ + private array $reservedIdentifiersByLevel; + + /** @var array */ + private array $cacheUsesLast = []; + + /** @var array */ + private array $cacheUseNameByShortNameLower; + + /** @var array */ + private array $cacheUseShortNameByNameLower; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Transforms imported FQCN parameters and return types in function arguments to short version.', + 'Removes the leading part of fully qualified symbol references if a given symbol is imported or belongs to the current namespace.', [ new CodeSample( 'baz = $baz; + } + + /** + * @return \Foo\Bar\Baz + */ + public function getBaz() { + return $this->baz; + } + + public function doX(\Foo\Bar $foo, \Exception $e): \Foo\Bar\Baz { + try {} + catch (\Foo\SomeException $e) {} } } ' @@ -55,16 +144,51 @@ public function doSomething(\Foo\Bar $foo) new CodeSample( ' true] + ), + new CodeSample( + ' true] + ), + new CodeSample( + ' true] ), ] ); @@ -73,44 +197,379 @@ public function doSomething(\Foo\Bar $foo): \Foo\Bar\Baz /** * {@inheritdoc} * - * Must run before NoSuperfluousPhpdocTagsFixer. - * Must run after PhpdocToReturnTypeFixer. + * Must run before NoSuperfluousPhpdocTagsFixer, OrderedAttributesFixer, OrderedImportsFixer, OrderedInterfacesFixer, StatementIndentationFixer. + * Must run after ClassKeywordFixer, PhpUnitAttributesFixer, PhpdocToReturnTypeFixer. */ public function getPriority(): int { return 7; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isTokenKindFound(T_FUNCTION); + return $tokens->isAnyTokenKindsFound([ + CT::T_USE_TRAIT, + ...(\defined('T_ATTRIBUTE') ? [T_ATTRIBUTE] : []), // @TODO: drop condition when PHP 8.0+ is required + T_CATCH, + T_DOUBLE_COLON, + T_DOC_COMMENT, + T_EXTENDS, + T_FUNCTION, + T_IMPLEMENTS, + T_INSTANCEOF, + T_NEW, + T_VARIABLE, + ]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'leading_backslash_in_global_namespace', + 'Whether FQCN is prefixed with backslash when that FQCN is used in global namespace context.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'import_symbols', + 'Whether FQCNs should be automatically imported.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + (new FixerOptionBuilder( + 'phpdoc_tags', + 'Collection of PHPDoc annotation tags where FQCNs should be processed. As of now only simple tags with `@tag \F\Q\C\N` format are supported (no complex types).' + )) + ->setAllowedTypes(['string[]']) + ->setDefault([ + 'param', + 'phpstan-param', + 'phpstan-property', + 'phpstan-property-read', + 'phpstan-property-write', + 'phpstan-return', + 'phpstan-var', + 'property', + 'property-read', + 'property-write', + 'psalm-param', + 'psalm-property', + 'psalm-property-read', + 'psalm-property-write', + 'psalm-return', + 'psalm-var', + 'return', + 'see', + 'throws', + 'var', + ]) + ->getOption(), + ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $namespacesAnalyzer = new NamespacesAnalyzer(); $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer(); $functionsAnalyzer = new FunctionsAnalyzer(); - foreach ($namespacesAnalyzer->getDeclarations($tokens) as $namespace) { - $namespaceName = strtolower($namespace->getFullName()); + $this->symbolsForImport = []; + + foreach ($tokens->getNamespaceDeclarations() as $namespaceIndex => $namespace) { + $namespace = $tokens->getNamespaceDeclarations()[$namespaceIndex]; + + $namespaceName = $namespace->getFullName(); $uses = []; + $lastUse = null; + + foreach ($namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace, true) as $use) { + if (!$use->isClass()) { + continue; + } + $uses[ltrim($use->getFullName(), '\\')] = $use->getShortName(); + $lastUse = $use; + } + + $indexDiff = 0; + foreach (true === $this->configuration['import_symbols'] ? [true, false] : [false] as $discoverSymbolsPhase) { + $this->discoveredSymbols = $discoverSymbolsPhase ? [] : null; + + $classyKinds = [T_CLASS, T_INTERFACE, T_TRAIT]; + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $classyKinds[] = T_ENUM; + } + + $openedCurlyBrackets = 0; + $this->reservedIdentifiersByLevel = []; + + for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex() + $indexDiff; ++$index) { + $origSize = \count($tokens); + + if ($tokens[$index]->equals('{')) { + ++$openedCurlyBrackets; + } if ($tokens[$index]->equals('}')) { + unset($this->reservedIdentifiersByLevel[$openedCurlyBrackets]); + --$openedCurlyBrackets; + } elseif ($discoverSymbolsPhase && $tokens[$index]->isGivenKind($classyKinds)) { + $this->fixNextName($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_FUNCTION)) { + $this->fixFunction($functionsAnalyzer, $tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { + $this->fixExtendsImplements($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_CATCH)) { + $this->fixCatch($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + $this->fixPrevName($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind([T_INSTANCEOF, T_NEW, CT::T_USE_TRAIT])) { + $this->fixNextName($tokens, $index, $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_VARIABLE)) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_STRING)) { + $this->fixPrevName($tokens, $index, $uses, $namespaceName); + } + } elseif (\defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) { // @TODO: drop const check when PHP 8.0+ is required + $this->fixAttribute($tokens, $index, $uses, $namespaceName); + } elseif ($discoverSymbolsPhase && !\defined('T_ATTRIBUTE') && $tokens[$index]->isComment() && Preg::match('/#\[\s*('.self::REGEX_CLASS.')/', $tokens[$index]->getContent(), $matches)) { // @TODO: drop when PHP 8.0+ is required + $this->determineShortType($matches[1], $uses, $namespaceName); + } elseif ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + Preg::matchAll('/\*\h*@(?:psalm-|phpstan-)?(?:template(?:-covariant|-contravariant)?|(?:import-)?type)\h+('.TypeExpression::REGEX_IDENTIFIER.')(?!\S)/i', $tokens[$index]->getContent(), $matches); + foreach ($matches[1] as $reservedIdentifier) { + $this->reservedIdentifiersByLevel[$openedCurlyBrackets + 1][$reservedIdentifier] = true; + } + + $this->fixPhpDoc($tokens, $index, $uses, $namespaceName); + } + + $indexDiff += \count($tokens) - $origSize; + } + + $this->reservedIdentifiersByLevel = []; + + if ($discoverSymbolsPhase) { + $this->setupUsesFromDiscoveredSymbols($uses, $namespaceName); + } + } + + if ([] !== $this->symbolsForImport) { + if (null !== $lastUse) { + $atIndex = $lastUse->getEndIndex() + 1; + } elseif (0 !== $namespace->getEndIndex()) { + $atIndex = $namespace->getEndIndex() + 1; + } else { + $firstTokenIndex = $tokens->getNextMeaningfulToken($namespace->getScopeStartIndex()); + if (null !== $firstTokenIndex && $tokens[$firstTokenIndex]->isGivenKind(T_DECLARE)) { + $atIndex = $tokens->getNextTokenOfKind($firstTokenIndex, [';']) + 1; + } else { + $atIndex = $namespace->getScopeStartIndex() + 1; + } + } + + // Insert all registered FQCNs + $this->createImportProcessor()->insertImports($tokens, $this->symbolsForImport, $atIndex); + + $this->symbolsForImport = []; + } + } + } + + /** + * @param array $uses + */ + private function refreshUsesCache(array $uses): void + { + if ($this->cacheUsesLast === $uses) { + return; + } + + $this->cacheUsesLast = $uses; + $this->cacheUseNameByShortNameLower = []; + $this->cacheUseShortNameByNameLower = []; + foreach ($uses as $useLongName => $useShortName) { + $this->cacheUseNameByShortNameLower[strtolower($useShortName)] = $useLongName; + $this->cacheUseShortNameByNameLower[strtolower($useLongName)] = $useShortName; + } + } + + private function isReservedIdentifier(string $symbol): bool + { + if (str_contains($symbol, '\\')) { // optimization only + return false; + } + + if ((new TypeAnalysis($symbol))->isReservedType()) { + return true; + } + + foreach ($this->reservedIdentifiersByLevel as $reservedIdentifiers) { + if (isset($reservedIdentifiers[$symbol])) { + return true; + } + } + + return false; + } + + /** + * Resolve absolute or relative symbol to normalized FQCN. + * + * @param array $uses + */ + private function resolveSymbol(string $symbol, array $uses, string $namespaceName): string + { + if (str_starts_with($symbol, '\\')) { + return substr($symbol, 1); + } + + if ($this->isReservedIdentifier($symbol)) { + return $symbol; + } + + $this->refreshUsesCache($uses); + + $symbolArr = explode('\\', $symbol, 2); + $shortStartNameLower = strtolower($symbolArr[0]); + if (isset($this->cacheUseNameByShortNameLower[$shortStartNameLower])) { + return $this->cacheUseNameByShortNameLower[$shortStartNameLower].(isset($symbolArr[1]) ? '\\'.$symbolArr[1] : ''); + } + + return ('' !== $namespaceName ? $namespaceName.'\\' : '').$symbol; + } + + /** + * Shorten normalized FQCN as much as possible. + * + * @param array $uses + */ + private function shortenSymbol(string $fqcn, array $uses, string $namespaceName): string + { + if ($this->isReservedIdentifier($fqcn)) { + return $fqcn; + } + + $this->refreshUsesCache($uses); + + $res = null; + + // try to shorten the name using namespace + $iMin = 0; + if (str_starts_with($fqcn, $namespaceName.'\\')) { + $tmpRes = substr($fqcn, \strlen($namespaceName) + 1); + if (!isset($this->cacheUseNameByShortNameLower[strtolower(explode('\\', $tmpRes, 2)[0])]) && !$this->isReservedIdentifier($tmpRes)) { + $res = $tmpRes; + $iMin = substr_count($namespaceName, '\\') + 1; + } + } + + // try to shorten the name using uses + $tmp = $fqcn; + for ($i = substr_count($fqcn, '\\'); $i >= $iMin; --$i) { + if (isset($this->cacheUseShortNameByNameLower[strtolower($tmp)])) { + $tmpRes = $this->cacheUseShortNameByNameLower[strtolower($tmp)].substr($fqcn, \strlen($tmp)); + if (!$this->isReservedIdentifier($tmpRes)) { + $res = $tmpRes; + + break; + } + } + + if ($i > 0) { + $tmp = substr($tmp, 0, strrpos($tmp, '\\')); + } + } + + // shortening is not possible, add leading backslash if needed + if (null === $res) { + $res = $fqcn; + if ('' !== $namespaceName + || true === $this->configuration['leading_backslash_in_global_namespace'] + || isset($this->cacheUseNameByShortNameLower[strtolower(explode('\\', $res, 2)[0])]) + ) { + $res = '\\'.$res; + } + } + + return $res; + } + + /** + * @param array $uses + */ + private function setupUsesFromDiscoveredSymbols(array &$uses, string $namespaceName): void + { + foreach ($this->discoveredSymbols as $kind => $discoveredSymbols) { + $discoveredFqcnByShortNameLower = []; + + if ('' === $namespaceName) { + foreach ($discoveredSymbols as $symbol) { + if (!str_starts_with($symbol, '\\')) { + $shortStartName = explode('\\', ltrim($symbol, '\\'), 2)[0]; + $shortStartNameLower = strtolower($shortStartName); + $discoveredFqcnByShortNameLower[$shortStartNameLower] = $this->resolveSymbol($shortStartName, $uses, $namespaceName); + } + } + } + + foreach ($uses as $useLongName => $useShortName) { + $discoveredFqcnByShortNameLower[strtolower($useShortName)] = $useLongName; + } + + $useByShortNameLower = []; + foreach ($uses as $useShortName) { + $useByShortNameLower[strtolower($useShortName)] = true; + } + + uasort($discoveredSymbols, static function ($a, $b) { + $res = str_starts_with($a, '\\') <=> str_starts_with($b, '\\'); + if (0 !== $res) { + return $res; + } + + return substr_count($a, '\\') <=> substr_count($b, '\\'); + }); + foreach ($discoveredSymbols as $symbol) { + while (true) { + $shortEndNameLower = strtolower(str_contains($symbol, '\\') ? substr($symbol, strrpos($symbol, '\\') + 1) : $symbol); + if (!isset($discoveredFqcnByShortNameLower[$shortEndNameLower])) { + $shortStartNameLower = strtolower(explode('\\', ltrim($symbol, '\\'), 2)[0]); + if (str_starts_with($symbol, '\\') || ('' === $namespaceName && !isset($useByShortNameLower[$shortStartNameLower])) + || !str_contains($symbol, '\\') + ) { + $discoveredFqcnByShortNameLower[$shortEndNameLower] = $this->resolveSymbol($symbol, $uses, $namespaceName); + + break; + } + } + // else short name collision - keep unimported + + if (str_starts_with($symbol, '\\') || '' === $namespaceName || !str_contains($symbol, '\\')) { + break; + } - foreach ($namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace) as $use) { - $uses[strtolower(ltrim($use->getFullName(), '\\'))] = $use->getShortName(); + $symbol = substr($symbol, 0, strrpos($symbol, '\\')); + } } - for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) { - if ($tokens[$index]->isGivenKind(T_FUNCTION)) { - $this->fixFunction($functionsAnalyzer, $tokens, $index, $uses, $namespaceName); + foreach ($uses as $useLongName => $useShortName) { + $discoveredLongName = $discoveredFqcnByShortNameLower[strtolower($useShortName)] ?? null; + if (strtolower($discoveredLongName) === strtolower($useLongName)) { + unset($discoveredFqcnByShortNameLower[strtolower($useShortName)]); } } + + foreach ($discoveredFqcnByShortNameLower as $fqcn) { + $shortenedName = ltrim($this->shortenSymbol($fqcn, [], $namespaceName), '\\'); + if (str_contains($shortenedName, '\\')) { // prevent importing non-namespaced names in global namespace + $shortEndName = str_contains($fqcn, '\\') ? substr($fqcn, strrpos($fqcn, '\\') + 1) : $fqcn; + $uses[$fqcn] = $shortEndName; + $this->symbolsForImport[$kind][$shortEndName] = $fqcn; + } + } + + if (isset($this->symbolsForImport[$kind])) { + ksort($this->symbolsForImport[$kind], SORT_NATURAL); + } } } @@ -121,7 +580,9 @@ private function fixFunction(FunctionsAnalyzer $functionsAnalyzer, Tokens $token { $arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index); - foreach ($arguments as $argument) { + foreach ($arguments as $i => $argument) { + $argument = $functionsAnalyzer->getFunctionArguments($tokens, $index)[$i]; + if ($argument->hasTypeAnalysis()) { $this->replaceByShortType($tokens, $argument->getTypeAnalysis(), $uses, $namespaceName); } @@ -137,85 +598,314 @@ private function fixFunction(FunctionsAnalyzer $functionsAnalyzer, Tokens $token /** * @param array $uses */ - private function replaceByShortType(Tokens $tokens, TypeAnalysis $type, array $uses, string $namespaceName): void + private function fixPhpDoc(Tokens $tokens, int $index, array $uses, string $namespaceName): void { - if ($type->isReservedType()) { + $allowedTags = $this->configuration['phpdoc_tags']; + + if ([] === $allowedTags) { return; } - $typeStartIndex = $type->getStartIndex(); + $phpDoc = $tokens[$index]; + $phpDocContent = $phpDoc->getContent(); + $phpDocContentNew = Preg::replaceCallback('/([*{]\h*@)(\S+)(\h+)('.TypeExpression::REGEX_TYPES.')(?!(?!\})\S)/', function ($matches) use ($allowedTags, $uses, $namespaceName) { + if (!\in_array($matches[2], $allowedTags, true)) { + return $matches[0]; + } - if ($tokens[$typeStartIndex]->isGivenKind(CT::T_NULLABLE_TYPE)) { - $typeStartIndex = $tokens->getNextMeaningfulToken($typeStartIndex); + /** @TODO parse the complex type using TypeExpression and fix all names inside (like `list<\Foo\Bar|'a|b|c'|string>` or `\Foo\Bar[]`) */ + $unsupported = false; + + return $matches[1].$matches[2].$matches[3].implode('|', array_map(function ($v) use ($uses, $namespaceName, &$unsupported) { + if ($unsupported || !Preg::match('/^'.self::REGEX_CLASS.'$/', $v)) { + $unsupported = true; + + return $v; + } + + $shortTokens = $this->determineShortType($v, $uses, $namespaceName); + if (null === $shortTokens) { + return $v; + } + + return implode('', array_map( + static fn (Token $token) => $token->getContent(), + $shortTokens + )); + }, explode('|', $matches[4]))); + }, $phpDocContent); + + if ($phpDocContentNew !== $phpDocContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $phpDocContentNew]); } + } - $namespaceNameLength = \strlen($namespaceName); - $types = $this->getTypes($tokens, $typeStartIndex, $type->getEndIndex()); + /** + * @param array $uses + */ + private function fixExtendsImplements(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + // We handle `extends` and `implements` with similar logic, but we need to exit the loop under different conditions. + $isExtends = $tokens[$index]->equals([T_EXTENDS]); + $index = $tokens->getNextMeaningfulToken($index); + + $typeStartIndex = null; + $typeEndIndex = null; + + while (true) { + if ($tokens[$index]->equalsAny([',', '{', [T_IMPLEMENTS]])) { + if (null !== $typeStartIndex) { + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + } + $typeStartIndex = null; - foreach ($types as $typeName => [$startIndex, $endIndex]) { - if (!str_starts_with($typeName, '\\')) { - continue; // no shorter type possible + if ($tokens[$index]->equalsAny($isExtends ? [[T_IMPLEMENTS], '{'] : ['{'])) { + break; + } + } else { + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; } - $typeName = substr($typeName, 1); - $typeNameLower = strtolower($typeName); + $index = $tokens->getNextMeaningfulToken($index); + } + } + + /** + * @param array $uses + */ + private function fixCatch(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $index = $tokens->getNextMeaningfulToken($index); // '(' + $index = $tokens->getNextMeaningfulToken($index); // first part of first exception class to be caught + + $typeStartIndex = null; + $typeEndIndex = null; - if (isset($uses[$typeNameLower])) { - // if the type without leading "\" equals any of the full "uses" long names, it can be replaced with the short one - $tokens->overrideRange($startIndex, $endIndex, $this->namespacedStringToTokens($uses[$typeNameLower])); - } elseif ('' === $namespaceName) { - // if we are in the global namespace and the type is not imported the leading '\' can be removed (TODO nice config candidate) - foreach ($uses as $useShortName) { - if (strtolower($useShortName) === $typeNameLower) { - continue 2; - } + while (true) { + if ($tokens[$index]->equalsAny([')', [T_VARIABLE], [CT::T_TYPE_ALTERNATION]])) { + if (null === $typeStartIndex) { + break; } - $tokens->overrideRange($startIndex, $endIndex, $this->namespacedStringToTokens($typeName)); - } elseif ($typeNameLower !== $namespaceName && str_starts_with($typeNameLower, $namespaceName)) { - // if the type starts with namespace and the type is not the same as the namespace it can be shortened - $typeNameShort = substr($typeName, $namespaceNameLength + 1); - $tokens->overrideRange($startIndex, $endIndex, $this->namespacedStringToTokens($typeNameShort)); + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + $typeStartIndex = null; + + if ($tokens[$index]->equals(')')) { + break; + } + } else { + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; } + + $index = $tokens->getNextMeaningfulToken($index); } } - private function getTypes(Tokens $tokens, int $index, int $endIndex): iterable + /** + * @param array $uses + */ + private function fixAttribute(Tokens $tokens, int $index, array $uses, string $namespaceName): void { - $index = $typeStartIndex = $typeEndIndex = $tokens->getNextMeaningfulToken($index - 1); - $type = $tokens[$index]->getContent(); + $attributeAnalysis = AttributeAnalyzer::collectOne($tokens, $index); + + foreach ($attributeAnalysis->getAttributes() as $attribute) { + $index = $attribute['start']; + while ($tokens[$index]->equalsAny([[T_STRING], [T_NS_SEPARATOR]])) { + $index = $tokens->getPrevMeaningfulToken($index); + } + $this->fixNextName($tokens, $index, $uses, $namespaceName); + } + } + + /** + * @param array $uses + */ + private function fixPrevName(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $typeStartIndex = null; + $typeEndIndex = null; + + while (true) { + $index = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$index]->isObjectOperator()) { + break; + } + + if ($tokens[$index]->equalsAny([[T_STRING], [T_NS_SEPARATOR]])) { + $typeStartIndex = $index; + if (null === $typeEndIndex) { + $typeEndIndex = $index; + } + } else { + if (null !== $typeEndIndex) { + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + } + + break; + } + } + } + + /** + * @param array $uses + */ + private function fixNextName(Tokens $tokens, int $index, array $uses, string $namespaceName): void + { + $typeStartIndex = null; + $typeEndIndex = null; while (true) { $index = $tokens->getNextMeaningfulToken($index); - if ($tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION])) { - yield $type => [$typeStartIndex, $typeEndIndex]; + if ($tokens[$index]->equalsAny([[T_STRING], [T_NS_SEPARATOR]])) { + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } + $typeEndIndex = $index; + } else { + if (null !== $typeStartIndex) { + $index += $this->shortenClassIfPossible($tokens, $typeStartIndex, $typeEndIndex, $uses, $namespaceName); + } + + break; + } + } + } + + /** + * @param array $uses + */ + private function shortenClassIfPossible(Tokens $tokens, int $typeStartIndex, int $typeEndIndex, array $uses, string $namespaceName): int + { + $content = $tokens->generatePartialCode($typeStartIndex, $typeEndIndex); + $newTokens = $this->determineShortType($content, $uses, $namespaceName); + if (null === $newTokens) { + return 0; + } + + $tokens->overrideRange($typeStartIndex, $typeEndIndex, $newTokens); - $index = $typeStartIndex = $typeEndIndex = $tokens->getNextMeaningfulToken($index); - $type = $tokens[$index]->getContent(); + return \count($newTokens) - ($typeEndIndex - $typeStartIndex) - 1; + } + + /** + * @param array $uses + */ + private function replaceByShortType(Tokens $tokens, TypeAnalysis $type, array $uses, string $namespaceName): void + { + $typeStartIndex = $type->getStartIndex(); + + if ($tokens[$typeStartIndex]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $typeStartIndex = $tokens->getNextMeaningfulToken($typeStartIndex); + } + + $types = $this->getTypes($tokens, $typeStartIndex, $type->getEndIndex()); + + foreach ($types as [$startIndex, $endIndex]) { + $content = $tokens->generatePartialCode($startIndex, $endIndex); + $newTokens = $this->determineShortType($content, $uses, $namespaceName); + if (null !== $newTokens) { + $tokens->overrideRange($startIndex, $endIndex, $newTokens); + } + } + } + + /** + * Determines short type based on FQCN, current namespace and imports (`use` declarations). + * + * @param array $uses + * + * @return null|list + */ + private function determineShortType(string $typeName, array $uses, string $namespaceName): ?array + { + if (null !== $this->discoveredSymbols) { + if (!$this->isReservedIdentifier($typeName)) { + $this->discoveredSymbols['class'][] = $typeName; + } + + return null; + } + + $fqcn = $this->resolveSymbol($typeName, $uses, $namespaceName); + $shortenedType = $this->shortenSymbol($fqcn, $uses, $namespaceName); + if ($shortenedType === $typeName) { + return null; + } + + return $this->namespacedStringToTokens($shortenedType); + } + + /** + * @return iterable + */ + private function getTypes(Tokens $tokens, int $index, int $endIndex): iterable + { + $skipNextYield = false; + $typeStartIndex = $typeEndIndex = null; + while (true) { + if ($tokens[$index]->isGivenKind(CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN)) { + $index = $tokens->getNextMeaningfulToken($index); + $typeStartIndex = $typeEndIndex = null; continue; } - if ($index > $endIndex || !$tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { - yield $type => [$typeStartIndex, $typeEndIndex]; + if ( + $tokens[$index]->isGivenKind([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE]) + || $index > $endIndex + ) { + if (!$skipNextYield && null !== $typeStartIndex) { + $origCount = \count($tokens); - break; + yield [$typeStartIndex, $typeEndIndex]; + + $endIndex += \count($tokens) - $origCount; + + // type tokens were possibly updated, restart type match + $skipNextYield = true; + $index = $typeEndIndex = $typeStartIndex; + } else { + $skipNextYield = false; + $index = $tokens->getNextMeaningfulToken($index); + $typeStartIndex = $typeEndIndex = null; + } + + if ($index > $endIndex) { + break; + } + + continue; } + if (null === $typeStartIndex) { + $typeStartIndex = $index; + } $typeEndIndex = $index; - $type .= $tokens[$index]->getContent(); + + $index = $tokens->getNextMeaningfulToken($index); } } /** - * @return Token[] + * @return list */ private function namespacedStringToTokens(string $input): array { $tokens = []; - $parts = explode('\\', $input); + if (str_starts_with($input, '\\')) { + $tokens[] = new Token([T_NS_SEPARATOR, '\\']); + $input = substr($input, 1); + } + + $parts = explode('\\', $input); foreach ($parts as $index => $part) { $tokens[] = new Token([T_STRING, $part]); @@ -226,4 +916,13 @@ private function namespacedStringToTokens(string $input): array return $tokens; } + + /** + * We need to create import processor dynamically (not in costructor), because actual whitespace configuration + * is set later, not when fixer's instance is created. + */ + private function createImportProcessor(): ImportProcessor + { + return new ImportProcessor($this->whitespacesConfig); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php index 183e26cb3b..56f7704f52 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GlobalNamespaceImportFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -29,21 +30,43 @@ use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * @author Gregor Harlan + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * import_classes?: bool|null, + * import_constants?: bool|null, + * import_functions?: bool|null + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * import_classes: bool|null, + * import_constants: bool|null, + * import_functions: bool|null + * } */ final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private ImportProcessor $importProcessor; + + public function __construct() + { + parent::__construct(); + + $this->importProcessor = new ImportProcessor($this->whitespacesConfig); + } + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -94,7 +117,7 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before NoUnusedImportsFixer, OrderedImportsFixer. + * Must run before NoUnusedImportsFixer, OrderedImportsFixer, StatementIndentationFixer. * Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer. */ public function getPriority(): int @@ -102,9 +125,6 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_DOC_COMMENT, T_NS_SEPARATOR, T_USE]) @@ -113,12 +133,9 @@ public function isCandidate(Tokens $tokens): bool && $tokens->isMonolithicPhp(); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $namespaceAnalyses = (new NamespacesAnalyzer())->getDeclarations($tokens); + $namespaceAnalyses = $tokens->getNamespaceDeclarations(); if (1 !== \count($namespaceAnalyses) || $namespaceAnalyses[0]->isGlobalNamespace()) { return; @@ -146,10 +163,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $this->fullyQualifyClasses($tokens, $useDeclarations); } - $newImports = array_filter($newImports); - if (\count($newImports) > 0) { - $this->insertImports($tokens, $newImports, $useDeclarations); + if (\count($useDeclarations) > 0) { + $useDeclaration = end($useDeclarations); + $atIndex = $useDeclaration->getEndIndex() + 1; + } else { + $namespace = $tokens->getNamespaceDeclarations()[0]; + $atIndex = $namespace->getEndIndex() + 1; + } + + $this->importProcessor->insertImports($tokens, $newImports, $atIndex); } } @@ -158,27 +181,27 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn return new FixerConfigurationResolver([ (new FixerOptionBuilder('import_constants', 'Whether to import, not import or ignore global constants.')) ->setDefault(null) - ->setAllowedValues([true, false, null]) + ->setAllowedTypes(['null', 'bool']) ->getOption(), (new FixerOptionBuilder('import_functions', 'Whether to import, not import or ignore global functions.')) ->setDefault(null) - ->setAllowedValues([true, false, null]) + ->setAllowedTypes(['null', 'bool']) ->getOption(), (new FixerOptionBuilder('import_classes', 'Whether to import, not import or ignore global classes.')) ->setDefault(true) - ->setAllowedValues([true, false, null]) + ->setAllowedTypes(['null', 'bool']) ->getOption(), ]); } /** - * @param NamespaceUseAnalysis[] $useDeclarations + * @param list $useDeclarations + * + * @return array */ private function importConstants(Tokens $tokens, array $useDeclarations): array { - [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration): bool { - return $declaration->isConstant(); - }, true); + [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isConstant(), true); // find namespaced const declarations (`const FOO = 1`) // and add them to the not importable names (already used) @@ -243,13 +266,13 @@ private function importConstants(Tokens $tokens, array $useDeclarations): array } /** - * @param NamespaceUseAnalysis[] $useDeclarations + * @param list $useDeclarations + * + * @return array */ private function importFunctions(Tokens $tokens, array $useDeclarations): array { - [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration): bool { - return $declaration->isFunction(); - }, false); + [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isFunction(), false); // find function declarations // and add them to the not importable names (already used) @@ -293,15 +316,15 @@ private function importFunctions(Tokens $tokens, array $useDeclarations): array } /** - * @param NamespaceUseAnalysis[] $useDeclarations + * @param list $useDeclarations + * + * @return array */ private function importClasses(Tokens $tokens, array $useDeclarations): array { - [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration): bool { - return $declaration->isClass(); - }, false); + [$global, $other] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isClass(), false); - /** @var DocBlock[] $docBlocks */ + /** @var array $docBlocks */ $docBlocks = []; // find class declarations and class usages in docblocks @@ -401,21 +424,24 @@ private function importClasses(Tokens $tokens, array $useDeclarations): array } } - return $imports + $this->prepareImports($tokens, $indices, $global, $other, false); + return array_merge($imports, $this->prepareImports($tokens, $indices, $global, $other, false)); } /** * Removes the leading slash at the given indices (when the name is not already used). * - * @param int[] $indices + * @param list $indices + * @param array $global + * @param array $other * - * @return array array keys contain the names that must be imported + * @return array array keys contain the names that must be imported */ private function prepareImports(Tokens $tokens, array $indices, array $global, array $other, bool $caseSensitive): array { $imports = []; foreach ($indices as $index) { + /** @var class-string $name */ $name = $tokens[$index]->getContent(); $checkName = $caseSensitive ? $name : strtolower($name); @@ -436,50 +462,7 @@ private function prepareImports(Tokens $tokens, array $indices, array $global, a } /** - * @param NamespaceUseAnalysis[] $useDeclarations - */ - private function insertImports(Tokens $tokens, array $imports, array $useDeclarations): void - { - if (\count($useDeclarations) > 0) { - $useDeclaration = end($useDeclarations); - $index = $useDeclaration->getEndIndex() + 1; - } else { - $namespace = (new NamespacesAnalyzer())->getDeclarations($tokens)[0]; - $index = $namespace->getEndIndex() + 1; - } - - $lineEnding = $this->whitespacesConfig->getLineEnding(); - - if (!$tokens[$index]->isWhitespace() || !str_contains($tokens[$index]->getContent(), "\n")) { - $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding])); - } - - foreach ($imports as $type => $typeImports) { - foreach ($typeImports as $name) { - $items = [ - new Token([T_WHITESPACE, $lineEnding]), - new Token([T_USE, 'use']), - new Token([T_WHITESPACE, ' ']), - ]; - - if ('const' === $type) { - $items[] = new Token([CT::T_CONST_IMPORT, 'const']); - $items[] = new Token([T_WHITESPACE, ' ']); - } elseif ('function' === $type) { - $items[] = new Token([CT::T_FUNCTION_IMPORT, 'function']); - $items[] = new Token([T_WHITESPACE, ' ']); - } - - $items[] = new Token([T_STRING, $name]); - $items[] = new Token(';'); - - $tokens->insertAt($index, $items); - } - } - } - - /** - * @param NamespaceUseAnalysis[] $useDeclarations + * @param list $useDeclarations */ private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): void { @@ -487,11 +470,9 @@ private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): return; } - [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration): bool { - return $declaration->isConstant() && !$declaration->isAliased(); - }, true); + [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isConstant() && !$declaration->isAliased(), true); - if (!$global) { + if ([] === $global) { return; } @@ -521,7 +502,7 @@ private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): } /** - * @param NamespaceUseAnalysis[] $useDeclarations + * @param list $useDeclarations */ private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): void { @@ -529,11 +510,9 @@ private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): return; } - [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration): bool { - return $declaration->isFunction() && !$declaration->isAliased(); - }, false); + [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isFunction() && !$declaration->isAliased(), false); - if (!$global) { + if ([] === $global) { return; } @@ -563,7 +542,7 @@ private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): } /** - * @param NamespaceUseAnalysis[] $useDeclarations + * @param list $useDeclarations */ private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): void { @@ -571,11 +550,9 @@ private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): vo return; } - [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration): bool { - return $declaration->isClass() && !$declaration->isAliased(); - }, false); + [$global] = $this->filterUseDeclarations($useDeclarations, static fn (NamespaceUseAnalysis $declaration): bool => $declaration->isClass() && !$declaration->isAliased(), false); - if (!$global) { + if ([] === $global) { return; } @@ -623,7 +600,9 @@ private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): vo } /** - * @param NamespaceUseAnalysis[] $declarations + * @param list $declarations + * + * @return array{0: array, 1: array} */ private function filterUseDeclarations(array $declarations, callable $callback, bool $caseSensitive): array { @@ -652,6 +631,9 @@ private function filterUseDeclarations(array $declarations, callable $callback, return [$global, $other]; } + /** + * @return iterable + */ private function findFunctionDeclarations(Tokens $tokens, int $start, int $end): iterable { for ($index = $start; $index <= $end; ++$index) { @@ -718,7 +700,7 @@ private function traverseDocBlockTypes(DocBlock $doc, callable $callback): bool foreach ($types as $i => $fullType) { $newFullType = $fullType; - Preg::matchAll('/[\\\\\w]+/', $fullType, $matches, PREG_OFFSET_CAPTURE); + Preg::matchAll('/[\\\\\w]+(?![\\\\\w:])/', $fullType, $matches, PREG_OFFSET_CAPTURE); foreach (array_reverse($matches[0]) as [$type, $offset]) { $newType = $callback($type); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php index d6e4351d52..df9af1f09f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/GroupImportFixer.php @@ -15,6 +15,11 @@ namespace PhpCsFixer\Fixer\Import; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -23,15 +28,36 @@ use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Utils; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Volodymyr Kupriienko + * @author Greg Korba + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * group_types?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * group_types: list + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ -final class GroupImportFixer extends AbstractFixer +final class GroupImportFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** @internal */ + public const GROUP_CLASSY = 'classy'; + + /** @internal */ + public const GROUP_CONSTANTS = 'constants'; + + /** @internal */ + public const GROUP_FUNCTIONS = 'functions'; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -40,39 +66,93 @@ public function getDefinition(): FixerDefinitionInterface new CodeSample( " [self::GROUP_CLASSY]] + ), ] ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_USE); } - /** - * {@inheritdoc} - */ + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $allowedTypes = [self::GROUP_CLASSY, self::GROUP_FUNCTIONS, self::GROUP_CONSTANTS]; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('group_types', 'Defines the order of import types.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([static function (array $types) use ($allowedTypes): bool { + foreach ($types as $type) { + if (!\in_array($type, $allowedTypes, true)) { + throw new InvalidOptionsException( + \sprintf( + 'Invalid group type: %s, allowed types: %s.', + $type, + Utils::naturalLanguageJoin($allowedTypes) + ) + ); + } + } + + return true; + }]) + ->setDefault($allowedTypes) + ->getOption(), + ]); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $useWithSameNamespaces = $this->getSameNamespaces($tokens); + $useWithSameNamespaces = $this->getSameNamespacesByType($tokens); if ([] === $useWithSameNamespaces) { return; } - $this->removeSingleUseStatements($useWithSameNamespaces, $tokens); - $this->addGroupUseStatements($useWithSameNamespaces, $tokens); + $typeMap = [ + NamespaceUseAnalysis::TYPE_CLASS => self::GROUP_CLASSY, + NamespaceUseAnalysis::TYPE_FUNCTION => self::GROUP_FUNCTIONS, + NamespaceUseAnalysis::TYPE_CONSTANT => self::GROUP_CONSTANTS, + ]; + + // As a first step we need to remove all the use statements for the enabled import types. + // We can't add new group imports yet, because we need to operate on previously determined token indices for all types. + foreach ($useWithSameNamespaces as $type => $uses) { + if (!\in_array($typeMap[$type], $this->configuration['group_types'], true)) { + continue; + } + + $this->removeSingleUseStatements($uses, $tokens); + } + + foreach ($useWithSameNamespaces as $type => $uses) { + if (!\in_array($typeMap[$type], $this->configuration['group_types'], true)) { + continue; + } + + $this->addGroupUseStatements($uses, $tokens); + } } /** * Gets namespace use analyzers with same namespaces. * - * @return NamespaceUseAnalysis[] + * @return array> */ - private function getSameNamespaces(Tokens $tokens): array + private function getSameNamespacesByType(Tokens $tokens): array { $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); @@ -81,15 +161,11 @@ private function getSameNamespaces(Tokens $tokens): array } $allNamespaceAndType = array_map( - function (NamespaceUseAnalysis $useDeclaration): string { - return $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType(); - }, + fn (NamespaceUseAnalysis $useDeclaration): string => $this->getNamespaceNameWithSlash($useDeclaration).$useDeclaration->getType(), $useDeclarations ); - $sameNamespaces = array_filter(array_count_values($allNamespaceAndType), static function (int $count): bool { - return $count > 1; - }); + $sameNamespaces = array_filter(array_count_values($allNamespaceAndType), static fn (int $count): bool => $count > 1); $sameNamespaces = array_keys($sameNamespaces); $sameNamespaceAnalysis = array_filter($useDeclarations, function (NamespaceUseAnalysis $useDeclaration) use ($sameNamespaces): bool { @@ -102,14 +178,23 @@ function (NamespaceUseAnalysis $useDeclaration): string { $namespaceA = $this->getNamespaceNameWithSlash($a); $namespaceB = $this->getNamespaceNameWithSlash($b); - return \strlen($namespaceA) - \strlen($namespaceB) ?: strcmp($a->getFullName(), $b->getFullName()); + $namespaceDifference = \strlen($namespaceA) <=> \strlen($namespaceB); + + return 0 !== $namespaceDifference ? $namespaceDifference : $a->getFullName() <=> $b->getFullName(); }); - return $sameNamespaceAnalysis; + $sameNamespaceAnalysisByType = []; + foreach ($sameNamespaceAnalysis as $analysis) { + $sameNamespaceAnalysisByType[$analysis->getType()][] = $analysis; + } + + ksort($sameNamespaceAnalysisByType); + + return $sameNamespaceAnalysisByType; } /** - * @param NamespaceUseAnalysis[] $statements + * @param list $statements */ private function removeSingleUseStatements(array $statements, Tokens $tokens): void { @@ -140,12 +225,21 @@ private function removeSingleUseStatements(array $statements, Tokens $tokens): v } /** - * @param NamespaceUseAnalysis[] $statements + * @param list $statements */ private function addGroupUseStatements(array $statements, Tokens $tokens): void { $currentUseDeclaration = null; - $insertIndex = \array_slice($statements, -1)[0]->getEndIndex() + 1; + $insertIndex = $statements[0]->getStartIndex(); + + // If group import was inserted in place of removed imports, it may have more tokens than before, + // and indices stored in imports of another type can be out-of-sync, and can point in the middle of group import. + // Let's move the pointer to the closest empty token (erased single import). + if (null !== $tokens[$insertIndex]->getId() || '' !== $tokens[$insertIndex]->getContent()) { + do { + ++$insertIndex; + } while (null !== $tokens[$insertIndex]->getId() || '' !== $tokens[$insertIndex]->getContent()); + } foreach ($statements as $index => $useDeclaration) { if ($this->areDeclarationsDifferent($currentUseDeclaration, $useDeclaration)) { @@ -154,7 +248,7 @@ private function addGroupUseStatements(array $statements, Tokens $tokens): void $tokens, $insertIndex, $useDeclaration, - $this->getNamespaceNameWithSlash($currentUseDeclaration) + rtrim($this->getNamespaceNameWithSlash($currentUseDeclaration), '\\') ); } else { $newTokens = [ @@ -208,7 +302,7 @@ private function insertToGroupUseWithAlias(Tokens $tokens, int $insertIndex, Nam $tokens->insertAt($insertIndex, $newTokens); - return \count($newTokens) + 1; + return \count($newTokens); } /** @@ -236,7 +330,7 @@ private function createNewGroup(Tokens $tokens, int $insertIndex, NamespaceUseAn $newTokens[] = new Token([T_WHITESPACE, ' ']); } - $namespaceParts = array_filter(explode('\\', $currentNamespace)); + $namespaceParts = explode('\\', $currentNamespace); foreach ($namespaceParts as $part) { $newTokens[] = new Token([T_STRING, $part]); @@ -252,15 +346,14 @@ private function createNewGroup(Tokens $tokens, int $insertIndex, NamespaceUseAn $insertIndex += $newTokensCount; if ($useDeclaration->isAliased()) { - $inserted = $this->insertToGroupUseWithAlias($tokens, $insertIndex + 1, $useDeclaration); + $inserted = $this->insertToGroupUseWithAlias($tokens, $insertIndex + 1, $useDeclaration) + 1; $insertedTokens += $inserted; $insertIndex += $inserted; } $tokens->insertAt($insertIndex, new Token([T_STRING, $useDeclaration->getShortName()])); - ++$insertedTokens; - return $insertedTokens; + return ++$insertedTokens; } /** diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php index a1bcc09a71..b9120518a3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoLeadingImportSlashFixer.php @@ -28,9 +28,6 @@ */ final class NoLeadingImportSlashFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -50,17 +47,11 @@ public function getPriority(): int return -20; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_USE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php index 56a7d9597a..2d1532c90c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnneededImportAliasFixer.php @@ -23,9 +23,6 @@ final class NoUnneededImportAliasFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -34,9 +31,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_USE, T_AS]); @@ -52,9 +46,6 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php index 9033db7f17..379396d961 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/NoUnusedImportsFixer.php @@ -22,7 +22,6 @@ use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis; use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; @@ -34,9 +33,6 @@ */ final class NoUnusedImportsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -56,26 +52,20 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_USE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens, true); if (0 === \count($useDeclarations)) { return; } - foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { + foreach ($tokens->getNamespaceDeclarations() as $namespace) { $currentNamespaceUseDeclarations = []; $currentNamespaceUseDeclarationIndices = []; @@ -141,10 +131,6 @@ private function isImportUsed(Tokens $tokens, NamespaceAnalysis $namespace, Name continue; } - if ($inAttribute) { - return true; - } - $prevMeaningfulToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevMeaningfulToken->isGivenKind(T_NAMESPACE)) { @@ -160,6 +146,10 @@ private function isImportUsed(Tokens $tokens, NamespaceAnalysis $namespace, Name continue; } + if ($inAttribute) { + return true; + } + $nextMeaningfulIndex = $tokens->getNextMeaningfulToken($index); if ($gotoLabelAnalyzer->belongsToGoToLabel($tokens, $nextMeaningfulIndex)) { @@ -185,7 +175,7 @@ private function isImportUsed(Tokens $tokens, NamespaceAnalysis $namespace, Name if ($token->isComment() && Preg::match( - '/(?getShortName().'(?![[:alnum:]])/i', + '/(?getShortName().'(?![[:alnum:]])/i', $token->getContent() ) ) { @@ -196,15 +186,23 @@ private function isImportUsed(Tokens $tokens, NamespaceAnalysis $namespace, Name return false; } - private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void - { - for ($index = $useDeclaration->getEndIndex() - 1; $index >= $useDeclaration->getStartIndex(); --$index) { + private function removeUseDeclaration( + Tokens $tokens, + NamespaceUseAnalysis $useDeclaration, + bool $forceCompleteRemoval = false + ): void { + [$start, $end] = ($useDeclaration->isInMulti() && !$forceCompleteRemoval) + ? [$useDeclaration->getChunkStartIndex(), $useDeclaration->getChunkEndIndex()] + : [$useDeclaration->getStartIndex(), $useDeclaration->getEndIndex()]; + $loopStartIndex = $useDeclaration->isInMulti() || $forceCompleteRemoval ? $end : $end - 1; + + for ($index = $loopStartIndex; $index >= $start; --$index) { if ($tokens[$index]->isComment()) { continue; } if (!$tokens[$index]->isWhitespace() || !str_contains($tokens[$index]->getContent(), "\n")) { - $tokens->clearTokenAndMergeSurroundingWhitespace($index); + $tokens->clearAt($index); continue; } @@ -220,12 +218,109 @@ private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useD } } + // For multi-use import statements the tokens containing FQN were already removed in the loop above. + // We need to clean up tokens around the ex-chunk to keep the correct syntax and achieve proper formatting. + if (!$forceCompleteRemoval && $useDeclaration->isInMulti()) { + $this->cleanUpAfterImportChunkRemoval($tokens, $useDeclaration); + + return; + } + if ($tokens[$useDeclaration->getEndIndex()]->equals(';')) { // do not remove `? >` $tokens->clearAt($useDeclaration->getEndIndex()); } - // remove white space above and below where the `use` statement was + $this->cleanUpSurroundingNewLines($tokens, $useDeclaration); + } + + /** + * @param list $useDeclarations + */ + private function removeUsesInSameNamespace(Tokens $tokens, array $useDeclarations, NamespaceAnalysis $namespaceDeclaration): void + { + $namespace = $namespaceDeclaration->getFullName(); + $nsLength = \strlen($namespace.'\\'); + + foreach ($useDeclarations as $useDeclaration) { + if ($useDeclaration->isAliased()) { + continue; + } + + $useDeclarationFullName = ltrim($useDeclaration->getFullName(), '\\'); + + if (!str_starts_with($useDeclarationFullName, $namespace.'\\')) { + continue; + } + + $partName = substr($useDeclarationFullName, $nsLength); + + if (!str_contains($partName, '\\')) { + $this->removeUseDeclaration($tokens, $useDeclaration); + } + } + } + + private function cleanUpAfterImportChunkRemoval(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void + { + $beforeChunkIndex = $tokens->getPrevMeaningfulToken($useDeclaration->getChunkStartIndex()); + $afterChunkIndex = $tokens->getNextMeaningfulToken($useDeclaration->getChunkEndIndex()); + $hasNonEmptyTokenBefore = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $afterChunkIndex, + -1 + ); + $hasNonEmptyTokenAfter = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $afterChunkIndex, + 1 + ); + // We don't want to merge consequent new lines with indentation (leading to e.g. `\n \n `), + // so it's safe to merge whitespace only if there is any non-empty token before or after the chunk. + $mergingSurroundingWhitespaceIsSafe = $hasNonEmptyTokenBefore[1] || $hasNonEmptyTokenAfter[1]; + $clearToken = static function (int $index) use ($tokens, $mergingSurroundingWhitespaceIsSafe): void { + if ($mergingSurroundingWhitespaceIsSafe) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens->clearAt($index); + } + }; + + if ($tokens[$afterChunkIndex]->equals(',')) { + $clearToken($afterChunkIndex); + } elseif ($tokens[$beforeChunkIndex]->equals(',')) { + $clearToken($beforeChunkIndex); + } + + // Ensure there's a single space where applicable, otherwise no space (before comma, before closing brace) + for ($index = $beforeChunkIndex; $index <= $afterChunkIndex; ++$index) { + if (null === $tokens[$index]->getId() || !$tokens[$index]->isWhitespace(' ')) { + continue; + } + + $nextTokenIndex = $tokens->getNextMeaningfulToken($index); + if ( + $tokens[$nextTokenIndex]->equals(',') + || $tokens[$nextTokenIndex]->equals(';') + || $tokens[$nextTokenIndex]->isGivenKind([CT::T_GROUP_IMPORT_BRACE_CLOSE]) + ) { + $tokens->clearAt($index); + } else { + $tokens[$index] = new Token([T_WHITESPACE, ' ']); + } + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevTokenIndex]->isGivenKind([CT::T_GROUP_IMPORT_BRACE_OPEN])) { + $tokens->clearAt($index); + } + } + + $this->removeLineIfEmpty($tokens, $useDeclaration); + $this->removeImportStatementIfEmpty($tokens, $useDeclaration); + } + + private function cleanUpSurroundingNewLines(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void + { $prevIndex = $useDeclaration->getStartIndex() - 1; $prevToken = $tokens[$prevIndex]; @@ -271,27 +366,90 @@ private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useD } } - private function removeUsesInSameNamespace(Tokens $tokens, array $useDeclarations, NamespaceAnalysis $namespaceDeclaration): void + private function removeImportStatementIfEmpty(Tokens $tokens, NamespaceUseAnalysis $useDeclaration): void { - $namespace = $namespaceDeclaration->getFullName(); - $nsLength = \strlen($namespace.'\\'); + // First we look for empty groups where all chunks were removed (`use Foo\{};`). + // We're only interested in ending brace if its index is between start and end of the import statement. + $endingBraceIndex = $tokens->getPrevTokenOfKind( + $useDeclaration->getEndIndex(), + [[CT::T_GROUP_IMPORT_BRACE_CLOSE]] + ); - foreach ($useDeclarations as $useDeclaration) { - if ($useDeclaration->isAliased()) { - continue; + if ($endingBraceIndex > $useDeclaration->getStartIndex()) { + $openingBraceIndex = $tokens->getPrevMeaningfulToken($endingBraceIndex); + + if ($tokens[$openingBraceIndex]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $this->removeUseDeclaration($tokens, $useDeclaration, true); } + } - $useDeclarationFullName = ltrim($useDeclaration->getFullName(), '\\'); + // Second we look for empty groups where all comma-separated chunks were removed (`use;`). + $beforeSemicolonIndex = $tokens->getPrevMeaningfulToken($useDeclaration->getEndIndex()); + if ( + $tokens[$beforeSemicolonIndex]->isGivenKind([T_USE]) + || \in_array($tokens[$beforeSemicolonIndex]->getContent(), ['function', 'const'], true) + ) { + $this->removeUseDeclaration($tokens, $useDeclaration, true); + } + } - if (!str_starts_with($useDeclarationFullName, $namespace.'\\')) { + private function removeLineIfEmpty(Tokens $tokens, NamespaceUseAnalysis $useAnalysis): void + { + if (!$useAnalysis->isInMulti()) { + return; + } + + $hasNonEmptyTokenBefore = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $useAnalysis->getChunkStartIndex(), + -1 + ); + $hasNonEmptyTokenAfter = $this->scanForNonEmptyTokensUntilNewLineFound( + $tokens, + $useAnalysis->getChunkEndIndex(), + 1 + ); + + if ( + \is_int($hasNonEmptyTokenBefore[0]) + && !$hasNonEmptyTokenBefore[1] + && \is_int($hasNonEmptyTokenAfter[0]) + && !$hasNonEmptyTokenAfter[1] + ) { + $tokens->clearRange($hasNonEmptyTokenBefore[0], $hasNonEmptyTokenAfter[0] - 1); + } + } + + /** + * Returns tuple with the index of first token with whitespace containing new line char + * and a flag if any non-empty token was found along the way. + * + * @param -1|1 $direction + * + * @return array{0: null|int, 1: bool} + */ + private function scanForNonEmptyTokensUntilNewLineFound(Tokens $tokens, int $index, int $direction): array + { + $hasNonEmptyToken = false; + $newLineTokenIndex = null; + + // Iterate until we find new line OR we get out of $tokens bounds (next sibling index is `null`). + while (\is_int($index)) { + $index = $tokens->getNonEmptySibling($index, $direction); + + if (null === $index || null === $tokens[$index]->getId()) { continue; } - $partName = substr($useDeclarationFullName, $nsLength); + if (!$tokens[$index]->isWhitespace()) { + $hasNonEmptyToken = true; + } elseif (str_starts_with($tokens[$index]->getContent(), "\n")) { + $newLineTokenIndex = $index; - if (!str_contains($partName, '\\')) { - $this->removeUseDeclaration($tokens, $useDeclaration); + break; } } + + return [$newLineTokenIndex, $hasNonEmptyToken]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php index b17e50dafd..eea91b4993 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/OrderedImportsFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -28,6 +29,7 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** @@ -35,9 +37,33 @@ * @author Dariusz Rumiński * @author Darius Matulionis * @author Adriano Pilger + * + * @phpstan-type _UseImportInfo array{ + * namespace: non-empty-string, + * startIndex: int, + * endIndex: int, + * importType: self::IMPORT_TYPE_*, + * group: bool, + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool, + * imports_order?: list|null, + * sort_algorithm?: 'alpha'|'length'|'none' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool, + * imports_order: list|null, + * sort_algorithm: 'alpha'|'length'|'none' + * } */ final class OrderedImportsFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -71,20 +97,17 @@ final class OrderedImportsFixer extends AbstractFixer implements ConfigurableFix /** * Array of supported sort types in configuration. * - * @var string[] + * @var list */ private const SUPPORTED_SORT_TYPES = [self::IMPORT_TYPE_CLASS, self::IMPORT_TYPE_CONST, self::IMPORT_TYPE_FUNCTION]; /** * Array of supported sort algorithms in configuration. * - * @var string[] + * @var list */ private const SUPPORTED_SORT_ALGORITHMS = [self::SORT_ALPHA, self::SORT_LENGTH, self::SORT_NONE]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -93,6 +116,10 @@ public function getDefinition(): FixerDefinitionInterface new CodeSample( " true] + ), new CodeSample( 'isTokenKindFound(T_USE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); - if (0 === \count($namespacesImports)) { - return; - } + foreach (array_reverse($namespacesImports) as $usesPerNamespaceIndices) { + $count = \count($usesPerNamespaceIndices); - $usesOrder = []; - foreach ($namespacesImports as $uses) { - $usesOrder[] = $this->getNewOrder(array_reverse($uses), $tokens); - } - $usesOrder = array_replace(...$usesOrder); + if (0 === $count) { + continue; // nothing to sort + } - $usesOrder = array_reverse($usesOrder, true); - $mapStartToEnd = []; + if (1 === $count) { + $this->setNewOrder($tokens, $this->getNewOrder($usesPerNamespaceIndices, $tokens)); - foreach ($usesOrder as $use) { - $mapStartToEnd[$use['startIndex']] = $use['endIndex']; - } + continue; + } - // Now insert the new tokens, starting from the end - foreach ($usesOrder as $index => $use) { - $declarationTokens = Tokens::fromCode( - sprintf( - ' [$usesPerNamespaceIndices[0]]]; - $declarationTokens->clearRange(0, 2); // clear `clearAt(\count($declarationTokens) - 1); // clear `;` - $declarationTokens->clearEmptyTokens(); + // if there's some logic between two `use` statements, sort only imports grouped before that logic + for ($index = 0; $index < $count - 1; ++$index) { + $nextGroupUse = $tokens->getNextTokenOfKind($usesPerNamespaceIndices[$index], [';', [T_CLOSE_TAG]]); - $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); - if ($use['group']) { - // a group import must start with `use` and cannot be part of comma separated import list - $prev = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prev]->equals(',')) { - $tokens[$prev] = new Token(';'); - $tokens->insertAt($prev + 1, new Token([T_USE, 'use'])); + if ($tokens[$nextGroupUse]->isGivenKind(T_CLOSE_TAG)) { + $nextGroupUse = $tokens->getNextTokenOfKind($usesPerNamespaceIndices[$index], [[T_OPEN_TAG]]); + } - if (!$tokens[$prev + 2]->isWhitespace()) { - $tokens->insertAt($prev + 2, new Token([T_WHITESPACE, ' '])); - } + $nextGroupUse = $tokens->getNextMeaningfulToken($nextGroupUse); + + if ($nextGroupUse !== $usesPerNamespaceIndices[$index + 1]) { + $groupUses[++$groupUsesOffset] = []; } + + $groupUses[$groupUsesOffset][] = $usesPerNamespaceIndices[$index + 1]; + } + + for ($index = $groupUsesOffset; $index >= 0; --$index) { + $this->setNewOrder($tokens, $this->getNewOrder($groupUses[$index], $tokens)); } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $supportedSortTypes = self::SUPPORTED_SORT_TYPES; return new FixerConfigurationResolver([ - (new FixerOptionBuilder('sort_algorithm', 'whether the statements should be sorted alphabetically or by length, or not sorted')) + (new FixerOptionBuilder('sort_algorithm', 'Whether the statements should be sorted alphabetically or by length, or not sorted.')) ->setAllowedValues(self::SUPPORTED_SORT_ALGORITHMS) ->setDefault(self::SORT_ALPHA) ->getOption(), (new FixerOptionBuilder('imports_order', 'Defines the order of import types.')) - ->setAllowedTypes(['array', 'null']) + ->setAllowedTypes(['string[]', 'null']) ->setAllowedValues([static function (?array $value) use ($supportedSortTypes): bool { if (null !== $value) { $missing = array_diff($supportedSortTypes, $value); if (\count($missing) > 0) { - throw new InvalidOptionsException(sprintf( - 'Missing sort %s "%s".', + throw new InvalidOptionsException(\sprintf( + 'Missing sort %s %s.', 1 === \count($missing) ? 'type' : 'types', - implode('", "', $missing) + Utils::naturalLanguageJoin($missing) )); } $unknown = array_diff($value, $supportedSortTypes); if (\count($unknown) > 0) { - throw new InvalidOptionsException(sprintf( - 'Unknown sort %s "%s".', + throw new InvalidOptionsException(\sprintf( + 'Unknown sort %s %s.', 1 === \count($unknown) ? 'type' : 'types', - implode('", "', $unknown) + Utils::naturalLanguageJoin($unknown) )); } } return true; }]) - ->setDefault(null) + ->setDefault(null) // @TODO set to ['class', 'function', 'const'] on 4.0 + ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) ->getOption(), ]); } @@ -287,10 +302,8 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn /** * This method is used for sorting the uses in a namespace. * - * @param array $first - * @param array $second - * - * @internal + * @param _UseImportInfo $first + * @param _UseImportInfo $second */ private function sortAlphabetically(array $first, array $second): int { @@ -298,16 +311,16 @@ private function sortAlphabetically(array $first, array $second): int $firstNamespace = str_replace('\\', ' ', $this->prepareNamespace($first['namespace'])); $secondNamespace = str_replace('\\', ' ', $this->prepareNamespace($second['namespace'])); - return strcasecmp($firstNamespace, $secondNamespace); + return true === $this->configuration['case_sensitive'] + ? $firstNamespace <=> $secondNamespace + : strcasecmp($firstNamespace, $secondNamespace); } /** * This method is used for sorting the uses statements in a namespace by length. * - * @param array $first - * @param array $second - * - * @internal + * @param _UseImportInfo $first + * @param _UseImportInfo $second */ private function sortByLength(array $first, array $second): int { @@ -318,7 +331,9 @@ private function sortByLength(array $first, array $second): int $secondNamespaceLength = \strlen($secondNamespace); if ($firstNamespaceLength === $secondNamespaceLength) { - $sortResult = strcasecmp($firstNamespace, $secondNamespace); + $sortResult = true === $this->configuration['case_sensitive'] + ? $firstNamespace <=> $secondNamespace + : strcasecmp($firstNamespace, $secondNamespace); } else { $sortResult = $firstNamespaceLength > $secondNamespaceLength ? 1 : -1; } @@ -332,15 +347,18 @@ private function prepareNamespace(string $namespace): string } /** - * @param int[] $uses + * @param list $uses + * + * @return array */ private function getNewOrder(array $uses, Tokens $tokens): array { $indices = []; $originalIndices = []; $lineEnding = $this->whitespacesConfig->getLineEnding(); + $usesCount = \count($uses); - for ($i = \count($uses) - 1; $i >= 0; --$i) { + for ($i = 0; $i < $usesCount; ++$i) { $index = $uses[$i]; $startIndex = $tokens->getTokenNotOfKindsSibling($index + 1, 1, [T_WHITESPACE]); @@ -461,7 +479,7 @@ private function getNewOrder(array $uses, Tokens $tokens): array } $namespaceTokens = []; - $nextPartIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[','], [T_WHITESPACE]]); + $nextPartIndex = $tokens->getTokenNotOfKindSibling($index, 1, [',', [T_WHITESPACE]]); $startIndex = $nextPartIndex; $index = $nextPartIndex; @@ -491,7 +509,7 @@ private function getNewOrder(array $uses, Tokens $tokens): array $sortedGroups = []; foreach ($this->configuration['imports_order'] as $type) { - if (isset($groupedByTypes[$type]) && !empty($groupedByTypes[$type])) { + if (isset($groupedByTypes[$type]) && [] !== $groupedByTypes[$type]) { foreach ($groupedByTypes[$type] as $startIndex => $item) { $sortedGroups[$startIndex] = $item; } @@ -516,7 +534,9 @@ private function getNewOrder(array $uses, Tokens $tokens): array } /** - * @param array[] $indices + * @param array $indices + * + * @return array */ private function sortByAlgorithm(array $indices): array { @@ -528,4 +548,53 @@ private function sortByAlgorithm(array $indices): array return $indices; } + + /** + * @param array $usesOrder + */ + private function setNewOrder(Tokens $tokens, array $usesOrder): void + { + $mapStartToEnd = []; + + foreach ($usesOrder as $use) { + $mapStartToEnd[$use['startIndex']] = $use['endIndex']; + } + + // Now insert the new tokens, starting from the end + foreach (array_reverse($usesOrder, true) as $index => $use) { + $code = \sprintf( + 'getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->equals(',')) { + $numberOfInitialTokensToClear = 5; // clear `clearRange(0, $numberOfInitialTokensToClear - 1); + $declarationTokens->clearAt(\count($declarationTokens) - 1); // clear `;` + $declarationTokens->clearEmptyTokens(); + + $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); + + if ($use['group']) { + // a group import must start with `use` and cannot be part of comma separated import list + $prev = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prev]->equals(',')) { + $tokens[$prev] = new Token(';'); + $tokens->insertAt($prev + 1, new Token([T_USE, 'use'])); + + if (!$tokens[$prev + 2]->isWhitespace()) { + $tokens->insertAt($prev + 2, new Token([T_WHITESPACE, ' '])); + } + } + } + } + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php index ce4aa66cf5..a344fe4410 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleImportPerStatementFixer.php @@ -15,7 +15,12 @@ namespace PhpCsFixer\Fixer\Import; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -29,17 +34,42 @@ * Fixer for rules defined in PSR2 ¶3. * * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * group_to_single_imports?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * group_to_single_imports: bool + * } */ -final class SingleImportPerStatementFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +final class SingleImportPerStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'There MUST be one use keyword per declaration.', - [new CodeSample(" true] + ), + ] ); } @@ -53,17 +83,11 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_USE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -73,13 +97,28 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $groupClose = $tokens->getPrevMeaningfulToken($endIndex); if ($tokens[$groupClose]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { - $this->fixGroupUse($tokens, $index, $endIndex); + if (true === $this->configuration['group_to_single_imports']) { + $this->fixGroupUse($tokens, $index, $endIndex); + } } else { $this->fixMultipleUse($tokens, $index, $endIndex); } } } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('group_to_single_imports', 'Whether to change group imports into single imports.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @return array{string, ?int, int, string} + */ private function getGroupDeclaration(Tokens $tokens, int $index): array { $groupPrefix = ''; @@ -120,7 +159,7 @@ private function getGroupDeclaration(Tokens $tokens, int $index): array } /** - * @return string[] + * @return list */ private function getGroupStatements(Tokens $tokens, string $groupPrefix, int $groupOpenIndex, int $groupCloseIndex, string $comment): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php index 773f25bf4c..b8b977974a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Import/SingleLineAfterImportsFixer.php @@ -32,17 +32,11 @@ */ final class SingleLineAfterImportsFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_USE); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -86,9 +80,6 @@ public function getPriority(): int return -11; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $ending = $this->whitespacesConfig->getLineEnding(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Indentation.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Indentation.php new file mode 100644 index 0000000000..0e370f20f0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Indentation.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer; + +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +trait Indentation +{ + private function getLineIndentation(Tokens $tokens, int $index): string + { + $newlineTokenIndex = $this->getPreviousNewlineTokenIndex($tokens, $index); + + if (null === $newlineTokenIndex) { + return ''; + } + + return $this->extractIndent($this->computeNewLineContent($tokens, $newlineTokenIndex)); + } + + private function extractIndent(string $content): string + { + if (Preg::match('/\R(\h*)[^\r\n]*$/D', $content, $matches)) { + return $matches[1]; + } + + return ''; + } + + private function getPreviousNewlineTokenIndex(Tokens $tokens, int $index): ?int + { + while ($index > 0) { + $index = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE], [T_INLINE_HTML]]); + + if (null === $index) { + break; + } + + if ($this->isNewLineToken($tokens, $index)) { + return $index; + } + } + + return null; + } + + private function computeNewLineContent(Tokens $tokens, int $index): string + { + $content = $tokens[$index]->getContent(); + + if (0 !== $index && $tokens[$index - 1]->equalsAny([[T_OPEN_TAG], [T_CLOSE_TAG]])) { + $content = Preg::replace('/\S/', '', $tokens[$index - 1]->getContent()).$content; + } + + return $content; + } + + private function isNewLineToken(Tokens $tokens, int $index): bool + { + $token = $tokens[$index]; + + if ( + $token->isGivenKind(T_OPEN_TAG) + && isset($tokens[$index + 1]) + && !$tokens[$index + 1]->isWhitespace() + && Preg::match('/\R/', $token->getContent()) + ) { + return true; + } + + if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML])) { + return false; + } + + return Preg::match('/\R/', $this->computeNewLineContent($tokens, $index)); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/InternalFixerInterface.php similarity index 67% rename from vendor/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php rename to vendor/friendsofphp/php-cs-fixer/src/Fixer/InternalFixerInterface.php index 38b6999e1d..cf843c8077 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Console/Output/NullOutput.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/InternalFixerInterface.php @@ -12,14 +12,9 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\Console\Output; +namespace PhpCsFixer\Fixer; /** * @internal */ -final class NullOutput implements ProcessOutputInterface -{ - public function printLegend(): void - { - } -} +interface InternalFixerInterface extends FixerInterface {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordFixer.php new file mode 100644 index 0000000000..b171e56b65 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordFixer.php @@ -0,0 +1,100 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ExperimentalFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + */ +final class ClassKeywordFixer extends AbstractFixer implements ExperimentalFixerInterface +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Converts FQCN strings to `*::class` keywords.', + [ + new CodeSample( + 'count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $name = substr($token->getContent(), 1, -1); + $name = ltrim($name, '\\'); + $name = str_replace('\\\\', '\\', $name); + + if ($this->exists($name)) { + $substitution = Tokens::fromCode("clearRange(0, 2); + $substitution->clearAt($substitution->getSize() - 1); + $substitution->clearEmptyTokens(); + + $tokens->clearAt($index); + $tokens->insertAt($index, $substitution); + } + } + } + } + + private function exists(string $name): bool + { + if (class_exists($name) || interface_exists($name) || trait_exists($name)) { + $rc = new \ReflectionClass($name); + + return $rc->getName() === $name; + } + + return false; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php index 491aa89fc5..3caf9f1b52 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php @@ -19,7 +19,6 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -33,13 +32,10 @@ final class ClassKeywordRemoveFixer extends AbstractFixer implements DeprecatedFixerInterface { /** - * @var string[] + * @var array */ private array $imports = []; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -57,9 +53,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function getSuccessorsNames(): array { return []; @@ -75,23 +68,15 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(CT::T_CLASS_CONSTANT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $namespacesAnalyzer = new NamespacesAnalyzer(); - $previousNamespaceScopeEndIndex = 0; - foreach ($namespacesAnalyzer->getDeclarations($tokens) as $declaration) { + foreach ($tokens->getNamespaceDeclarations() as $declaration) { $this->replaceClassKeywordsSection($tokens, '', $previousNamespaceScopeEndIndex, $declaration->getStartIndex()); $this->replaceClassKeywordsSection($tokens, $declaration->getFullName(), $declaration->getStartIndex(), $declaration->getScopeEndIndex()); $previousNamespaceScopeEndIndex = $declaration->getScopeEndIndex(); @@ -124,15 +109,11 @@ private function storeImports(Tokens $tokens, int $startIndex, int $endIndex): v if ($tokens[$index]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { $groupEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $index); $groupImports = array_map( - static function (string $import): string { - return trim($import); - }, + static fn (string $import): string => trim($import), explode(',', $tokens->generatePartialCode($index + 1, $groupEndIndex - 1)) ); foreach ($groupImports as $groupImport) { - $groupImportParts = array_map(static function (string $import): string { - return trim($import); - }, explode(' as ', $groupImport)); + $groupImportParts = array_map(static fn (string $import): string => trim($import), explode(' as ', $groupImport)); if (2 === \count($groupImportParts)) { $this->imports[$groupImportParts[1]] = $import.$groupImportParts[0]; } else { @@ -207,7 +188,7 @@ private function replaceClassKeyword(Tokens $tokens, string $namespacePrefix, in $classStringArray = explode('\\', $classString); $namespaceToTest = $classStringArray[0]; - if (0 === strcmp($namespaceToTest, substr($import, -\strlen($namespaceToTest)))) { + if (0 === ($namespaceToTest <=> substr($import, -\strlen($namespaceToTest)))) { $classImport = $import; break; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php index ce09c125d5..e84bcb9dce 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php @@ -23,9 +23,6 @@ final class CombineConsecutiveIssetsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,24 +34,18 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before MultilineWhitespaceBeforeSemicolonsFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer. + * Must run before MultilineWhitespaceBeforeSemicolonsFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SpacesInsideParenthesesFixer. */ public function getPriority(): int { - return 3; + return 4; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_ISSET, T_BOOLEAN_AND]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokenCount = $tokens->count(); @@ -113,7 +104,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @param int[] $indices + * @param list $indices */ private function clearTokens(Tokens $tokens, array $indices): void { @@ -125,7 +116,7 @@ private function clearTokens(Tokens $tokens, array $indices): void /** * @param int $index of T_ISSET * - * @return int[] indices of meaningful tokens belonging to the isset statement + * @return list indices of meaningful tokens belonging to the isset statement */ private function getIssetInfo(Tokens $tokens, int $index): array { @@ -155,9 +146,9 @@ private function getIssetInfo(Tokens $tokens, int $index): array } /** - * @param int[] $indices + * @param list $indices * - * @return Token[] + * @return list */ private function getTokenClones(Tokens $tokens, array $indices): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php index 6a595b69c8..24f1895823 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php @@ -23,9 +23,6 @@ final class CombineConsecutiveUnsetsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,17 +42,11 @@ public function getPriority(): int return 24; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_UNSET); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; $index >= 0; --$index) { @@ -101,7 +92,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @param int[] $indices + * @param list $indices */ private function clearOffsetTokens(Tokens $tokens, int $offset, array $indices): void { @@ -121,7 +112,7 @@ private function clearOffsetTokens(Tokens $tokens, int $offset, array $indices): * * Or the index to where the method looked for a call. * - * @return int|int[] + * @return array{int, int, int, int}|int */ private function getPreviousUnsetCall(Tokens $tokens, int $index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php index 8e0d4da92f..442ee7238e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DeclareEqualNormalizeFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,27 +28,21 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * space?: 'none'|'single' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * space: 'none'|'single' + * } */ final class DeclareEqualNormalizeFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * @var string - */ - private $callback; - - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; - $this->callback = 'none' === $this->configuration['space'] ? 'removeWhitespaceAroundToken' : 'ensureWhitespaceAroundToken'; - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -69,20 +64,13 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DECLARE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $callback = $this->callback; for ($index = 0, $count = $tokens->count(); $index < $count - 6; ++$index) { if (!$tokens[$index]->isGivenKind(T_DECLARE)) { continue; @@ -93,15 +81,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void for ($i = $closeParenthesisIndex; $i > $openParenthesisIndex; --$i) { if ($tokens[$i]->equals('=')) { - $this->{$callback}($tokens, $i); + if ('none' === $this->configuration['space']) { + $this->removeWhitespaceAroundToken($tokens, $i); + } else { + $this->ensureWhitespaceAroundToken($tokens, $i); + } } } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php index 112101a324..a19368e09d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/DirConstantFixer.php @@ -26,9 +26,6 @@ */ final class DirConstantFixer extends AbstractFunctionReferenceFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -39,9 +36,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_STRING, T_FILE]); @@ -57,9 +51,6 @@ public function getPriority(): int return 40; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $currIndex = 0; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php index 24b607f1c7..551d8e334d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,9 +30,25 @@ /** * @author Jules Pietri * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * mute_deprecation_error?: bool, + * noise_remaining_usages?: bool, + * noise_remaining_usages_exclude?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * mute_deprecation_error: bool, + * noise_remaining_usages: bool, + * noise_remaining_usages_exclude: list + * } */ final class ErrorSuppressionFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -47,9 +64,6 @@ final class ErrorSuppressionFixer extends AbstractFixer implements ConfigurableF */ public const OPTION_NOISE_REMAINING_USAGES_EXCLUDE = 'noise_remaining_usages_exclude'; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -73,25 +87,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -103,22 +108,17 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), - (new FixerOptionBuilder(self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE, 'List of global functions to exclude from removing `@`')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder(self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE, 'List of global functions to exclude from removing `@`.')) + ->setAllowedTypes(['string[]']) ->setDefault([]) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); - $excludedFunctions = array_map(static function (string $function): string { - return strtolower($function); - }, $this->configuration[self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE]); + $excludedFunctions = array_map(static fn (string $function): string => strtolower($function), $this->configuration[self::OPTION_NOISE_REMAINING_USAGES_EXCLUDE]); for ($index = $tokens->count() - 1; $index >= 0; --$index) { $token = $tokens[$index]; @@ -174,7 +174,7 @@ private function isDeprecationErrorCall(Tokens $tokens, int $index): bool return false; } - $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($index, [T_STRING, '('])); + $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($index, [[T_STRING], '('])); $prevIndex = $tokens->getPrevMeaningfulToken($endBraceIndex); if ($tokens[$prevIndex]->equals(',')) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php index 25925fe1f1..38ef5fe901 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php @@ -27,9 +27,6 @@ */ final class ExplicitIndirectVariableFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,29 +34,23 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( <<<'EOT' -$bar['baz']; -echo $foo->$callback($baz); + $bar['baz']; + echo $foo->$callback($baz); -EOT + EOT ), ] ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_VARIABLE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; $index > 1; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php index 40ce735ef5..a182616d54 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/FunctionToConstantFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -28,17 +29,30 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * functions?: list<'get_called_class'|'get_class'|'get_class_this'|'php_sapi_name'|'phpversion'|'pi'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * functions: list<'get_called_class'|'get_class'|'get_class_this'|'php_sapi_name'|'phpversion'|'pi'> + * } + */ final class FunctionToConstantFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var array + * @var array> */ private static $availableFunctions; /** - * @var array + * @var array> */ - private $functionsFixMap; + private array $functionsFixMap; public function __construct() { @@ -49,7 +63,11 @@ public function __construct() new Token([T_DOUBLE_COLON, '::']), new Token([CT::T_CLASS_CONSTANT, 'class']), ], - 'get_class' => [new Token([T_CLASS_C, '__CLASS__'])], + 'get_class' => [ + new Token([T_STRING, 'self']), + new Token([T_DOUBLE_COLON, '::']), + new Token([CT::T_CLASS_CONSTANT, 'class']), + ], 'get_class_this' => [ new Token([T_STATIC, 'static']), new Token([T_DOUBLE_COLON, '::']), @@ -64,22 +82,6 @@ public function __construct() parent::__construct(); } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->functionsFixMap = []; - foreach ($this->configuration['functions'] as $key) { - $this->functionsFixMap[$key] = self::$availableFunctions[$key]; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -101,33 +103,33 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before NativeFunctionCasingFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SelfStaticAccessorFixer. - * Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer. + * Must run before NativeConstantInvocationFixer, NativeFunctionCasingFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SelfStaticAccessorFixer. + * Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. */ public function getPriority(): int { - return 1; + return 2; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->functionsFixMap = []; + + foreach ($this->configuration['functions'] as $key) { + $this->functionsFixMap[$key] = self::$availableFunctions[$key]; + } + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionAnalyzer = new FunctionsAnalyzer(); @@ -148,16 +150,13 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $functionNames = array_keys(self::$availableFunctions); return new FixerConfigurationResolver([ (new FixerOptionBuilder('functions', 'List of function names to fix.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($functionNames)]) ->setDefault([ 'get_called_class', @@ -172,7 +171,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn } /** - * @param Token[] $replacements + * @param list $replacements */ private function fixFunctionCallToConstant(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex, array $replacements): void { @@ -184,7 +183,10 @@ private function fixFunctionCallToConstant(Tokens $tokens, int $index, int $brac $tokens->clearTokenAndMergeSurroundingWhitespace($i); } - if ($replacements[0]->isGivenKind([T_CLASS_C, T_STATIC])) { + if ( + $replacements[0]->isGivenKind([T_CLASS_C, T_STATIC]) + || ($replacements[0]->isGivenKind(T_STRING) && 'self' === $replacements[0]->getContent()) + ) { $prevIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevIndex]; if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { @@ -196,6 +198,9 @@ private function fixFunctionCallToConstant(Tokens $tokens, int $index, int $brac $tokens->insertAt($index, $replacements); } + /** + * @return ?array{int, int, list} + */ private function getReplaceCandidate( Tokens $tokens, FunctionsAnalyzer $functionAnalyzer, @@ -233,6 +238,9 @@ private function getReplaceCandidate( return $this->getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex); } + /** + * @return ?array{int, int, list} + */ private function fixGetClassCall( Tokens $tokens, FunctionsAnalyzer $functionAnalyzer, @@ -284,12 +292,15 @@ private function fixGetClassCall( return null; } + /** + * @return array{int, int, list} + */ private function getReplacementTokenClones(string $lowerContent, int $braceOpenIndex, int $braceCloseIndex): array { - $clones = []; - foreach ($this->functionsFixMap[$lowerContent] as $token) { - $clones[] = clone $token; - } + $clones = array_map( + static fn (Token $token): Token => clone $token, + $this->functionsFixMap[$lowerContent], + ); return [ $braceOpenIndex, diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php index c9e738402c..d5ceb526f3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php @@ -29,9 +29,6 @@ */ final class GetClassToClassKeywordFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -39,11 +36,11 @@ public function getDefinition(): FixerDefinitionInterface [ new VersionSpecificCodeSample( "= 80000 && $tokens->isAllTokenKindsFound([T_STRING, T_VARIABLE]); + return \PHP_VERSION_ID >= 8_00_00 && $tokens->isAllTokenKindsFound([T_STRING, T_VARIABLE]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -137,6 +125,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $tokens->insertSlices($tokenSlices); } + /** + * @return list + */ private function getReplacementTokenSlices(Tokens $tokens, int $variableIndex): array { return [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php index 9068abfe37..a99695d4fa 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/IsNullFixer.php @@ -27,9 +27,6 @@ */ final class IsNullFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -52,25 +49,16 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { static $sequenceNeeded = [[T_STRING, 'is_null'], '(']; @@ -135,11 +123,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - // edge cases: is_null() followed/preceded by ==, ===, !=, !==, <> + // edge cases: is_null() followed/preceded by ==, ===, !=, !==, <>, (int-or-other-casting) $parentLeftToken = $tokens[$tokens->getPrevMeaningfulToken($isNullIndex)]; $parentRightToken = $tokens[$tokens->getNextMeaningfulToken($referenceEnd)]; $parentOperations = [T_IS_EQUAL, T_IS_NOT_EQUAL, T_IS_IDENTICAL, T_IS_NOT_IDENTICAL]; - $wrapIntoParentheses = $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations); + $wrapIntoParentheses = $parentLeftToken->isCast() || $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations); // possible trailing comma removed $prevIndex = $tokens->getPrevMeaningfulToken($referenceEnd); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php index af7f65d604..c72a783a2d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php @@ -27,9 +27,6 @@ */ final class NoUnsetOnPropertyFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -43,17 +40,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_UNSET) @@ -93,7 +84,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @return array> + * @return list> */ private function getUnsetsInfo(Tokens $tokens, int $index): array { @@ -153,7 +144,7 @@ private function isProperty(Tokens $tokens, int $index, int $endIndex): bool } /** - * @param array> $unsetsInfo + * @param list> $unsetsInfo */ private function isAnyUnsetToTransform(array $unsetsInfo): bool { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NullableTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NullableTypeDeclarationFixer.php new file mode 100644 index 0000000000..73626f8774 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/NullableTypeDeclarationFixer.php @@ -0,0 +1,343 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author John Paul E. Balandan, CPA + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * syntax?: 'question_mark'|'union' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * syntax: 'question_mark'|'union' + * } + */ +final class NullableTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private const OPTION_SYNTAX_UNION = 'union'; + private const OPTION_SYNTAX_QUESTION_MARK = 'question_mark'; + + private int $candidateTokenKind; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Nullable single type declaration should be standardised using configured syntax.', + [ + new VersionSpecificCodeSample( + " self::OPTION_SYNTAX_UNION] + ), + new VersionSpecificCodeSample( + ' self::OPTION_SYNTAX_QUESTION_MARK] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_00_00 && $tokens->isTokenKindFound($this->candidateTokenKind); + } + + /** + * {@inheritdoc} + * + * Must run before OrderedTypesFixer, TypesSpacesFixer. + * Must run after NullableTypeDeclarationForDefaultNullValueFixer, SingleSpaceAroundConstructFixer. + */ + public function getPriority(): int + { + return 2; + } + + protected function configurePostNormalisation(): void + { + $this->candidateTokenKind = self::OPTION_SYNTAX_QUESTION_MARK === $this->configuration['syntax'] + ? CT::T_TYPE_ALTERNATION // `|` -> `?` + : CT::T_NULLABLE_TYPE; // `?` -> `|` + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('syntax', 'Whether to use question mark (`?`) or explicit `null` union for nullable type.')) + ->setAllowedValues([self::OPTION_SYNTAX_UNION, self::OPTION_SYNTAX_QUESTION_MARK]) + ->setDefault(self::OPTION_SYNTAX_QUESTION_MARK) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach (array_reverse($this->getElements($tokens), true) as $index => $type) { + if ('property' === $type) { + $this->normalizePropertyType($tokens, $index); + + continue; + } + + $this->normalizeMethodReturnType($functionsAnalyzer, $tokens, $index); + $this->normalizeMethodArgumentType($functionsAnalyzer, $tokens, $index); + } + } + + /** + * @return array + * + * @phpstan-return array + */ + private function getElements(Tokens $tokens): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $elements = array_map( + static fn (array $element): string => 'method' === $element['type'] ? 'function' : $element['type'], + array_filter( + $tokensAnalyzer->getClassyElements(), + static fn (array $element): bool => \in_array($element['type'], ['method', 'property'], true) + ) + ); + + foreach ($tokens as $index => $token) { + if ( + $token->isGivenKind(T_FN) + || ($token->isGivenKind(T_FUNCTION) && !isset($elements[$index])) + ) { + $elements[$index] = 'function'; + } + } + + return $elements; + } + + private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis + { + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex); + $typeEndIndex = $typeStartIndex; + + for ($i = $typeStartIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null; + } + + private function isTypeNormalizable(TypeAnalysis $typeAnalysis): bool + { + $type = $typeAnalysis->getName(); + + if ('null' === strtolower($type) || !$typeAnalysis->isNullable()) { + return false; + } + + if (str_contains($type, '&')) { + return false; // skip DNF types + } + + if (!str_contains($type, '|')) { + return true; + } + + return 1 === substr_count($type, '|') && Preg::match('/(?:\|null$|^null\|)/i', $type); + } + + private function normalizePropertyType(Tokens $tokens, int $index): void + { + $propertyEndIndex = $index; + $propertyModifiers = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + + if (\defined('T_READONLY')) { + $propertyModifiers[] = T_READONLY; // @TODO: Drop condition when PHP 8.1+ is required + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while (!$tokens[$index]->isGivenKind($propertyModifiers)); + + $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyEndIndex); + + if (null === $propertyType || !$this->isTypeNormalizable($propertyType)) { + return; + } + + $this->normalizeNullableType($tokens, $propertyType); + } + + private function normalizeMethodArgumentType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + foreach (array_reverse($functionsAnalyzer->getFunctionArguments($tokens, $index), true) as $argumentInfo) { + $argumentType = $argumentInfo->getTypeAnalysis(); + + if (null === $argumentType || !$this->isTypeNormalizable($argumentType)) { + continue; + } + + $this->normalizeNullableType($tokens, $argumentType); + } + } + + private function normalizeMethodReturnType(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + $returnType = $functionsAnalyzer->getFunctionReturnType($tokens, $index); + + if (null === $returnType || !$this->isTypeNormalizable($returnType)) { + return; + } + + $this->normalizeNullableType($tokens, $returnType); + } + + private function normalizeNullableType(Tokens $tokens, TypeAnalysis $typeAnalysis): void + { + $type = $typeAnalysis->getName(); + + if (!str_contains($type, '|') && !str_contains($type, '&')) { + $type = ($typeAnalysis->isNullable() ? '?' : '').$type; + } + + $isQuestionMarkSyntax = self::OPTION_SYNTAX_QUESTION_MARK === $this->configuration['syntax']; + + if ($isQuestionMarkSyntax) { + $normalizedType = $this->convertToNullableType($type); + $normalizedTypeAsString = implode('', $normalizedType); + } else { + $normalizedType = $this->convertToExplicitUnionType($type); + $normalizedTypeAsString = implode('|', $normalizedType); + } + + if ($normalizedTypeAsString === $type) { + return; // nothing to fix + } + + $tokens->overrideRange( + $typeAnalysis->getStartIndex(), + $typeAnalysis->getEndIndex(), + $this->createTypeDeclarationTokens($normalizedType, $isQuestionMarkSyntax) + ); + } + + /** + * @return list + */ + private function convertToNullableType(string $type): array + { + if (str_starts_with($type, '?')) { + return [$type]; // no need to convert; already fixed + } + + return ['?', Preg::replace('/(?:\|null$|^null\|)/i', '', $type)]; + } + + /** + * @return list + */ + private function convertToExplicitUnionType(string $type): array + { + if (str_contains($type, '|')) { + return [$type]; // no need to convert; already fixed + } + + return ['null', substr($type, 1)]; + } + + /** + * @param list $types + * + * @return list + */ + private function createTypeDeclarationTokens(array $types, bool $isQuestionMarkSyntax): array + { + static $specialTypes = [ + '?' => CT::T_NULLABLE_TYPE, + 'array' => CT::T_ARRAY_TYPEHINT, + 'callable' => T_CALLABLE, + 'static' => T_STATIC, + ]; + + $count = \count($types); + $newTokens = []; + + foreach ($types as $index => $type) { + if (isset($specialTypes[strtolower($type)])) { + $newTokens[] = new Token([$specialTypes[strtolower($type)], $type]); + } else { + foreach (explode('\\', $type) as $nsIndex => $value) { + if (0 === $nsIndex && '' === $value) { + continue; + } + + if ($nsIndex > 0) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + $newTokens[] = new Token([T_STRING, $value]); + } + } + + if ($index <= $count - 2 && !$isQuestionMarkSyntax) { + $newTokens[] = new Token([CT::T_TYPE_ALTERNATION, '|']); + } + } + + return $newTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php index 030eb9aa21..7ca0c4e379 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php @@ -14,8 +14,10 @@ namespace PhpCsFixer\Fixer\LanguageConstruct; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -23,16 +25,27 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; /** * @author Andreas Möller + * + * @deprecated + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * constructs?: list<'abstract'|'as'|'attribute'|'break'|'case'|'catch'|'class'|'clone'|'comment'|'const'|'const_import'|'continue'|'do'|'echo'|'else'|'elseif'|'enum'|'extends'|'final'|'finally'|'for'|'foreach'|'function'|'function_import'|'global'|'goto'|'if'|'implements'|'include'|'include_once'|'instanceof'|'insteadof'|'interface'|'match'|'named_argument'|'namespace'|'new'|'open_tag_with_echo'|'php_doc'|'php_open'|'print'|'private'|'protected'|'public'|'readonly'|'require'|'require_once'|'return'|'static'|'switch'|'throw'|'trait'|'try'|'type_colon'|'use'|'use_lambda'|'use_trait'|'var'|'while'|'yield'|'yield_from'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * constructs: list<'abstract'|'as'|'attribute'|'break'|'case'|'catch'|'class'|'clone'|'comment'|'const'|'const_import'|'continue'|'do'|'echo'|'else'|'elseif'|'enum'|'extends'|'final'|'finally'|'for'|'foreach'|'function'|'function_import'|'global'|'goto'|'if'|'implements'|'include'|'include_once'|'instanceof'|'insteadof'|'interface'|'match'|'named_argument'|'namespace'|'new'|'open_tag_with_echo'|'php_doc'|'php_open'|'print'|'private'|'protected'|'public'|'readonly'|'require'|'require_once'|'return'|'static'|'switch'|'throw'|'trait'|'try'|'type_colon'|'use'|'use_lambda'|'use_trait'|'var'|'while'|'yield'|'yield_from'> + * } */ -final class SingleSpaceAfterConstructFixer extends AbstractFixer implements ConfigurableFixerInterface +final class SingleSpaceAfterConstructFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var array */ @@ -90,6 +103,7 @@ final class SingleSpaceAfterConstructFixer extends AbstractFixer implements Conf 'throw' => T_THROW, 'trait' => T_TRAIT, 'try' => T_TRY, + 'type_colon' => CT::T_TYPE_COLON, 'use' => T_USE, 'use_lambda' => CT::T_USE_LAMBDA, 'use_trait' => CT::T_USE_TRAIT, @@ -99,54 +113,20 @@ final class SingleSpaceAfterConstructFixer extends AbstractFixer implements Conf 'yield_from' => T_YIELD_FROM, ]; - /** - * @var array - */ - private array $fixTokenMap = []; + private SingleSpaceAroundConstructFixer $singleSpaceAroundConstructFixer; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void + public function __construct() { - parent::configure($configuration); - - if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required - self::$tokenMap['match'] = T_MATCH; - } - - if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required - self::$tokenMap['readonly'] = T_READONLY; - } - - if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required - self::$tokenMap['enum'] = T_ENUM; - } + $this->singleSpaceAroundConstructFixer = new SingleSpaceAroundConstructFixer(); - $this->fixTokenMap = []; - - foreach ($this->configuration['constructs'] as $key) { - if (null !== self::$tokenMap[$key]) { - $this->fixTokenMap[$key] = self::$tokenMap[$key]; - } - } - - if (isset($this->fixTokenMap['public'])) { - $this->fixTokenMap['constructor_public'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC; - } - - if (isset($this->fixTokenMap['protected'])) { - $this->fixTokenMap['constructor_protected'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED; - } + parent::__construct(); + } - if (isset($this->fixTokenMap['private'])) { - $this->fixTokenMap['constructor_private'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE; - } + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -188,167 +168,42 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before BracesFixer, FunctionDeclarationFixer. - * Must run after ModernizeStrposFixer. + * Must run after ArraySyntaxFixer, ModernizeStrposFixer. */ public function getPriority(): int { - return 36; + return parent::getPriority(); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + protected function configurePostNormalisation(): void { - return $tokens->isAnyTokenKindsFound(array_values($this->fixTokenMap)) && !$tokens->hasAlternativeSyntax(); + $this->singleSpaceAroundConstructFixer->configure([ + 'constructs_contain_a_single_space' => [ + 'yield_from', + ], + 'constructs_preceded_by_a_single_space' => [], + 'constructs_followed_by_a_single_space' => $this->configuration['constructs'], + ]); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function createProxyFixers(): array { - $tokenKinds = array_values($this->fixTokenMap); - - for ($index = $tokens->count() - 2; $index >= 0; --$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind($tokenKinds)) { - continue; - } - - $whitespaceTokenIndex = $index + 1; - - if ($tokens[$whitespaceTokenIndex]->equalsAny([',', ';', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]])) { - continue; - } - - if ( - $token->isGivenKind(T_STATIC) - && !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind([T_FUNCTION, T_VARIABLE]) - ) { - continue; - } - - if ($token->isGivenKind(T_OPEN_TAG)) { - if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && !str_contains($token->getContent(), "\n")) { - $tokens->clearAt($whitespaceTokenIndex); - } - - continue; - } - - if ($token->isGivenKind(T_CLASS) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(')) { - continue; - } - - if ($token->isGivenKind([T_EXTENDS, T_IMPLEMENTS]) && $this->isMultilineExtendsOrImplementsWithMoreThanOneAncestor($tokens, $index)) { - continue; - } - - if ($token->isGivenKind(T_RETURN) && $this->isMultiLineReturn($tokens, $index)) { - continue; - } - - if ($token->isGivenKind(T_CONST) && $this->isMultilineConstant($tokens, $index)) { - continue; - } - - if ($token->isComment() || $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { - if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { - continue; - } - } - - $tokens->ensureWhitespaceAtIndex($whitespaceTokenIndex, 0, ' '); - - if ( - $token->isGivenKind(T_YIELD_FROM) - && 'yield from' !== strtolower($token->getContent()) - ) { - $tokens[$index] = new Token([T_YIELD_FROM, Preg::replace( - '/\s+/', - ' ', - $token->getContent() - )]); - } - } + return [$this->singleSpaceAroundConstructFixer]; } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - $tokens = array_keys(self::$tokenMap); + $defaults = self::$tokenMap; + $tokens = array_keys($defaults); + + unset($defaults['type_colon']); return new FixerConfigurationResolver([ (new FixerOptionBuilder('constructs', 'List of constructs which must be followed by a single space.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($tokens)]) - ->setDefault($tokens) + ->setDefault(array_keys($defaults)) ->getOption(), ]); } - - private function isMultiLineReturn(Tokens $tokens, int $index): bool - { - ++$index; - $tokenFollowingReturn = $tokens[$index]; - - if ( - !$tokenFollowingReturn->isGivenKind(T_WHITESPACE) - || !str_contains($tokenFollowingReturn->getContent(), "\n") - ) { - return false; - } - - $nestedCount = 0; - - for ($indexEnd = \count($tokens) - 1, ++$index; $index < $indexEnd; ++$index) { - if (str_contains($tokens[$index]->getContent(), "\n")) { - return true; - } - - if ($tokens[$index]->equals('{')) { - ++$nestedCount; - } elseif ($tokens[$index]->equals('}')) { - --$nestedCount; - } elseif (0 === $nestedCount && $tokens[$index]->equalsAny([';', [T_CLOSE_TAG]])) { - break; - } - } - - return false; - } - - private function isMultilineExtendsOrImplementsWithMoreThanOneAncestor(Tokens $tokens, int $index): bool - { - $hasMoreThanOneAncestor = false; - - while (++$index) { - $token = $tokens[$index]; - - if ($token->equals(',')) { - $hasMoreThanOneAncestor = true; - - continue; - } - - if ($token->equals('{')) { - return false; - } - - if ($hasMoreThanOneAncestor && str_contains($token->getContent(), "\n")) { - return true; - } - } - - return false; - } - - private function isMultilineConstant(Tokens $tokens, int $index): bool - { - $scopeEnd = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]) - 1; - $hasMoreThanOneConstant = null !== $tokens->findSequence([new Token(',')], $index + 1, $scopeEnd); - - return $hasMoreThanOneConstant && $tokens->isPartialCodeMultiline($index, $scopeEnd); - } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php new file mode 100644 index 0000000000..ac535cefea --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php @@ -0,0 +1,498 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\LanguageConstruct; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Andreas Möller + * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * constructs_contain_a_single_space?: list<'yield_from'>, + * constructs_followed_by_a_single_space?: list<'abstract'|'as'|'attribute'|'break'|'case'|'catch'|'class'|'clone'|'comment'|'const'|'const_import'|'continue'|'do'|'echo'|'else'|'elseif'|'enum'|'extends'|'final'|'finally'|'for'|'foreach'|'function'|'function_import'|'global'|'goto'|'if'|'implements'|'include'|'include_once'|'instanceof'|'insteadof'|'interface'|'match'|'named_argument'|'namespace'|'new'|'open_tag_with_echo'|'php_doc'|'php_open'|'print'|'private'|'protected'|'public'|'readonly'|'require'|'require_once'|'return'|'static'|'switch'|'throw'|'trait'|'try'|'type_colon'|'use'|'use_lambda'|'use_trait'|'var'|'while'|'yield'|'yield_from'>, + * constructs_preceded_by_a_single_space?: list<'as'|'use_lambda'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * constructs_contain_a_single_space: list<'yield_from'>, + * constructs_followed_by_a_single_space: list<'abstract'|'as'|'attribute'|'break'|'case'|'catch'|'class'|'clone'|'comment'|'const'|'const_import'|'continue'|'do'|'echo'|'else'|'elseif'|'enum'|'extends'|'final'|'finally'|'for'|'foreach'|'function'|'function_import'|'global'|'goto'|'if'|'implements'|'include'|'include_once'|'instanceof'|'insteadof'|'interface'|'match'|'named_argument'|'namespace'|'new'|'open_tag_with_echo'|'php_doc'|'php_open'|'print'|'private'|'protected'|'public'|'readonly'|'require'|'require_once'|'return'|'static'|'switch'|'throw'|'trait'|'try'|'type_colon'|'use'|'use_lambda'|'use_trait'|'var'|'while'|'yield'|'yield_from'>, + * constructs_preceded_by_a_single_space: list<'as'|'use_lambda'> + * } + */ +final class SingleSpaceAroundConstructFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** + * @var array + */ + private static array $tokenMapContainASingleSpace = [ + // for now, only one case - but we are ready to extend it, when we learn about new cases to cover + 'yield_from' => T_YIELD_FROM, + ]; + + /** + * @var array + */ + private static array $tokenMapPrecededByASingleSpace = [ + 'as' => T_AS, + 'use_lambda' => CT::T_USE_LAMBDA, + ]; + + /** + * @var array + */ + private static array $tokenMapFollowedByASingleSpace = [ + 'abstract' => T_ABSTRACT, + 'as' => T_AS, + 'attribute' => CT::T_ATTRIBUTE_CLOSE, + 'break' => T_BREAK, + 'case' => T_CASE, + 'catch' => T_CATCH, + 'class' => T_CLASS, + 'clone' => T_CLONE, + 'comment' => T_COMMENT, + 'const' => T_CONST, + 'const_import' => CT::T_CONST_IMPORT, + 'continue' => T_CONTINUE, + 'do' => T_DO, + 'echo' => T_ECHO, + 'else' => T_ELSE, + 'elseif' => T_ELSEIF, + 'enum' => null, + 'extends' => T_EXTENDS, + 'final' => T_FINAL, + 'finally' => T_FINALLY, + 'for' => T_FOR, + 'foreach' => T_FOREACH, + 'function' => T_FUNCTION, + 'function_import' => CT::T_FUNCTION_IMPORT, + 'global' => T_GLOBAL, + 'goto' => T_GOTO, + 'if' => T_IF, + 'implements' => T_IMPLEMENTS, + 'include' => T_INCLUDE, + 'include_once' => T_INCLUDE_ONCE, + 'instanceof' => T_INSTANCEOF, + 'insteadof' => T_INSTEADOF, + 'interface' => T_INTERFACE, + 'match' => null, + 'named_argument' => CT::T_NAMED_ARGUMENT_COLON, + 'namespace' => T_NAMESPACE, + 'new' => T_NEW, + 'open_tag_with_echo' => T_OPEN_TAG_WITH_ECHO, + 'php_doc' => T_DOC_COMMENT, + 'php_open' => T_OPEN_TAG, + 'print' => T_PRINT, + 'private' => T_PRIVATE, + 'protected' => T_PROTECTED, + 'public' => T_PUBLIC, + 'readonly' => null, + 'require' => T_REQUIRE, + 'require_once' => T_REQUIRE_ONCE, + 'return' => T_RETURN, + 'static' => T_STATIC, + 'switch' => T_SWITCH, + 'throw' => T_THROW, + 'trait' => T_TRAIT, + 'try' => T_TRY, + 'type_colon' => CT::T_TYPE_COLON, + 'use' => T_USE, + 'use_lambda' => CT::T_USE_LAMBDA, + 'use_trait' => CT::T_USE_TRAIT, + 'var' => T_VAR, + 'while' => T_WHILE, + 'yield' => T_YIELD, + 'yield_from' => T_YIELD_FROM, + ]; + + /** + * @var array + */ + private array $fixTokenMapFollowedByASingleSpace = []; + + /** + * @var array + */ + private array $fixTokenMapContainASingleSpace = []; + + /** + * @var array + */ + private array $fixTokenMapPrecededByASingleSpace = []; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensures a single space after language constructs.', + [ + new CodeSample( + ' [ + 'yield_from', + ], + 'constructs_followed_by_a_single_space' => [ + 'yield_from', + ], + ] + ), + + new CodeSample( + ' [ + 'use_lambda', + ], + 'constructs_followed_by_a_single_space' => [ + 'use_lambda', + ], + ] + ), + new CodeSample( + ' [ + 'echo', + ], + ] + ), + new CodeSample( + ' [ + 'yield_from', + ], + ] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before BracesFixer, FunctionDeclarationFixer, NullableTypeDeclarationFixer. + * Must run after ArraySyntaxFixer, ModernizeStrposFixer. + */ + public function getPriority(): int + { + return 36; + } + + public function isCandidate(Tokens $tokens): bool + { + $tokenKinds = [ + ...array_values($this->fixTokenMapContainASingleSpace), + ...array_values($this->fixTokenMapPrecededByASingleSpace), + ...array_values($this->fixTokenMapFollowedByASingleSpace), + ]; + + return $tokens->isAnyTokenKindsFound($tokenKinds) && !$tokens->hasAlternativeSyntax(); + } + + protected function configurePostNormalisation(): void + { + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + self::$tokenMapFollowedByASingleSpace['match'] = T_MATCH; + } + + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + self::$tokenMapFollowedByASingleSpace['readonly'] = T_READONLY; + } + + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + self::$tokenMapFollowedByASingleSpace['enum'] = T_ENUM; + } + + $this->fixTokenMapContainASingleSpace = []; + + foreach ($this->configuration['constructs_contain_a_single_space'] as $key) { + if (null !== self::$tokenMapContainASingleSpace[$key]) { + $this->fixTokenMapContainASingleSpace[$key] = self::$tokenMapContainASingleSpace[$key]; + } + } + + $this->fixTokenMapPrecededByASingleSpace = []; + + foreach ($this->configuration['constructs_preceded_by_a_single_space'] as $key) { + if (null !== self::$tokenMapPrecededByASingleSpace[$key]) { + $this->fixTokenMapPrecededByASingleSpace[$key] = self::$tokenMapPrecededByASingleSpace[$key]; + } + } + + $this->fixTokenMapFollowedByASingleSpace = []; + + foreach ($this->configuration['constructs_followed_by_a_single_space'] as $key) { + if (null !== self::$tokenMapFollowedByASingleSpace[$key]) { + $this->fixTokenMapFollowedByASingleSpace[$key] = self::$tokenMapFollowedByASingleSpace[$key]; + } + } + + if (isset($this->fixTokenMapFollowedByASingleSpace['public'])) { + $this->fixTokenMapFollowedByASingleSpace['constructor_public'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC; + } + + if (isset($this->fixTokenMapFollowedByASingleSpace['protected'])) { + $this->fixTokenMapFollowedByASingleSpace['constructor_protected'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED; + } + + if (isset($this->fixTokenMapFollowedByASingleSpace['private'])) { + $this->fixTokenMapFollowedByASingleSpace['constructor_private'] = CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE; + } + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokenKindsContainASingleSpace = array_values($this->fixTokenMapContainASingleSpace); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($tokenKindsContainASingleSpace)) { + $token = $tokens[$index]; + + if ( + $token->isGivenKind(T_YIELD_FROM) + && 'yield from' !== strtolower($token->getContent()) + ) { + $tokens[$index] = new Token([T_YIELD_FROM, Preg::replace( + '/\s+/', + ' ', + $token->getContent() + )]); + } + } + } + + $tokenKindsPrecededByASingleSpace = array_values($this->fixTokenMapPrecededByASingleSpace); + + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($tokenKindsPrecededByASingleSpace)) { + $tokens->ensureWhitespaceAtIndex($index - 1, 1, ' '); + } + } + + $tokenKindsFollowedByASingleSpace = array_values($this->fixTokenMapFollowedByASingleSpace); + + for ($index = $tokens->count() - 2; $index >= 0; --$index) { + $token = $tokens[$index]; + + if (!$token->isGivenKind($tokenKindsFollowedByASingleSpace)) { + continue; + } + + $whitespaceTokenIndex = $index + 1; + + if ($tokens[$whitespaceTokenIndex]->equalsAny([',', ';', ')', [CT::T_ARRAY_SQUARE_BRACE_CLOSE], [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]])) { + continue; + } + + if ( + $token->isGivenKind(T_STATIC) + && !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind([T_FN, T_FUNCTION, T_NS_SEPARATOR, T_STRING, T_VARIABLE, CT::T_ARRAY_TYPEHINT, CT::T_NULLABLE_TYPE]) + ) { + continue; + } + + if ($token->isGivenKind(T_OPEN_TAG)) { + if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && !str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n") && !str_contains($token->getContent(), "\n")) { + $tokens->clearAt($whitespaceTokenIndex); + } + + continue; + } + + if ($token->isGivenKind(T_CLASS) && $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(')) { + continue; + } + + if ($token->isGivenKind([T_EXTENDS, T_IMPLEMENTS]) && $this->isMultilineExtendsOrImplementsWithMoreThanOneAncestor($tokens, $index)) { + continue; + } + + if ($token->isGivenKind(T_RETURN) && $this->isMultiLineReturn($tokens, $index)) { + continue; + } + + if ($token->isGivenKind(T_CONST) && $this->isMultilineCommaSeparatedConstant($tokens, $index)) { + continue; + } + + if ($token->isComment() || $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { + continue; + } + } + + if ($tokens[$whitespaceTokenIndex]->isWhitespace() && str_contains($tokens[$whitespaceTokenIndex]->getContent(), "\n")) { + $nextNextToken = $tokens[$whitespaceTokenIndex + 1]; + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition and else when PHP 8.0+ is required + if ($nextNextToken->isGivenKind(T_ATTRIBUTE)) { + continue; + } + } else { + if ($nextNextToken->isComment() && str_starts_with($nextNextToken->getContent(), '#[')) { + continue; + } + } + + if ($nextNextToken->isGivenKind(T_DOC_COMMENT)) { + continue; + } + } + + $tokens->ensureWhitespaceAtIndex($whitespaceTokenIndex, 0, ' '); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $tokenMapContainASingleSpaceKeys = array_keys(self::$tokenMapContainASingleSpace); + $tokenMapPrecededByASingleSpaceKeys = array_keys(self::$tokenMapPrecededByASingleSpace); + $tokenMapFollowedByASingleSpaceKeys = array_keys(self::$tokenMapFollowedByASingleSpace); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('constructs_contain_a_single_space', 'List of constructs which must contain a single space.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset($tokenMapContainASingleSpaceKeys)]) + ->setDefault($tokenMapContainASingleSpaceKeys) + ->getOption(), + (new FixerOptionBuilder('constructs_preceded_by_a_single_space', 'List of constructs which must be preceded by a single space.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset($tokenMapPrecededByASingleSpaceKeys)]) + ->setDefault($tokenMapPrecededByASingleSpaceKeys) + ->getOption(), + (new FixerOptionBuilder('constructs_followed_by_a_single_space', 'List of constructs which must be followed by a single space.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset($tokenMapFollowedByASingleSpaceKeys)]) + ->setDefault($tokenMapFollowedByASingleSpaceKeys) + ->getOption(), + ]); + } + + private function isMultiLineReturn(Tokens $tokens, int $index): bool + { + ++$index; + $tokenFollowingReturn = $tokens[$index]; + + if ( + !$tokenFollowingReturn->isGivenKind(T_WHITESPACE) + || !str_contains($tokenFollowingReturn->getContent(), "\n") + ) { + return false; + } + + $nestedCount = 0; + + for ($indexEnd = \count($tokens) - 1, ++$index; $index < $indexEnd; ++$index) { + if (str_contains($tokens[$index]->getContent(), "\n")) { + return true; + } + + if ($tokens[$index]->equals('{')) { + ++$nestedCount; + } elseif ($tokens[$index]->equals('}')) { + --$nestedCount; + } elseif (0 === $nestedCount && $tokens[$index]->equalsAny([';', [T_CLOSE_TAG]])) { + break; + } + } + + return false; + } + + private function isMultilineExtendsOrImplementsWithMoreThanOneAncestor(Tokens $tokens, int $index): bool + { + $hasMoreThanOneAncestor = false; + + while (++$index) { + $token = $tokens[$index]; + + if ($token->equals(',')) { + $hasMoreThanOneAncestor = true; + + continue; + } + + if ($token->equals('{')) { + return false; + } + + if ($hasMoreThanOneAncestor && str_contains($token->getContent(), "\n")) { + return true; + } + } + + return false; + } + + private function isMultilineCommaSeparatedConstant(Tokens $tokens, int $constantIndex): bool + { + $isMultilineConstant = false; + $hasMoreThanOneConstant = false; + $index = $constantIndex; + while (!$tokens[$index]->equalsAny([';', [T_CLOSE_TAG]])) { + ++$index; + + $isMultilineConstant = $isMultilineConstant || str_contains($tokens[$index]->getContent(), "\n"); + + if ($tokens[$index]->equals(',')) { + $hasMoreThanOneConstant = true; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + + if (null !== $blockType && true === $blockType['isStart']) { + $index = $tokens->findBlockEnd($blockType['type'], $index); + } + } + + return $hasMoreThanOneConstant && $isMultilineConstant; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php index bdd35d7c60..3f550e6bcd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ListNotation/ListSyntaxFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,30 +28,26 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * syntax?: 'long'|'short' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * syntax: 'long'|'short' + * } + */ final class ListSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var null|int */ private $candidateTokenKind; - /** - * Use 'syntax' => 'long'|'short'. - * - * @param array $configuration - * - * @throws InvalidFixerConfigurationException - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN : T_LIST; - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -74,20 +71,22 @@ public function getDefinition(): FixerDefinitionInterface */ public function getPriority(): int { - return 1; + return 2; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound($this->candidateTokenKind); } /** - * {@inheritdoc} + * @throws InvalidFixerConfigurationException */ + protected function configurePostNormalisation(): void + { + $this->candidateTokenKind = 'long' === $this->configuration['syntax'] ? CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN : T_LIST; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; 0 <= $index; --$index) { @@ -101,13 +100,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` `list` syntax.')) + (new FixerOptionBuilder('syntax', 'Whether to use the `long` or `short` syntax for array destructuring.')) ->setAllowedValues(['long', 'short']) ->setDefault('short') ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php index 3ef36517cc..7c27bef6c3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLineAfterNamespaceFixer.php @@ -30,9 +30,6 @@ */ final class BlankLineAfterNamespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -54,17 +51,11 @@ public function getPriority(): int return -20; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_NAMESPACE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $lastIndex = $tokens->count() - 1; @@ -103,7 +94,7 @@ private function getIndexToEnsureBlankLineAfter(Tokens $tokens, int $index): int $token = $tokens[$nextIndex]; if ($token->isWhitespace()) { - if (1 === Preg::match('/\R/', $token->getContent())) { + if (Preg::match('/\R/', $token->getContent())) { break; } $nextNextIndex = $tokens->getNonEmptySibling($nextIndex, 1); @@ -129,7 +120,7 @@ private function getTokenToInsert(string $currentContent, bool $isLastIndex): To $ending = $this->whitespacesConfig->getLineEnding(); $emptyLines = $isLastIndex ? $ending : $ending.$ending; - $indent = 1 === Preg::match('/^.*\R( *)$/s', $currentContent, $matches) ? $matches[1] : ''; + $indent = Preg::match('/^.*\R( *)$/s', $currentContent, $matches) ? $matches[1] : ''; return new Token([T_WHITESPACE, $emptyLines.$indent]); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php new file mode 100644 index 0000000000..2a3a9456cf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php @@ -0,0 +1,242 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\NamespaceNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Options; + +/** + * @author Graham Campbell + * @author Greg Korba + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * max_line_breaks?: int, + * min_line_breaks?: int + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * max_line_breaks: int, + * min_line_breaks: int + * } + */ +final class BlankLinesBeforeNamespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Controls blank lines before a namespace declaration.', + [ + new CodeSample(" 1]), + new CodeSample(" 2]), + new CodeSample(" 2]), + new CodeSample(" 0, 'max_line_breaks' => 0]), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NAMESPACE); + } + + /** + * {@inheritdoc} + * + * Must run after BlankLineAfterOpeningTagFixer, HeaderCommentFixer. + */ + public function getPriority(): int + { + return -31; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('min_line_breaks', 'Minimum line breaks that should exist before namespace declaration.')) + ->setAllowedTypes(['int']) + ->setDefault(2) + ->setNormalizer(static function (Options $options, int $value): int { + if ($value < 0) { + throw new InvalidFixerConfigurationException( + (new self())->getName(), + 'Option `min_line_breaks` cannot be lower than 0.' + ); + } + + return $value; + }) + ->getOption(), + (new FixerOptionBuilder('max_line_breaks', 'Maximum line breaks that should exist before namespace declaration.')) + ->setAllowedTypes(['int']) + ->setDefault(2) + ->setNormalizer(static function (Options $options, int $value): int { + if ($value < 0) { + throw new InvalidFixerConfigurationException( + (new self())->getName(), + 'Option `max_line_breaks` cannot be lower than 0.' + ); + } + + if ($value < $options['min_line_breaks']) { + throw new InvalidFixerConfigurationException( + (new self())->getName(), + 'Option `max_line_breaks` cannot have lower value than `min_line_breaks`.' + ); + } + + return $value; + }) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + + if ($token->isGivenKind(T_NAMESPACE)) { + $this->fixLinesBeforeNamespace( + $tokens, + $index, + $this->configuration['min_line_breaks'], + $this->configuration['max_line_breaks'] + ); + } + } + } + + /** + * Make sure # of line breaks prefixing namespace is within given range. + * + * @param int $expectedMin min. # of line breaks + * @param int $expectedMax max. # of line breaks + */ + protected function fixLinesBeforeNamespace(Tokens $tokens, int $index, int $expectedMin, int $expectedMax): void + { + // Let's determine the total numbers of new lines before the namespace + // and the opening token + $openingTokenIndex = null; + $precedingNewlines = 0; + $newlineInOpening = false; + $openingToken = null; + + for ($i = 1; $i <= 2; ++$i) { + if (isset($tokens[$index - $i])) { + $token = $tokens[$index - $i]; + + if ($token->isGivenKind(T_OPEN_TAG)) { + $openingToken = $token; + $openingTokenIndex = $index - $i; + $newlineInOpening = str_contains($token->getContent(), "\n"); + + if ($newlineInOpening) { + ++$precedingNewlines; + } + + break; + } + + if (false === $token->isGivenKind(T_WHITESPACE)) { + break; + } + + $precedingNewlines += substr_count($token->getContent(), "\n"); + } + } + + if ($precedingNewlines >= $expectedMin && $precedingNewlines <= $expectedMax) { + return; + } + + $previousIndex = $index - 1; + $previous = $tokens[$previousIndex]; + + if (0 === $expectedMax) { + // Remove all the previous new lines + if ($previous->isWhitespace()) { + $tokens->clearAt($previousIndex); + } + + // Remove new lines in opening token + if ($newlineInOpening) { + $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, rtrim($openingToken->getContent()).' ']); + } + + return; + } + + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + // Allow only as many line breaks as configured: + // - keep as-is when current preceding line breaks are within configured range + // - use configured max line breaks if currently there is more preceding line breaks + // - use configured min line breaks if currently there is less preceding line breaks + $newlinesForWhitespaceToken = $precedingNewlines >= $expectedMax + ? $expectedMax + : max($precedingNewlines, $expectedMin); + + if (null !== $openingToken) { + // Use the configured line ending for the PHP opening tag + $content = rtrim($openingToken->getContent()); + $newContent = $content.$lineEnding; + $tokens[$openingTokenIndex] = new Token([T_OPEN_TAG, $newContent]); + --$newlinesForWhitespaceToken; + } + + if (0 === $newlinesForWhitespaceToken) { + // We have all the needed new lines in the opening tag + if ($previous->isWhitespace()) { + // Let's remove the previous token containing extra new lines + $tokens->clearAt($previousIndex); + } + + return; + } + + if ($previous->isWhitespace()) { + // Fix the previous whitespace token + $tokens[$previousIndex] = new Token( + [ + T_WHITESPACE, + str_repeat($lineEnding, $newlinesForWhitespaceToken).substr( + $previous->getContent(), + strrpos($previous->getContent(), "\n") + 1 + ), + ] + ); + } else { + // Add a new whitespace token + $tokens->insertAt($index, new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)])); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php index b38355801f..bb63adbd24 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/CleanNamespaceFixer.php @@ -14,26 +14,23 @@ namespace PhpCsFixer\Fixer\NamespaceNotation; -use PhpCsFixer\AbstractLinesBeforeNamespaceFixer; +use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\FixerDefinition\VersionSpecification; use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Tokenizer\Tokens; -final class CleanNamespaceFixer extends AbstractLinesBeforeNamespaceFixer +final class CleanNamespaceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { $samples = []; - foreach (['namespace Foo \\ Bar;', 'echo foo /* comment */ \\ bar();'] as $sample) { + foreach (['namespace Foo \ Bar;', 'echo foo /* comment */ \ bar();'] as $sample) { $samples[] = new VersionSpecificCodeSample( "isTokenKindFound(T_NS_SEPARATOR); + return \PHP_VERSION_ID < 8_00_00 && $tokens->isTokenKindFound(T_NS_SEPARATOR); } /** * {@inheritdoc} + * + * Must run before PhpUnitDataProviderReturnTypeFixer. */ + public function getPriority(): int + { + return 3; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $count = $tokens->count(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php index 96bc14b085..20dc38eaac 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoBlankLinesBeforeNamespaceFixer.php @@ -14,7 +14,9 @@ namespace PhpCsFixer\Fixer\NamespaceNotation; -use PhpCsFixer\AbstractLinesBeforeNamespaceFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -22,20 +24,21 @@ /** * @author Graham Campbell + * + * @deprecated Use `blank_lines_before_namespace` with config: ['min_line_breaks' => 0, 'max_line_breaks' => 1] */ -final class NoBlankLinesBeforeNamespaceFixer extends AbstractLinesBeforeNamespaceFixer +final class NoBlankLinesBeforeNamespaceFixer extends AbstractProxyFixer implements WhitespacesAwareFixerInterface, DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_NAMESPACE); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -58,19 +61,16 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function createProxyFixers(): array { - for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind(T_NAMESPACE)) { - continue; - } + $blankLineBeforeNamespace = new BlankLinesBeforeNamespaceFixer(); + $blankLineBeforeNamespace->configure([ + 'min_line_breaks' => 0, + 'max_line_breaks' => 1, + ]); - $this->fixLinesBeforeNamespace($tokens, $index, 0, 1); - } + return [ + $blankLineBeforeNamespace, + ]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php index 6a038c8f9b..0d866d4a81 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php @@ -28,17 +28,11 @@ */ final class NoLeadingNamespaceWhitespaceFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_NAMESPACE); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -54,9 +48,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; 0 <= $index; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php index 667b1da55f..67de6a243e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/NamespaceNotation/SingleBlankLineBeforeNamespaceFixer.php @@ -14,7 +14,9 @@ namespace PhpCsFixer\Fixer\NamespaceNotation; -use PhpCsFixer\AbstractLinesBeforeNamespaceFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -22,12 +24,16 @@ /** * @author Graham Campbell + * + * @deprecated Use `blank_lines_before_namespace` with config: ['min_line_breaks' => 2, 'max_line_breaks' => 2] (default) */ -final class SingleBlankLineBeforeNamespaceFixer extends AbstractLinesBeforeNamespaceFixer +final class SingleBlankLineBeforeNamespaceFixer extends AbstractProxyFixer implements WhitespacesAwareFixerInterface, DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -39,9 +45,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_NAMESPACE); @@ -49,23 +52,24 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} + * + * Must run after HeaderCommentFixer. */ public function getPriority(): int { - return -21; + return parent::getPriority(); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function createProxyFixers(): array { - for ($index = $tokens->count() - 1; $index >= 0; --$index) { - $token = $tokens[$index]; + $blankLineBeforeNamespace = new BlankLinesBeforeNamespaceFixer(); + $blankLineBeforeNamespace->configure([ + 'min_line_breaks' => 2, + 'max_line_breaks' => 2, + ]); - if ($token->isGivenKind(T_NAMESPACE)) { - $this->fixLinesBeforeNamespace($tokens, $index, 2, 2); - } - } + return [ + $blankLineBeforeNamespace, + ]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php index ae4cfb29e8..ff91d5c133 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Naming/NoHomoglyphNamesFixer.php @@ -45,6 +45,8 @@ final class NoHomoglyphNamesFixer extends AbstractFixer * * This is not the complete list of unicode homographs, but limited * to those you are more likely to have typed/copied by accident + * + * @var array */ private static array $replacements = [ 'O' => '0', @@ -191,9 +193,6 @@ final class NoHomoglyphNamesFixer extends AbstractFixer 'z' => 'z', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -204,25 +203,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_VARIABLE, T_STRING]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -230,11 +220,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - $replaced = Preg::replaceCallback('/[^[:ascii:]]/u', static function (array $matches): string { - return self::$replacements[$matches[0]] ?? $matches[0]; - }, $token->getContent(), -1, $count); + $replaced = Preg::replaceCallback('/[^[:ascii:]]/u', static fn (array $matches): string => self::$replacements[$matches[0]] ?? $matches[0], $token->getContent(), -1, $count); - if ($count) { + if ($count > 0) { $tokens->offsetSet($index, new Token([$token->getId(), $replaced])); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php index bbf1602d32..b1c37606ad 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php @@ -14,29 +14,22 @@ namespace PhpCsFixer\Fixer\Operator; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\AbstractShortOperatorFixer; +use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; -use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer; -use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -final class AssignNullCoalescingToCoalesceEqualFixer extends AbstractFixer +final class AssignNullCoalescingToCoalesceEqualFixer extends AbstractShortOperatorFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Use the null coalescing assignment operator `??=` where possible.', [ - new VersionSpecificCodeSample( + new CodeSample( "isTokenKindFound(T_COALESCE); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void - { - for ($index = \count($tokens) - 1; $index > 3; --$index) { - if (!$tokens[$index]->isGivenKind(T_COALESCE)) { - continue; - } - - // make sure after '??' does not contain '? :' - - $nextIndex = $tokens->getNextTokenOfKind($index, ['?', ';', [T_CLOSE_TAG]]); - - if ($tokens[$nextIndex]->equals('?')) { - continue; - } - - // get what is before '??' - - $beforeRange = $this->getBeforeOperator($tokens, $index); - $equalsIndex = $tokens->getPrevMeaningfulToken($beforeRange['start']); - - // make sure that before that is '=' - - if (!$tokens[$equalsIndex]->equals('=')) { - continue; - } - - // get what is before '=' - - $assignRange = $this->getBeforeOperator($tokens, $equalsIndex); - $beforeAssignmentIndex = $tokens->getPrevMeaningfulToken($assignRange['start']); - - // make sure that before that is ';', '{', '}', '(', ')' or 'equalsAny([';', '{', '}', ')', '(', [T_OPEN_TAG]])) { - continue; - } - - // make sure before and after are the same - - if (!RangeAnalyzer::rangeEqualsRange($tokens, $assignRange, $beforeRange)) { - continue; - } - - $tokens[$equalsIndex] = new Token([T_COALESCE_EQUAL, '??=']); - $tokens->clearTokenAndMergeSurroundingWhitespace($index); - $this->clearMeaningfulFromRange($tokens, $beforeRange); - - foreach ([$equalsIndex, $assignRange['end']] as $i) { - $i = $tokens->getNonEmptySibling($i, 1); - - if ($tokens[$i]->isWhitespace(" \t")) { - $tokens[$i] = new Token([T_WHITESPACE, ' ']); - } elseif (!$tokens[$i]->isWhitespace()) { - $tokens->insertAt($i, new Token([T_WHITESPACE, ' '])); - } - } - } - } - - private function getBeforeOperator(Tokens $tokens, int $index): array + protected function isOperatorTokenCandidate(Tokens $tokens, int $index): bool { - $controlStructureWithoutBracesTypes = [T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE]; - - $index = $tokens->getPrevMeaningfulToken($index); - $range = [ - 'start' => $index, - 'end' => $index, - ]; - - $previousIndex = $index; - $previousToken = $tokens[$previousIndex]; - - while ($previousToken->equalsAny([ - '$', - ']', - ')', - [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], - [CT::T_DYNAMIC_PROP_BRACE_CLOSE], - [CT::T_DYNAMIC_VAR_BRACE_CLOSE], - [T_NS_SEPARATOR], - [T_STRING], - [T_VARIABLE], - ])) { - $blockType = Tokens::detectBlockType($previousToken); - - if (null !== $blockType) { - $blockStart = $tokens->findBlockStart($blockType['type'], $previousIndex); - - if ($tokens[$previousIndex]->equals(')') && $tokens[$tokens->getPrevMeaningfulToken($blockStart)]->isGivenKind($controlStructureWithoutBracesTypes)) { - break; // we went too far back - } - - $previousIndex = $blockStart; - } - - $index = $previousIndex; - $previousIndex = $tokens->getPrevMeaningfulToken($previousIndex); - $previousToken = $tokens[$previousIndex]; + if (!$tokens[$index]->isGivenKind(T_COALESCE)) { + return false; } - if ($previousToken->isGivenKind(T_OBJECT_OPERATOR)) { - $index = $this->getBeforeOperator($tokens, $previousIndex)['start']; - } elseif ($previousToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { - $index = $this->getBeforeOperator($tokens, $tokens->getPrevMeaningfulToken($previousIndex))['start']; - } + // make sure after '??' does not contain '? :' - $range['start'] = $index; + $nextIndex = $tokens->getNextTokenOfKind($index, ['?', ';', [T_CLOSE_TAG]]); - return $range; + return !$tokens[$nextIndex]->equals('?'); } - private function clearMeaningfulFromRange(Tokens $tokens, array $range): void + protected function getReplacementToken(Token $token): Token { - // $range['end'] must be meaningful! - for ($i = $range['end']; $i >= $range['start']; $i = $tokens->getPrevMeaningfulToken($i)) { - $tokens->clearTokenAndMergeSurroundingWhitespace($i); - } + return new Token([T_COALESCE_EQUAL, '??=']); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php index 940b9a9440..cf2f84f44b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/BinaryOperatorSpacesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,18 +28,38 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * default?: 'align'|'align_by_scope'|'align_single_space'|'align_single_space_by_scope'|'align_single_space_minimal'|'align_single_space_minimal_by_scope'|'at_least_single_space'|'no_space'|'single_space'|null, + * operators?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * default: 'align'|'align_by_scope'|'align_single_space'|'align_single_space_by_scope'|'align_single_space_minimal'|'align_single_space_minimal_by_scope'|'at_least_single_space'|'no_space'|'single_space'|null, + * operators: array + * } */ final class BinaryOperatorSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ public const SINGLE_SPACE = 'single_space'; + /** + * @internal + */ + public const AT_LEAST_SINGLE_SPACE = 'at_least_single_space'; + /** * @internal */ @@ -49,11 +70,21 @@ final class BinaryOperatorSpacesFixer extends AbstractFixer implements Configura */ public const ALIGN = 'align'; + /** + * @internal + */ + public const ALIGN_BY_SCOPE = 'align_by_scope'; + /** * @internal */ public const ALIGN_SINGLE_SPACE = 'align_single_space'; + /** + * @internal + */ + public const ALIGN_SINGLE_SPACE_BY_SCOPE = 'align_single_space_by_scope'; + /** * @internal */ @@ -61,12 +92,18 @@ final class BinaryOperatorSpacesFixer extends AbstractFixer implements Configura /** * @internal + */ + public const ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE = 'align_single_space_minimal_by_scope'; + + /** + * @internal + * * @const Placeholder used as anchor for right alignment. */ public const ALIGN_PLACEHOLDER = "\x2 ALIGNABLE%d \x3"; /** - * @var string[] + * @var list */ private const SUPPORTED_OPERATORS = [ '=', @@ -128,14 +165,18 @@ final class BinaryOperatorSpacesFixer extends AbstractFixer implements Configura private int $currentLevel; /** - * @var array + * @var list */ private static array $allowedValues = [ self::ALIGN, + self::ALIGN_BY_SCOPE, self::ALIGN_SINGLE_SPACE, self::ALIGN_SINGLE_SPACE_MINIMAL, + self::ALIGN_SINGLE_SPACE_BY_SCOPE, + self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE, self::SINGLE_SPACE, self::NO_SPACE, + self::AT_LEAST_SINGLE_SPACE, null, ]; @@ -151,19 +192,6 @@ final class BinaryOperatorSpacesFixer extends AbstractFixer implements Configura */ private array $operators = []; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->operators = $this->resolveOperatorsFromConfig(); - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -220,6 +248,8 @@ public function getDefinition(): FixerDefinitionInterface $array = [ "foo" => 12, "baaaaaaaaaaar" => 13, + + "baz" => 1, ]; ', ['operators' => ['=>' => 'align']] @@ -229,6 +259,19 @@ public function getDefinition(): FixerDefinitionInterface $array = [ "foo" => 12, "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_by_scope']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, ]; ', ['operators' => ['=>' => 'align_single_space']] @@ -238,10 +281,34 @@ public function getDefinition(): FixerDefinitionInterface $array = [ "foo" => 12, "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_single_space_by_scope']] + ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, ]; ', ['operators' => ['=>' => 'align_single_space_minimal']] ), + new CodeSample( + ' 12, + "baaaaaaaaaaar" => 13, + + "baz" => 1, +]; +', + ['operators' => ['=>' => 'align_single_space_minimal_by_scope']] + ), ] ); } @@ -249,24 +316,23 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run after ArrayIndentationFixer, ArraySyntaxFixer, AssignNullCoalescingToCoalesceEqualFixer, ListSyntaxFixer, ModernizeStrposFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUnsetCastFixer, PowToExponentiationFixer, StandardizeNotEqualsFixer, StrictComparisonFixer. + * Must run after ArrayIndentationFixer, ArraySyntaxFixer, AssignNullCoalescingToCoalesceEqualFixer, ListSyntaxFixer, LongToShorthandOperatorFixer, ModernizeStrposFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUnsetCastFixer, PowToExponentiationFixer, StandardizeNotEqualsFixer, StrictComparisonFixer. */ public function getPriority(): int { return -32; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->operators = $this->resolveOperatorsFromConfig(); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $this->tokensAnalyzer = new TokensAnalyzer($tokens); @@ -297,9 +363,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -307,15 +370,15 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setDefault(self::SINGLE_SPACE) ->setAllowedValues(self::$allowedValues) ->getOption(), - (new FixerOptionBuilder('operators', 'Dictionary of `binary operator` => `fix strategy` values that differ from the default strategy. Supported are: `'.implode('`, `', self::SUPPORTED_OPERATORS).'`')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('operators', 'Dictionary of `binary operator` => `fix strategy` values that differ from the default strategy. Supported are: '.Utils::naturalLanguageJoinWithBackticks(self::SUPPORTED_OPERATORS).'.')) + ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $option): bool { foreach ($option as $operator => $value) { if (!\in_array($operator, self::SUPPORTED_OPERATORS, true)) { throw new InvalidOptionsException( - sprintf( - 'Unexpected "operators" key, expected any of "%s", got "%s".', - implode('", "', self::SUPPORTED_OPERATORS), + \sprintf( + 'Unexpected "operators" key, expected any of %s, got "%s".', + Utils::naturalLanguageJoin(self::SUPPORTED_OPERATORS), \gettype($operator).'#'.$operator ) ); @@ -323,10 +386,13 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn if (!\in_array($value, self::$allowedValues, true)) { throw new InvalidOptionsException( - sprintf( - 'Unexpected value for operator "%s", expected any of "%s", got "%s".', + \sprintf( + 'Unexpected value for operator "%s", expected any of %s, got "%s".', $operator, - implode('", "', self::$allowedValues), + Utils::naturalLanguageJoin(array_map( + static fn ($value): string => Utils::toString($value), + self::$allowedValues + )), \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) ) ); @@ -354,6 +420,12 @@ private function fixWhiteSpaceAroundOperator(Tokens $tokens, int $index): void return; } + if (self::AT_LEAST_SINGLE_SPACE === $this->operators[$tokenContent]) { + $this->fixWhiteSpaceAroundOperatorToAtLeastSingleSpace($tokens, $index); + + return; + } + if (self::NO_SPACE === $this->operators[$tokenContent]) { $this->fixWhiteSpaceAroundOperatorToNoSpace($tokens, $index); @@ -363,13 +435,19 @@ private function fixWhiteSpaceAroundOperator(Tokens $tokens, int $index): void // schedule for alignment $this->alignOperatorTokens[$tokenContent] = $this->operators[$tokenContent]; - if (self::ALIGN === $this->operators[$tokenContent]) { + if ( + self::ALIGN === $this->operators[$tokenContent] + || self::ALIGN_BY_SCOPE === $this->operators[$tokenContent] + ) { return; } // fix white space after operator if ($tokens[$index + 1]->isWhitespace()) { - if (self::ALIGN_SINGLE_SPACE_MINIMAL === $this->operators[$tokenContent]) { + if ( + self::ALIGN_SINGLE_SPACE_MINIMAL === $this->operators[$tokenContent] + || self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE === $this->operators[$tokenContent] + ) { $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); } @@ -402,6 +480,19 @@ private function fixWhiteSpaceAroundOperatorToSingleSpace(Tokens $tokens, int $i } } + private function fixWhiteSpaceAroundOperatorToAtLeastSingleSpace(Tokens $tokens, int $index): void + { + // fix white space after operator + if (!$tokens[$index + 1]->isWhitespace()) { + $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); + } + + // fix white space before operator + if (!$tokens[$index - 1]->isWhitespace()) { + $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); + } + } + private function fixWhiteSpaceAroundOperatorToNoSpace(Tokens $tokens, int $index): void { // fix white space after operator @@ -486,11 +577,16 @@ private function fixAlignment(Tokens $tokens, array $toAlign): void if ('=>' === $tokenContent) { $this->injectAlignmentPlaceholdersForArrow($tokensClone, 0, \count($tokens)); } else { - $this->injectAlignmentPlaceholders($tokensClone, 0, \count($tokens), $tokenContent); + $this->injectAlignmentPlaceholdersDefault($tokensClone, 0, \count($tokens), $tokenContent); } // for all tokens that should be aligned but do not have anything to align with, fix spacing if needed - if (self::ALIGN_SINGLE_SPACE === $alignStrategy || self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy) { + if ( + self::ALIGN_SINGLE_SPACE === $alignStrategy + || self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy + || self::ALIGN_SINGLE_SPACE_BY_SCOPE === $alignStrategy + || self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE === $alignStrategy + ) { if ('=>' === $tokenContent) { for ($index = $tokens->count() - 2; $index > 0; --$index) { if ($tokens[$index]->isGivenKind(T_DOUBLE_ARROW)) { // always binary operator, never part of declare statement @@ -513,39 +609,72 @@ private function fixAlignment(Tokens $tokens, array $toAlign): void } } - $tokens->setCode($this->replacePlaceholders($tokensClone, $alignStrategy)); + $tokens->setCode($this->replacePlaceholders($tokensClone, $alignStrategy, $tokenContent)); } } - private function injectAlignmentPlaceholders(Tokens $tokens, int $startAt, int $endAt, string $tokenContent): void + private function injectAlignmentPlaceholdersDefault(Tokens $tokens, int $startAt, int $endAt, string $tokenContent): void { - $functionKind = [T_FUNCTION, T_FN]; + $newLineFoundSinceLastPlaceholder = true; for ($index = $startAt; $index < $endAt; ++$index) { $token = $tokens[$index]; $content = $token->getContent(); + if (str_contains($content, "\n")) { + $newLineFoundSinceLastPlaceholder = true; + } + if ( strtolower($content) === $tokenContent && $this->tokensAnalyzer->isBinaryOperator($index) && ('=' !== $content || !$this->isEqualPartOfDeclareStatement($tokens, $index)) + && $newLineFoundSinceLastPlaceholder ) { - $tokens[$index] = new Token(sprintf(self::ALIGN_PLACEHOLDER, $this->deepestLevel).$content); + $tokens[$index] = new Token(\sprintf(self::ALIGN_PLACEHOLDER, $this->currentLevel).$content); + $newLineFoundSinceLastPlaceholder = false; continue; } - if ($token->isGivenKind($functionKind)) { - ++$this->deepestLevel; - $index = $tokens->getNextTokenOfKind($index, ['(']); - $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + if ($token->isGivenKind(T_FN)) { + $from = $tokens->getNextMeaningfulToken($index); + $until = $this->tokensAnalyzer->getLastTokenIndexOfArrowFunction($index); + $this->injectAlignmentPlaceholders($tokens, $from + 1, $until - 1, $tokenContent); + $index = $until; continue; } - if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE, T_IF, T_SWITCH, T_ELSEIF])) { - $index = $tokens->getNextMeaningfulToken($index); - $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + if ($token->isGivenKind([T_FUNCTION, T_CLASS])) { + $index = $tokens->getNextTokenOfKind($index, ['{', ';', '(']); + // We don't align `=` on multi-line definition of function parameters with default values + if ($tokens[$index]->equals('(')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + + continue; + } + + if ($tokens[$index]->equals(';')) { + continue; + } + + // Update the token to the `{` one in order to apply the following logic + $token = $tokens[$index]; + } + + if ($token->equals('{')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $this->injectAlignmentPlaceholders($tokens, $index + 1, $until - 1, $tokenContent); + $index = $until; + + continue; + } + + if ($token->equals('(')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $this->injectAlignmentPlaceholders($tokens, $index + 1, $until - 1, $tokenContent); + $index = $until; continue; } @@ -557,26 +686,57 @@ private function injectAlignmentPlaceholders(Tokens $tokens, int $startAt, int $ } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { - $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index); + $this->injectAlignmentPlaceholders($tokens, $index + 1, $until - 1, $tokenContent); + $index = $until; continue; } } } + private function injectAlignmentPlaceholders(Tokens $tokens, int $from, int $until, string $tokenContent): void + { + // Only inject placeholders for multi-line code + if ($tokens->isPartialCodeMultiline($from, $until)) { + ++$this->deepestLevel; + $currentLevel = $this->currentLevel; + $this->currentLevel = $this->deepestLevel; + $this->injectAlignmentPlaceholdersDefault($tokens, $from, $until, $tokenContent); + $this->currentLevel = $currentLevel; + } + } + private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startAt, int $endAt): void { + $newLineFoundSinceLastPlaceholder = true; + $yieldFoundSinceLastPlaceholder = false; + for ($index = $startAt; $index < $endAt; ++$index) { + /** @var Token $token */ $token = $tokens[$index]; + $content = $token->getContent(); + + if (str_contains($content, "\n")) { + $newLineFoundSinceLastPlaceholder = true; + } - if ($token->isGivenKind([T_FOREACH, T_FOR, T_WHILE, T_IF, T_SWITCH, T_ELSEIF])) { - $index = $tokens->getNextMeaningfulToken($index); - $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + if ($token->isGivenKind(T_YIELD)) { + $yieldFoundSinceLastPlaceholder = true; + } + + if ($token->isGivenKind(T_FN)) { + $yieldFoundSinceLastPlaceholder = false; + $from = $tokens->getNextMeaningfulToken($index); + $until = $this->tokensAnalyzer->getLastTokenIndexOfArrowFunction($index); + $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1); + $index = $until; continue; } if ($token->isGivenKind(T_ARRAY)) { // don't use "$tokens->isArray()" here, short arrays are handled in the next case + $yieldFoundSinceLastPlaceholder = false; $from = $tokens->getNextMeaningfulToken($index); $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $from); $index = $until; @@ -587,6 +747,7 @@ private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startA } if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + $yieldFoundSinceLastPlaceholder = false; $from = $index; $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $from); $index = $until; @@ -596,8 +757,14 @@ private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startA continue; } - if ($token->isGivenKind(T_DOUBLE_ARROW)) { // no need to analyze for `isBinaryOperator` (always true), nor if part of declare statement (not valid PHP) - $tokenContent = sprintf(self::ALIGN_PLACEHOLDER, $this->currentLevel).$token->getContent(); + // no need to analyze for `isBinaryOperator` (always true), nor if part of declare statement (not valid PHP) + // there is also no need to analyse the second arrow of a line + if ($token->isGivenKind(T_DOUBLE_ARROW) && $newLineFoundSinceLastPlaceholder) { + if ($yieldFoundSinceLastPlaceholder) { + ++$this->deepestLevel; + ++$this->currentLevel; + } + $tokenContent = \sprintf(self::ALIGN_PLACEHOLDER, $this->currentLevel).$token->getContent(); $nextToken = $tokens[$index + 1]; if (!$nextToken->isWhitespace()) { @@ -607,6 +774,8 @@ private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startA } $tokens[$index] = new Token([T_DOUBLE_ARROW, $tokenContent]); + $newLineFoundSinceLastPlaceholder = false; + $yieldFoundSinceLastPlaceholder = false; continue; } @@ -621,14 +790,15 @@ private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startA if ($token->equals(',')) { for ($i = $index; $i < $endAt - 1; ++$i) { if (str_contains($tokens[$i - 1]->getContent(), "\n")) { + $newLineFoundSinceLastPlaceholder = true; + break; } if ($tokens[$i + 1]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) { $arrayStartIndex = $tokens[$i + 1]->isGivenKind(T_ARRAY) ? $tokens->getNextMeaningfulToken($i + 1) - : $i + 1 - ; + : $i + 1; $blockType = Tokens::detectBlockType($tokens[$arrayStartIndex]); $arrayEndIndex = $tokens->findBlockEnd($blockType['type'], $arrayStartIndex); @@ -640,6 +810,22 @@ private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startA ++$index; } } + + if ($token->equals('{')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $this->injectArrayAlignmentPlaceholders($tokens, $index + 1, $until - 1); + $index = $until; + + continue; + } + + if ($token->equals('(')) { + $until = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $this->injectArrayAlignmentPlaceholders($tokens, $index + 1, $until - 1); + $index = $until; + + continue; + } } } @@ -648,9 +834,10 @@ private function injectArrayAlignmentPlaceholders(Tokens $tokens, int $from, int // Only inject placeholders for multi-line arrays if ($tokens->isPartialCodeMultiline($from, $until)) { ++$this->deepestLevel; - ++$this->currentLevel; + $currentLevel = $this->currentLevel; + $this->currentLevel = $this->deepestLevel; $this->injectAlignmentPlaceholdersForArrow($tokens, $from, $until); - --$this->currentLevel; + $this->currentLevel = $currentLevel; } } @@ -663,7 +850,10 @@ private function fixWhiteSpaceBeforeOperator(Tokens $tokens, int $index, string return; } - if (self::ALIGN_SINGLE_SPACE_MINIMAL !== $alignStrategy || $tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) { + if ( + self::ALIGN_SINGLE_SPACE_MINIMAL !== $alignStrategy && self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE !== $alignStrategy + || $tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment() + ) { return; } @@ -676,12 +866,12 @@ private function fixWhiteSpaceBeforeOperator(Tokens $tokens, int $index, string /** * Look for group of placeholders and provide vertical alignment. */ - private function replacePlaceholders(Tokens $tokens, string $alignStrategy): string + private function replacePlaceholders(Tokens $tokens, string $alignStrategy, string $tokenContent): string { $tmpCode = $tokens->generateCode(); for ($j = 0; $j <= $this->deepestLevel; ++$j) { - $placeholder = sprintf(self::ALIGN_PLACEHOLDER, $j); + $placeholder = \sprintf(self::ALIGN_PLACEHOLDER, $j); if (!str_contains($tmpCode, $placeholder)) { continue; @@ -695,7 +885,11 @@ private function replacePlaceholders(Tokens $tokens, string $alignStrategy): str foreach ($lines as $index => $line) { if (substr_count($line, $placeholder) > 0) { $groups[$groupIndex][] = $index; - } else { + } elseif ( + self::ALIGN_BY_SCOPE !== $alignStrategy + && self::ALIGN_SINGLE_SPACE_BY_SCOPE !== $alignStrategy + && self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE !== $alignStrategy + ) { ++$groupIndex; $groups[$groupIndex] = []; } @@ -712,12 +906,18 @@ private function replacePlaceholders(Tokens $tokens, string $alignStrategy): str $currentPosition = strpos($lines[$index], $placeholder); $before = substr($lines[$index], 0, $currentPosition); - if (self::ALIGN_SINGLE_SPACE === $alignStrategy) { + if ( + self::ALIGN_SINGLE_SPACE === $alignStrategy + || self::ALIGN_SINGLE_SPACE_BY_SCOPE === $alignStrategy + ) { if (!str_ends_with($before, ' ')) { // if last char of before-content is not ' '; add it $before .= ' '; } - } elseif (self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy) { - if (1 !== Preg::match('/^\h+$/', $before)) { // if indent; do not move, leave to other fixer + } elseif ( + self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy + || self::ALIGN_SINGLE_SPACE_MINIMAL_BY_SCOPE === $alignStrategy + ) { + if (!Preg::match('/^\h+$/', $before)) { // if indent; do not move, leave to other fixer $before = rtrim($before).' '; } } @@ -728,12 +928,12 @@ private function replacePlaceholders(Tokens $tokens, string $alignStrategy): str $rightmostSymbol = 0; foreach ($group as $index) { - $rightmostSymbol = max($rightmostSymbol, strpos(utf8_decode($lines[$index]), $placeholder)); + $rightmostSymbol = max($rightmostSymbol, mb_strpos($lines[$index], $placeholder)); } foreach ($group as $index) { $line = $lines[$index]; - $currentSymbol = strpos(utf8_decode($line), $placeholder); + $currentSymbol = mb_strpos($line, $placeholder); $delta = abs($rightmostSymbol - $currentSymbol); if ($delta > 0) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php index c0b87eb436..c9d962c376 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ConcatSpaceFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,35 +28,25 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * spacing?: 'none'|'one' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * spacing: 'none'|'one' + * } */ final class ConcatSpaceFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * @var null|string - */ - private $fixCallback; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - if ('one' === $this->configuration['spacing']) { - $this->fixCallback = 'fixConcatenationToSingleSpace'; - } else { - $this->fixCallback = 'fixConcatenationToNoSpace'; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Concatenation should be spaced according configuration.', + 'Concatenation should be spaced according to configuration.', [ new CodeSample( "isTokenKindFound('.'); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $callBack = $this->fixCallback; for ($index = $tokens->count() - 1; $index >= 0; --$index) { if ($tokens[$index]->equals('.')) { - $this->{$callBack}($tokens, $index); + if ('one' === $this->configuration['spacing']) { + $this->fixConcatenationToSingleSpace($tokens, $index); + } else { + $this->fixConcatenationToNoSpace($tokens, $index); + } } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -147,10 +132,17 @@ private function fixConcatenationToSingleSpace(Tokens $tokens, int $index): void */ private function fixWhiteSpaceAroundConcatToken(Tokens $tokens, int $index, int $offset): void { + if (-1 !== $offset && 1 !== $offset) { + throw new \InvalidArgumentException(\sprintf( + 'Expected `-1|1` for "$offset", got "%s"', + $offset + )); + } + $offsetIndex = $index + $offset; if (!$tokens[$offsetIndex]->isWhitespace()) { - $tokens->insertAt($index + (1 === $offset ?: 0), new Token([T_WHITESPACE, ' '])); + $tokens->insertAt($index + (1 === $offset ? 1 : 0), new Token([T_WHITESPACE, ' '])); return; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php index 1c2ae5f005..bc5de5bd3f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/IncrementStyleFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractIncrementOperatorFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,9 +30,21 @@ /** * @author Gregor Harlan * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * style?: 'post'|'pre' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * style: 'post'|'pre' + * } */ final class IncrementStyleFixer extends AbstractIncrementOperatorFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -42,9 +55,6 @@ final class IncrementStyleFixer extends AbstractIncrementOperatorFixer implement */ public const STYLE_POST = 'post'; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -62,24 +72,19 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * + * Must run before NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. * Must run after StandardizeIncrementFixer. */ public function getPriority(): int { - return 0; + return 15; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_INC, T_DEC]); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -90,9 +95,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php index 583e2a9afb..5e05aac690 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LogicalOperatorsFixer.php @@ -26,9 +26,6 @@ */ final class LogicalOperatorsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -47,25 +44,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_LOGICAL_AND, T_LOGICAL_OR]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LongToShorthandOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LongToShorthandOperatorFixer.php new file mode 100644 index 0000000000..fad4c48a6d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/LongToShorthandOperatorFixer.php @@ -0,0 +1,138 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\Fixer\AbstractShortOperatorFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +final class LongToShorthandOperatorFixer extends AbstractShortOperatorFixer +{ + /** + * @var array + */ + private static array $operators = [ + '+' => [T_PLUS_EQUAL, '+='], + '-' => [T_MINUS_EQUAL, '-='], + '*' => [T_MUL_EQUAL, '*='], + '/' => [T_DIV_EQUAL, '/='], + '&' => [T_AND_EQUAL, '&='], + '.' => [T_CONCAT_EQUAL, '.='], + '%' => [T_MOD_EQUAL, '%='], + '|' => [T_OR_EQUAL, '|='], + '^' => [T_XOR_EQUAL, '^='], + ]; + + /** + * @var list + */ + private array $operatorTypes; + + private TokensAnalyzer $tokensAnalyzer; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Shorthand notation for operators should be used if possible.', + [ + new CodeSample("isAnyTokenKindsFound(array_keys(self::$operators))) { + return true; + } + + // @TODO: drop condition when PHP 8.0 is required and the "&" issues went away + return \defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG'); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->operatorTypes = array_keys(self::$operators); + $this->tokensAnalyzer = new TokensAnalyzer($tokens); + + parent::applyFix($file, $tokens); + } + + protected function isOperatorTokenCandidate(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equalsAny($this->operatorTypes)) { + return false; + } + + while (null !== $index) { + $index = $tokens->getNextMeaningfulToken($index); + $otherToken = $tokens[$index]; + + if ($otherToken->equalsAny([';', [T_CLOSE_TAG]])) { + return true; + } + + // fast precedence check + if ($otherToken->equals('?') || $otherToken->isGivenKind(T_INSTANCEOF)) { + return false; + } + + $blockType = Tokens::detectBlockType($otherToken); + + if (null !== $blockType) { + if (false === $blockType['isStart']) { + return true; + } + + $index = $tokens->findBlockEnd($blockType['type'], $index); + + continue; + } + + // precedence check + if ($this->tokensAnalyzer->isBinaryOperator($index)) { + return false; + } + } + + return false; // unreachable, but keeps SCA happy + } + + protected function getReplacementToken(Token $token): Token + { + return new Token(self::$operators[$token->getContent()]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php index 83cdccfb66..865f689731 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithBracesFixer.php @@ -14,41 +14,53 @@ namespace PhpCsFixer\Fixer\Operator; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; -use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; -use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; -use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; /** * @author Dariusz Rumiński + * + * @deprecated + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * anonymous_class?: bool, + * named_class?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * anonymous_class: bool, + * named_class: bool + * } */ -final class NewWithBracesFixer extends AbstractFixer implements ConfigurableFixerInterface +final class NewWithBracesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private NewWithParenthesesFixer $newWithParenthesesFixer; + + public function __construct() + { + $this->newWithParenthesesFixer = new NewWithParenthesesFixer(); + + parent::__construct(); + } + public function getDefinition(): FixerDefinitionInterface { + $fixerDefinition = $this->newWithParenthesesFixer->getDefinition(); + return new FixerDefinition( 'All instances created with `new` keyword must (not) be followed by braces.', - [ - new CodeSample(" false] - ), - new CodeSample( - " false] - ), - ] + $fixerDefinition->getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription(), ); } @@ -59,154 +71,33 @@ public function getDefinition(): FixerDefinitionInterface */ public function getPriority(): int { - return 37; + return $this->newWithParenthesesFixer->getPriority(); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isTokenKindFound(T_NEW); + return [ + $this->newWithParenthesesFixer->getName(), + ]; } /** - * {@inheritdoc} + * @param _AutogeneratedInputConfiguration $configuration */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function configurePreNormalisation(array $configuration): void { - static $nextTokenKinds = null; - - if (null === $nextTokenKinds) { - $nextTokenKinds = [ - '?', - ';', - ',', - '(', - ')', - '[', - ']', - ':', - '<', - '>', - '+', - '-', - '*', - '/', - '%', - '&', - '^', - '|', - [T_CLASS], - [T_IS_SMALLER_OR_EQUAL], - [T_IS_GREATER_OR_EQUAL], - [T_IS_EQUAL], - [T_IS_NOT_EQUAL], - [T_IS_IDENTICAL], - [T_IS_NOT_IDENTICAL], - [T_CLOSE_TAG], - [T_LOGICAL_AND], - [T_LOGICAL_OR], - [T_LOGICAL_XOR], - [T_BOOLEAN_AND], - [T_BOOLEAN_OR], - [T_SL], - [T_SR], - [T_INSTANCEOF], - [T_AS], - [T_DOUBLE_ARROW], - [T_POW], - [T_SPACESHIP], - [CT::T_ARRAY_SQUARE_BRACE_OPEN], - [CT::T_ARRAY_SQUARE_BRACE_CLOSE], - [CT::T_BRACE_CLASS_INSTANTIATION_OPEN], - [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE], - ]; - - if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required - $nextTokenKinds[] = [T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG]; - $nextTokenKinds[] = [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]; - } - } - - for ($index = $tokens->count() - 3; $index > 0; --$index) { - if (!$tokens[$index]->isGivenKind(T_NEW)) { - continue; - } - - $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds); - - // new anonymous class definition - if ($tokens[$nextIndex]->isGivenKind(T_CLASS)) { - $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); - - if ($this->configuration['anonymous_class']) { - $this->ensureBracesAt($tokens, $nextIndex); - } else { - $this->ensureNoBracesAt($tokens, $nextIndex); - } - - continue; - } - - // entrance into array index syntax - need to look for exit - - while ($tokens[$nextIndex]->equals('[') || $tokens[$nextIndex]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) { - $nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($tokens[$nextIndex])['type'], $nextIndex); - $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); - } - - if ($this->configuration['named_class']) { - $this->ensureBracesAt($tokens, $nextIndex); - } else { - $this->ensureNoBracesAt($tokens, $nextIndex); - } - } + $this->newWithParenthesesFixer->configure($configuration); } - /** - * {@inheritdoc} - */ - protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + protected function createProxyFixers(): array { - return new FixerConfigurationResolver([ - (new FixerOptionBuilder('named_class', 'Whether named classes should be followed by parentheses.')) - ->setAllowedTypes(['bool']) - ->setDefault(true) - ->getOption(), - (new FixerOptionBuilder('anonymous_class', 'Whether anonymous classes should be followed by parentheses.')) - ->setAllowedTypes(['bool']) - ->setDefault(true) - ->getOption(), - ]); - } - - private function ensureBracesAt(Tokens $tokens, int $index): void - { - $token = $tokens[$index]; - - if (!$token->equals('(') && !$token->isObjectOperator()) { - $tokens->insertAt( - $tokens->getPrevMeaningfulToken($index) + 1, - [new Token('('), new Token(')')] - ); - } + return [ + $this->newWithParenthesesFixer, + ]; } - private function ensureNoBracesAt(Tokens $tokens, int $index): void + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - if (!$tokens[$index]->equals('(')) { - return; - } - - $closingIndex = $tokens->getNextMeaningfulToken($index); - - // constructor has arguments - braces can not be removed - if (!$tokens[$closingIndex]->equals(')')) { - return; - } - - $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex); - $tokens->clearTokenAndMergeSurroundingWhitespace($index); + return $this->newWithParenthesesFixer->createConfigurationDefinition(); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithParenthesesFixer.php new file mode 100644 index 0000000000..e1621fc81d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NewWithParenthesesFixer.php @@ -0,0 +1,216 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * anonymous_class?: bool, + * named_class?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * anonymous_class: bool, + * named_class: bool + * } + */ +final class NewWithParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'All instances created with `new` keyword must (not) be followed by parentheses.', + [ + new CodeSample(" false] + ), + new CodeSample( + " false] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before ClassDefinitionFixer. + */ + public function getPriority(): int + { + return 37; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NEW); + } + + /** @protected */ + public function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('named_class', 'Whether named classes should be followed by parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + (new FixerOptionBuilder('anonymous_class', 'Whether anonymous classes should be followed by parentheses.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $nextTokenKinds = null; + + if (null === $nextTokenKinds) { + $nextTokenKinds = [ + '?', + ';', + ',', + '(', + ')', + '[', + ']', + ':', + '<', + '>', + '+', + '-', + '*', + '/', + '%', + '&', + '^', + '|', + [T_CLASS], + [T_IS_SMALLER_OR_EQUAL], + [T_IS_GREATER_OR_EQUAL], + [T_IS_EQUAL], + [T_IS_NOT_EQUAL], + [T_IS_IDENTICAL], + [T_IS_NOT_IDENTICAL], + [T_CLOSE_TAG], + [T_LOGICAL_AND], + [T_LOGICAL_OR], + [T_LOGICAL_XOR], + [T_BOOLEAN_AND], + [T_BOOLEAN_OR], + [T_SL], + [T_SR], + [T_INSTANCEOF], + [T_AS], + [T_DOUBLE_ARROW], + [T_POW], + [T_SPACESHIP], + [CT::T_ARRAY_SQUARE_BRACE_OPEN], + [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_BRACE_CLASS_INSTANTIATION_OPEN], + [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE], + ]; + + if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required + $nextTokenKinds[] = [T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG]; + $nextTokenKinds[] = [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]; + } + } + + for ($index = $tokens->count() - 3; $index > 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_NEW)) { + continue; + } + + $nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds); + + // new anonymous class definition + if ($tokens[$nextIndex]->isGivenKind(T_CLASS)) { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (true === $this->configuration['anonymous_class']) { + $this->ensureParenthesesAt($tokens, $nextIndex); + } else { + $this->ensureNoParenthesesAt($tokens, $nextIndex); + } + + continue; + } + + // entrance into array index syntax - need to look for exit + + while ($tokens[$nextIndex]->equals('[') || $tokens[$nextIndex]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) { + $nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($tokens[$nextIndex])['type'], $nextIndex); + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + + if (true === $this->configuration['named_class']) { + $this->ensureParenthesesAt($tokens, $nextIndex); + } else { + $this->ensureNoParenthesesAt($tokens, $nextIndex); + } + } + } + + private function ensureParenthesesAt(Tokens $tokens, int $index): void + { + $token = $tokens[$index]; + + if (!$token->equals('(') && !$token->isObjectOperator()) { + $tokens->insertAt( + $tokens->getPrevMeaningfulToken($index) + 1, + [new Token('('), new Token(')')] + ); + } + } + + private function ensureNoParenthesesAt(Tokens $tokens, int $index): void + { + if (!$tokens[$index]->equals('(')) { + return; + } + + $closingIndex = $tokens->getNextMeaningfulToken($index); + + // constructor has arguments - parentheses can not be removed + if (!$tokens[$closingIndex]->equals(')')) { + return; + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php index 8d0bec3b84..d897fff595 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoSpaceAroundDoubleColonFixer.php @@ -22,28 +22,29 @@ final class NoSpaceAroundDoubleColonFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'There must be no space around double colons (also called Scope Resolution Operator or Paamayim Nekudotayim).', - [new CodeSample("\nisTokenKindFound(T_DOUBLE_COLON); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 2; $index > 1; --$index) { @@ -54,6 +55,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + /** + * @param -1|1 $direction + */ private function removeSpace(Tokens $tokens, int $index, int $direction): void { if (!$tokens[$index + $direction]->isWhitespace()) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessConcatOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessConcatOperatorFixer.php new file mode 100644 index 0000000000..f42196d2a5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessConcatOperatorFixer.php @@ -0,0 +1,375 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @phpstan-type _ConcatOperandType array{ + * start: int, + * end: int, + * type: self::STR_*, + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * juggle_simple_strings?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * juggle_simple_strings: bool + * } + */ +final class NoUselessConcatOperatorFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private const STR_DOUBLE_QUOTE = 0; + private const STR_DOUBLE_QUOTE_VAR = 1; + private const STR_SINGLE_QUOTE = 2; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be useless concat operations.', + [ + new CodeSample(" true]), + ], + ); + } + + /** + * {@inheritdoc} + * + * Must run before DateTimeCreateFromFormatCallFixer, EregToPregFixer, PhpUnitDedicateAssertInternalTypeFixer, RegularCallableCallFixer, SetTypeToCastFixer. + * Must run after ExplicitStringVariableFixer, NoBinaryStringFixer, SingleQuoteFixer. + */ + public function getPriority(): int + { + return 5; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound('.') && $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, '"']); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index > 0; --$index) { + if (!$tokens[$index]->equals('.')) { + continue; + } + + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index); + + if ($this->containsLinebreak($tokens, $index, $nextMeaningfulTokenIndex)) { + continue; + } + + $secondOperand = $this->getConcatOperandType($tokens, $nextMeaningfulTokenIndex, 1); + + if (null === $secondOperand) { + continue; + } + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index); + + if ($this->containsLinebreak($tokens, $prevMeaningfulTokenIndex, $index)) { + continue; + } + + $firstOperand = $this->getConcatOperandType($tokens, $prevMeaningfulTokenIndex, -1); + + if (null === $firstOperand) { + continue; + } + + $this->fixConcatOperation($tokens, $firstOperand, $index, $secondOperand); + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('juggle_simple_strings', 'Allow for simple string quote juggling if it results in more concat-operations merges.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + /** + * @param _ConcatOperandType $firstOperand + * @param _ConcatOperandType $secondOperand + */ + private function fixConcatOperation(Tokens $tokens, array $firstOperand, int $concatIndex, array $secondOperand): void + { + // if both operands are of the same type then these operands can always be merged + + if ( + (self::STR_DOUBLE_QUOTE === $firstOperand['type'] && self::STR_DOUBLE_QUOTE === $secondOperand['type']) + || (self::STR_SINGLE_QUOTE === $firstOperand['type'] && self::STR_SINGLE_QUOTE === $secondOperand['type']) + ) { + $this->mergeConstantEscapedStringOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + + return; + } + + if (self::STR_DOUBLE_QUOTE_VAR === $firstOperand['type'] && self::STR_DOUBLE_QUOTE_VAR === $secondOperand['type']) { + if ($this->operandsCanNotBeMerged($tokens, $firstOperand, $secondOperand)) { + return; + } + + $this->mergeConstantEscapedStringVarOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + + return; + } + + // if any is double and the other is not, check for simple other, than merge with " + + $operands = [ + [$firstOperand, $secondOperand], + [$secondOperand, $firstOperand], + ]; + + foreach ($operands as $operandPair) { + [$operand1, $operand2] = $operandPair; + + if (self::STR_DOUBLE_QUOTE_VAR === $operand1['type'] && self::STR_DOUBLE_QUOTE === $operand2['type']) { + if ($this->operandsCanNotBeMerged($tokens, $operand1, $operand2)) { + return; + } + + $this->mergeConstantEscapedStringVarOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + + return; + } + + if (false === $this->configuration['juggle_simple_strings']) { + continue; + } + + if (self::STR_DOUBLE_QUOTE === $operand1['type'] && self::STR_SINGLE_QUOTE === $operand2['type']) { + $operantContent = $tokens[$operand2['start']]->getContent(); + + if ($this->isSimpleQuotedStringContent($operantContent)) { + $this->mergeConstantEscapedStringOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + } + + return; + } + + if (self::STR_DOUBLE_QUOTE_VAR === $operand1['type'] && self::STR_SINGLE_QUOTE === $operand2['type']) { + $operantContent = $tokens[$operand2['start']]->getContent(); + + if ($this->isSimpleQuotedStringContent($operantContent)) { + if ($this->operandsCanNotBeMerged($tokens, $operand1, $operand2)) { + return; + } + + $this->mergeConstantEscapedStringVarOperands($tokens, $firstOperand, $concatIndex, $secondOperand); + } + + return; + } + } + } + + /** + * @param -1|1 $direction + * + * @return null|_ConcatOperandType + */ + private function getConcatOperandType(Tokens $tokens, int $index, int $direction): ?array + { + if ($tokens[$index]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $firstChar = $tokens[$index]->getContent(); + + if ('b' === $firstChar[0] || 'B' === $firstChar[0]) { + return null; // we don't care about these, priorities are set to do deal with these cases + } + + return [ + 'start' => $index, + 'end' => $index, + 'type' => '"' === $firstChar[0] ? self::STR_DOUBLE_QUOTE : self::STR_SINGLE_QUOTE, + ]; + } + + if ($tokens[$index]->equals('"')) { + $end = $tokens->getTokenOfKindSibling($index, $direction, ['"']); + + return [ + 'start' => 1 === $direction ? $index : $end, + 'end' => 1 === $direction ? $end : $index, + 'type' => self::STR_DOUBLE_QUOTE_VAR, + ]; + } + + return null; + } + + /** + * @param _ConcatOperandType $firstOperand + * @param _ConcatOperandType $secondOperand + */ + private function mergeConstantEscapedStringOperands( + Tokens $tokens, + array $firstOperand, + int $concatOperatorIndex, + array $secondOperand + ): void { + $quote = self::STR_DOUBLE_QUOTE === $firstOperand['type'] || self::STR_DOUBLE_QUOTE === $secondOperand['type'] ? '"' : "'"; + $firstOperandTokenContent = $tokens[$firstOperand['start']]->getContent(); + $secondOperandTokenContent = $tokens[$secondOperand['start']]->getContent(); + + $tokens[$firstOperand['start']] = new Token( + [ + T_CONSTANT_ENCAPSED_STRING, + $quote.substr($firstOperandTokenContent, 1, -1).substr($secondOperandTokenContent, 1, -1).$quote, + ], + ); + + $this->clearConcatAndAround($tokens, $concatOperatorIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($secondOperand['start']); + } + + /** + * @param _ConcatOperandType $firstOperand + * @param _ConcatOperandType $secondOperand + */ + private function mergeConstantEscapedStringVarOperands( + Tokens $tokens, + array $firstOperand, + int $concatOperatorIndex, + array $secondOperand + ): void { + // build up the new content + $newContent = ''; + + foreach ([$firstOperand, $secondOperand] as $operant) { + $operandContent = ''; + + for ($i = $operant['start']; $i <= $operant['end'];) { + $operandContent .= $tokens[$i]->getContent(); + $i = $tokens->getNextMeaningfulToken($i); + } + + $newContent .= substr($operandContent, 1, -1); + } + + // remove tokens making up the concat statement + + for ($i = $secondOperand['end']; $i >= $secondOperand['start'];) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + $i = $tokens->getPrevMeaningfulToken($i); + } + + $this->clearConcatAndAround($tokens, $concatOperatorIndex); + + for ($i = $firstOperand['end']; $i > $firstOperand['start'];) { + $tokens->clearTokenAndMergeSurroundingWhitespace($i); + $i = $tokens->getPrevMeaningfulToken($i); + } + + // insert new tokens based on the new content + + $newTokens = Tokens::fromCode('overrideRange($firstOperand['start'], $firstOperand['start'], $insertTokens); + } + + private function clearConcatAndAround(Tokens $tokens, int $concatOperatorIndex): void + { + if ($tokens[$concatOperatorIndex + 1]->isWhitespace()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($concatOperatorIndex + 1); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($concatOperatorIndex); + + if ($tokens[$concatOperatorIndex - 1]->isWhitespace()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($concatOperatorIndex - 1); + } + } + + private function isSimpleQuotedStringContent(string $candidate): bool + { + return !Preg::match('#[\$"\'\\\]#', substr($candidate, 1, -1)); + } + + private function containsLinebreak(Tokens $tokens, int $startIndex, int $endIndex): bool + { + for ($i = $endIndex; $i > $startIndex; --$i) { + if (Preg::match('/\R/', $tokens[$i]->getContent())) { + return true; + } + } + + return false; + } + + /** + * @param _ConcatOperandType $firstOperand + * @param _ConcatOperandType $secondOperand + */ + private function operandsCanNotBeMerged(Tokens $tokens, array $firstOperand, array $secondOperand): bool + { + // If the first operand does not end with a variable, no variables would be broken by concatenation. + if (self::STR_DOUBLE_QUOTE_VAR !== $firstOperand['type']) { + return false; + } + if (!$tokens[$firstOperand['end'] - 1]->isGivenKind(T_VARIABLE)) { + return false; + } + + $allowedPatternsForSecondOperand = [ + '/^ .*/', // e.g. " foo", ' bar', " $baz" + '/^-(?!\>)/', // e.g. "-foo", '-bar', "-$baz" + ]; + + // If the first operand ends with a variable, the second operand should match one of the allowed patterns. + // Otherwise, the concatenation can break a variable in the first operand. + foreach ($allowedPatternsForSecondOperand as $allowedPattern) { + $secondOperandInnerContent = substr($tokens->generatePartialCode($secondOperand['start'], $secondOperand['end']), 1, -1); + + if (Preg::match($allowedPattern, $secondOperandInnerContent)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessNullsafeOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessNullsafeOperatorFixer.php new file mode 100644 index 0000000000..2754e152f3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NoUselessNullsafeOperatorFixer.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Operator; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +final class NoUselessNullsafeOperatorFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'There should not be useless Null-safe operator `?->` used.', + [ + new VersionSpecificCodeSample( + 'parentMethod(); + } +} +', + new VersionSpecification(8_00_00) + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_00_00 && $tokens->isAllTokenKindsFound([T_VARIABLE, T_NULLSAFE_OBJECT_OPERATOR]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(T_NULLSAFE_OBJECT_OPERATOR)) { + continue; + } + + $nullsafeObjectOperatorIndex = $index; + $index = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$index]->isGivenKind(T_VARIABLE)) { + continue; + } + + if ('$this' !== strtolower($tokens[$index]->getContent())) { + continue; + } + + $tokens[$nullsafeObjectOperatorIndex] = new Token([T_OBJECT_OPERATOR, '->']); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php index 83c5cf5509..f40dcc223e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSpaceFixer.php @@ -26,9 +26,6 @@ */ final class NotOperatorWithSpaceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -54,17 +51,11 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound('!'); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; $index >= 0; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php index 2290131876..bf228b5255 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/NotOperatorWithSuccessorSpaceFixer.php @@ -25,9 +25,6 @@ */ final class NotOperatorWithSuccessorSpaceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,17 +50,11 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound('!'); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; $index >= 0; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php index 71e4cd6b9a..fe6e7ac969 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/ObjectOperatorWithoutWhitespaceFixer.php @@ -27,9 +27,6 @@ */ final class ObjectOperatorWithoutWhitespaceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -38,17 +35,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getObjectOperatorKinds()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // [Structure] there should not be space before or after "->" or "?->" diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php index a5f8a68793..fdec2fb1f8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/OperatorLinebreakFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -24,30 +25,40 @@ use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; -use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\ReferenceAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * only_booleans?: bool, + * position?: 'beginning'|'end' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * only_booleans: bool, + * position: 'beginning'|'end' + * } */ final class OperatorLinebreakFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + private const BOOLEAN_OPERATORS = [[T_BOOLEAN_AND], [T_BOOLEAN_OR], [T_LOGICAL_AND], [T_LOGICAL_OR], [T_LOGICAL_XOR]]; private string $position = 'beginning'; /** - * @var array|string> + * @var list */ private array $operators = []; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -72,13 +83,13 @@ function foo() { ); } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void + public function isCandidate(Tokens $tokens): bool { - parent::configure($configuration); + return true; + } + protected function configurePostNormalisation(): void + { $this->position = $this->configuration['position']; $this->operators = self::BOOLEAN_OPERATORS; @@ -87,42 +98,26 @@ public function configure(array $configuration): void } } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool - { - return true; - } - - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('only_booleans', 'whether to limit operators to only boolean ones')) + (new FixerOptionBuilder('only_booleans', 'Whether to limit operators to only boolean ones.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), - (new FixerOptionBuilder('position', 'whether to place operators at the beginning or at the end of the line')) + (new FixerOptionBuilder('position', 'Whether to place operators at the beginning or at the end of the line.')) ->setAllowedValues(['beginning', 'end']) ->setDefault($this->position) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $referenceAnalyzer = new ReferenceAnalyzer(); $gotoLabelAnalyzer = new GotoLabelAnalyzer(); $alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); - $excludedIndices = $this->getExcludedIndices($tokens); - $index = $tokens->count(); while ($index > 1) { --$index; @@ -143,7 +138,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - if (\in_array($index, $excludedIndices, true)) { + if (SwitchAnalyzer::belongsToSwitch($tokens, $index)) { continue; } @@ -162,33 +157,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * Currently only colons from "switch". - * - * @return int[] - */ - private function getExcludedIndices(Tokens $tokens): array - { - $colonIndices = []; - - foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { - foreach ($analysis->getCases() as $case) { - $colonIndices[] = $case->getColonIndex(); - } - - if ($analysis instanceof SwitchAnalysis) { - $defaultAnalysis = $analysis->getDefaultAnalysis(); - - if (null !== $defaultAnalysis) { - $colonIndices[] = $defaultAnalysis->getColonIndex(); - } - } - } - - return $colonIndices; - } - - /** - * @param int[] $operatorIndices + * @param non-empty-list $operatorIndices */ private function fixOperatorLinebreak(Tokens $tokens, array $operatorIndices): void { @@ -220,7 +189,7 @@ private function fixOperatorLinebreak(Tokens $tokens, array $operatorIndices): v } /** - * @param int[] $operatorIndices + * @param non-empty-list $operatorIndices */ private function fixMoveToTheBeginning(Tokens $tokens, array $operatorIndices): void { @@ -231,7 +200,7 @@ private function fixMoveToTheBeginning(Tokens $tokens, array $operatorIndices): $nextIndex = $tokens->getNextMeaningfulToken(max($operatorIndices)); for ($i = $nextIndex - 1; $i > max($operatorIndices); --$i) { - if ($tokens[$i]->isWhitespace() && 1 === Preg::match('/\R/u', $tokens[$i]->getContent())) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/u', $tokens[$i]->getContent())) { $isWhitespaceBefore = $tokens[$prevIndex]->isWhitespace(); $inserts = $this->getReplacementsAndClear($tokens, $operatorIndices, -1); if ($isWhitespaceBefore) { @@ -245,7 +214,7 @@ private function fixMoveToTheBeginning(Tokens $tokens, array $operatorIndices): } /** - * @param int[] $operatorIndices + * @param non-empty-list $operatorIndices */ private function fixMoveToTheEnd(Tokens $tokens, array $operatorIndices): void { @@ -256,7 +225,7 @@ private function fixMoveToTheEnd(Tokens $tokens, array $operatorIndices): void $nextIndex = $tokens->getNonEmptySibling(max($operatorIndices), 1); for ($i = $prevIndex + 1; $i < max($operatorIndices); ++$i) { - if ($tokens[$i]->isWhitespace() && 1 === Preg::match('/\R/u', $tokens[$i]->getContent())) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/u', $tokens[$i]->getContent())) { $isWhitespaceAfter = $tokens[$nextIndex]->isWhitespace(); $inserts = $this->getReplacementsAndClear($tokens, $operatorIndices, 1); if ($isWhitespaceAfter) { @@ -270,9 +239,9 @@ private function fixMoveToTheEnd(Tokens $tokens, array $operatorIndices): void } /** - * @param int[] $indices + * @param list $indices * - * @return Token[] + * @return list */ private function getReplacementsAndClear(Tokens $tokens, array $indices, int $direction): array { @@ -303,6 +272,9 @@ private function isMultiline(Tokens $tokens, int $indexStart, int $indexEnd): bo return false; } + /** + * @return list + */ private static function getNonBooleanOperators(): array { return array_merge( @@ -314,7 +286,7 @@ private static function getNonBooleanOperators(): array [T_POW_EQUAL], [T_SL], [T_SL_EQUAL], [T_SR], [T_SR_EQUAL], [T_XOR_EQUAL], [T_COALESCE], [T_SPACESHIP], ], - array_map(static function ($id): array { return [$id]; }, Token::getObjectOperatorKinds()) + array_map(static fn (int $id): array => [$id], Token::getObjectOperatorKinds()), ); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php index 3142b8e6b3..1aaae169e8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeIncrementFixer.php @@ -38,9 +38,6 @@ final class StandardizeIncrementFixer extends AbstractIncrementOperatorFixer [T_CLOSE_TAG], ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -56,23 +53,18 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before IncrementStyleFixer. + * Must run after LongToShorthandOperatorFixer. */ public function getPriority(): int { - return 1; + return 16; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_PLUS_EQUAL, T_MINUS_EQUAL]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1; $index > 0; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php index fe5a73e4da..2dab60920f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/StandardizeNotEqualsFixer.php @@ -26,9 +26,6 @@ */ final class StandardizeNotEqualsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -47,17 +44,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_IS_NOT_EQUAL); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php index 2bbff51b13..758c79393b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryOperatorSpacesFixer.php @@ -19,9 +19,8 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; -use PhpCsFixer\Tokenizer\Analyzer\ControlCaseStructuresAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -30,9 +29,6 @@ */ final class TernaryOperatorSpacesFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -48,33 +44,26 @@ public function getDefinition(): FixerDefinitionInterface */ public function getPriority(): int { - return 0; + return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound(['?', ':']); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); $gotoLabelAnalyzer = new GotoLabelAnalyzer(); $ternaryOperatorIndices = []; - $excludedIndices = $this->getColonIndicesForSwitch($tokens); foreach ($tokens as $index => $token) { if (!$token->equalsAny(['?', ':'])) { continue; } - if (\in_array($index, $excludedIndices, true)) { + if (SwitchAnalyzer::belongsToSwitch($tokens, $index)) { continue; } @@ -123,30 +112,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * @return int[] - */ - private function getColonIndicesForSwitch(Tokens $tokens): array - { - $colonIndices = []; - - foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { - foreach ($analysis->getCases() as $case) { - $colonIndices[] = $case->getColonIndex(); - } - - if ($analysis instanceof SwitchAnalysis) { - $defaultAnalysis = $analysis->getDefaultAnalysis(); - - if (null !== $defaultAnalysis) { - $colonIndices[] = $defaultAnalysis->getColonIndex(); - } - } - } - - return $colonIndices; - } - private function ensureWhitespaceExistence(Tokens $tokens, int $index, bool $after): void { if ($tokens[$index]->isWhitespace()) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php index dd3e79eb1b..191d8bf904 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToElvisOperatorFixer.php @@ -29,7 +29,7 @@ final class TernaryToElvisOperatorFixer extends AbstractFixer * * Ordered by most common types first. * - * @var array + * @var list */ private const VALID_BEFORE_ENDTYPES = [ '=', @@ -56,9 +56,6 @@ final class TernaryToElvisOperatorFixer extends AbstractFixer [T_XOR_EQUAL], // ^= ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -83,32 +80,21 @@ public function getDefinition(): FixerDefinitionInterface */ public function getPriority(): int { - return 1; + return 2; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound('?'); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $blockEdgeDefinitions = Tokens::getBlockEdgeDefinitions(); - for ($index = \count($tokens) - 5; $index > 1; --$index) { if (!$tokens[$index]->equals('?')) { continue; @@ -122,7 +108,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // get and check what is before the `?` operator - $beforeOperator = $this->getBeforeOperator($tokens, $index, $blockEdgeDefinitions); + $beforeOperator = $this->getBeforeOperator($tokens, $index); if (null === $beforeOperator) { continue; // contains something we cannot fix because of priorities @@ -141,10 +127,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @return null|array null if contains ++/-- operator + * @return ?array{start: int, end: int} null if contains ++/-- operator */ - private function getBeforeOperator(Tokens $tokens, int $index, array $blockEdgeDefinitions): ?array + private function getBeforeOperator(Tokens $tokens, int $index): ?array { + $blockEdgeDefinitions = Tokens::getBlockEdgeDefinitions(); $index = $tokens->getPrevMeaningfulToken($index); $before = ['end' => $index]; @@ -194,6 +181,9 @@ private function getBeforeOperator(Tokens $tokens, int $index, array $blockEdgeD return $before; } + /** + * @return array{start: int, end: int} + */ private function getAfterOperator(Tokens $tokens, int $index): array { $index = $tokens->getNextMeaningfulToken($index); @@ -213,6 +203,9 @@ private function getAfterOperator(Tokens $tokens, int $index): array return $after; } + /** + * @param array{start: int, end: int} $range + */ private function clearMeaningfulFromRange(Tokens $tokens, array $range): void { // $range['end'] must be meaningful! diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php index b5b5ea8c1d..83cf9337de 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/TernaryToNullCoalescingFixer.php @@ -26,9 +26,6 @@ */ final class TernaryToNullCoalescingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,17 +48,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_ISSET); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $issetIndices = array_keys($tokens->findGivenKind(T_ISSET)); @@ -98,11 +89,17 @@ private function fixIsset(Tokens $tokens, int $index): void return; // some weird stuff inside the isset } + $issetCode = $issetTokens->generateCode(); + + if ('$this' === $issetCode) { + return; // null coalescing operator does not with $this + } + // search what is inside the middle argument of ternary operator $ternaryColonIndex = $tokens->getNextTokenOfKind($ternaryQuestionMarkIndex, [':']); $ternaryFirstOperandTokens = $this->getMeaningfulSequence($tokens, $ternaryQuestionMarkIndex, $ternaryColonIndex); - if ($issetTokens->generateCode() !== $ternaryFirstOperandTokens->generateCode()) { + if ($issetCode !== $ternaryFirstOperandTokens->generateCode()) { return; // regardless of non-meaningful tokens, the operands are different } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php index c1558d7e3c..42b977fe80 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Operator/UnaryOperatorSpacesFixer.php @@ -15,6 +15,11 @@ namespace PhpCsFixer\Fixer\Operator; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -23,17 +28,41 @@ /** * @author Gregor Harlan + * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * only_dec_inc?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * only_dec_inc: bool + * } */ -final class UnaryOperatorSpacesFixer extends AbstractFixer +final class UnaryOperatorSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Unary operators should be placed adjacent to their operands.', - [new CodeSample(" false] + ), + new CodeSample( + ' true] + ), + ] ); } @@ -47,22 +76,30 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('only_dec_inc', 'Limit to increment and decrement operators.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (true === $this->configuration['only_dec_inc'] && !$tokens[$index]->isGivenKind([T_DEC, T_INC])) { + continue; + } + if ($tokensAnalyzer->isUnarySuccessorOperator($index)) { if (!$tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) { $tokens->removeLeadingWhitespace($index); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php index f6f7ab184f..d4af8196ec 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php @@ -27,9 +27,6 @@ */ final class BlankLineAfterOpeningTagFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -41,7 +38,7 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before NoBlankLinesBeforeNamespaceFixer. + * Must run before BlankLinesBeforeNamespaceFixer, NoBlankLinesBeforeNamespaceFixer. * Must run after DeclareStrictTypesFixer. */ public function getPriority(): int @@ -49,31 +46,18 @@ public function getPriority(): int return 1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isTokenKindFound(T_OPEN_TAG); + return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $lineEnding = $this->whitespacesConfig->getLineEnding(); - // ignore files with short open tag and ignore non-monolithic files - if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) { - return; - } - $newlineFound = false; - - /** @var Token $token */ foreach ($tokens as $token) { - if ($token->isWhitespace() && str_contains($token->getContent(), "\n")) { + if (($token->isWhitespace() || $token->isGivenKind(T_OPEN_TAG)) && str_contains($token->getContent(), "\n")) { $newlineFound = true; break; @@ -85,17 +69,19 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void return; } - $token = $tokens[0]; + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; + $token = $tokens[$openTagIndex]; if (!str_contains($token->getContent(), "\n")) { - $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]); + $tokens[$openTagIndex] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]); } - if (!str_contains($tokens[1]->getContent(), "\n")) { - if ($tokens[1]->isWhitespace()) { - $tokens[1] = new Token([T_WHITESPACE, $lineEnding.$tokens[1]->getContent()]); + $newLineIndex = $openTagIndex + 1; + if (isset($tokens[$newLineIndex]) && !str_contains($tokens[$newLineIndex]->getContent(), "\n")) { + if ($tokens[$newLineIndex]->isWhitespace()) { + $tokens[$newLineIndex] = new Token([T_WHITESPACE, $lineEnding.$tokens[$newLineIndex]->getContent()]); } else { - $tokens->insertAt(1, new Token([T_WHITESPACE, $lineEnding])); + $tokens->insertAt($newLineIndex, new Token([T_WHITESPACE, $lineEnding])); } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php index e11cb6ab6e..2953c35de3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/EchoTagSyntaxFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,9 +28,25 @@ /** * @author Michele Locati + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * format?: 'long'|'short', + * long_function?: 'echo'|'print', + * shorten_simple_statements_only?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * format: 'long'|'short', + * long_function: 'echo'|'print', + * shorten_simple_statements_only: bool + * } */ final class EchoTagSyntaxFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** @internal */ public const OPTION_FORMAT = 'format'; @@ -61,19 +78,15 @@ final class EchoTagSyntaxFixer extends AbstractFixer implements ConfigurableFixe self::LONG_FUNCTION_PRINT, ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { $sample = <<<'EOT' - - - - + + + + -EOT - ; + EOT; return new FixerDefinition( 'Replaces short-echo `configuration[self::OPTION_FORMAT]) { @@ -110,9 +120,6 @@ public function isCandidate(Tokens $tokens): bool return $tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -120,20 +127,17 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setAllowedValues(self::SUPPORTED_FORMAT_OPTIONS) ->setDefault(self::FORMAT_LONG) ->getOption(), - (new FixerOptionBuilder(self::OPTION_LONG_FUNCTION, 'The function to be used to expand the short echo tags')) + (new FixerOptionBuilder(self::OPTION_LONG_FUNCTION, 'The function to be used to expand the short echo tags.')) ->setAllowedValues(self::SUPPORTED_LONGFUNCTION_OPTIONS) ->setDefault(self::LONG_FUNCTION_ECHO) ->getOption(), - (new FixerOptionBuilder(self::OPTION_SHORTEN_SIMPLE_STATEMENTS_ONLY, 'Render short-echo tags only in case of simple code')) + (new FixerOptionBuilder(self::OPTION_SHORTEN_SIMPLE_STATEMENTS_ONLY, 'Render short-echo tags only in case of simple code.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (self::FORMAT_SHORT === $this->configuration[self::OPTION_FORMAT]) { @@ -239,7 +243,7 @@ private function isComplexCode(Tokens $tokens, int $index): bool /** * Builds the list of tokens that replace a long echo sequence. * - * @return Token[] + * @return list */ private function buildLongToShortTokens(Tokens $tokens, int $openTagIndex, int $echoTagIndex): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php index c1e60f0079..bf6c1313ab 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/FullOpeningTagFixer.php @@ -29,9 +29,6 @@ */ final class FullOpeningTagFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -47,26 +44,17 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function getPriority(): int { // must run before all Token-based fixers return 98; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $content = $tokens->generateCode(); @@ -74,7 +62,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // replace all $token) { if ($token->isGivenKind(T_OPEN_TAG)) { $tokenContent = $token->getContent(); + $possibleOpenContent = substr($content, $tokensOldContentLength, 5); - if ('isTokenKindFound(T_OPEN_TAG); + return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - // ignore files with short open tag and ignore non-monolithic files - if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) { - return; - } + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; // ignore if linebreak already present - if (str_contains($tokens[0]->getContent(), "\n")) { + if (str_contains($tokens[$openTagIndex]->getContent(), "\n")) { return; } $newlineFound = false; foreach ($tokens as $token) { - if ($token->isWhitespace() && str_contains($token->getContent(), "\n")) { + if (($token->isWhitespace() || $token->isGivenKind(T_OPEN_TAG)) && str_contains($token->getContent(), "\n")) { $newlineFound = true; break; @@ -75,6 +63,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void return; } - $tokens[0] = new Token([T_OPEN_TAG, rtrim($tokens[0]->getContent()).$this->whitespacesConfig->getLineEnding()]); + $tokens[$openTagIndex] = new Token([T_OPEN_TAG, rtrim($tokens[$openTagIndex]->getContent()).$this->whitespacesConfig->getLineEnding()]); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php index ff1e00beb4..14c40c037f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpTag/NoClosingTagFixer.php @@ -28,9 +28,6 @@ */ final class NoClosingTagFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -39,25 +36,15 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isTokenKindFound(T_CLOSE_TAG); + return \count($tokens) >= 2 && $tokens->isMonolithicPhp() && $tokens->isTokenKindFound(T_CLOSE_TAG); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - if (\count($tokens) < 2 || !$tokens->isMonolithicPhp() || !$tokens->isTokenKindFound(T_CLOSE_TAG)) { - return; - } - $closeTags = $tokens->findGivenKind(T_CLOSE_TAG); - $index = key($closeTags); + $index = array_key_first($closeTags); if (isset($tokens[$index - 1]) && $tokens[$index - 1]->isWhitespace()) { $tokens->clearAt($index - 1); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitAssertNewNamesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitAssertNewNamesFixer.php new file mode 100644 index 0000000000..71f02b5df6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitAssertNewNamesFixer.php @@ -0,0 +1,107 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Krzysztof Ciszewski + */ +final class PhpUnitAssertNewNamesFixer extends AbstractPhpUnitFixer +{ + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Rename deprecated PHPUnit assertions like `assertFileNotExists` to new methods like `assertFileDoesNotExist`.', + [ + new CodeSample( + 'assertFileNotExists("test.php"); + $this->assertNotIsWritable("path.php"); + } +} +' + ), + ], + null, + 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' + ); + } + + /** + * {@inheritdoc} + * + * Must run after PhpUnitDedicateAssertFixer. + */ + public function getPriority(): int + { + return -10; + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + foreach ($this->getPreviousAssertCall($tokens, $startIndex, $endIndex) as $assertCall) { + $this->fixAssertNewNames($tokens, $assertCall); + } + } + + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ + private function fixAssertNewNames(Tokens $tokens, array $assertCall): void + { + $replacements = [ + 'assertnotisreadable' => 'assertIsNotReadable', + 'assertnotiswritable' => 'assertIsNotWritable', + 'assertdirectorynotexists' => 'assertDirectoryDoesNotExist', + 'assertfilenotexists' => 'assertFileDoesNotExist', + 'assertdirectorynotisreadable' => 'assertDirectoryIsNotReadable', + 'assertdirectorynotiswritable' => 'assertDirectoryIsNotWriteable', + 'assertfilenotisreadable' => 'assertFileIsNotReadable', + 'assertfilenotiswritable' => 'assertFileIsNotWriteable', + 'assertregexp' => 'assertMatchesRegularExpression', + 'assertnotregexp' => 'assertDoesNotMatchRegularExpression', + ]; + $replacement = $replacements[$assertCall['loweredName']] ?? null; + + if (null === $replacement) { + return; + } + + $tokens[$assertCall['index']] = new Token([ + T_STRING, + $replacement, + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitAttributesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitAttributesFixer.php new file mode 100644 index 0000000000..6186798ccc --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitAttributesFixer.php @@ -0,0 +1,611 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\AttributeNotation\OrderedAttributesFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * keep_annotations?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * keep_annotations: bool + * } + */ +final class PhpUnitAttributesFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** @var array */ + private array $fixingMap; + + public function __construct() + { + parent::__construct(); + $this->prepareFixingMap(); + } + + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = <<<'PHP' + true]), + ], + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return \PHP_VERSION_ID >= 8_00_00 && parent::isCandidate($tokens); + } + + /** + * {@inheritdoc} + * + * Must run before FullyQualifiedStrictTypesFixer, PhpdocSeparationFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer. + */ + public function getPriority(): int + { + return 8; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('keep_annotations', 'Whether to keep annotations or not. This may be helpful for projects that support PHP before version 8 or PHPUnit before version 10.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex); + if ($tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT)) { + $startIndex = $docBlockIndex; + } + + for ($index = $endIndex; $index >= $startIndex; --$index) { + if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + $targetIndex = $tokens->getTokenNotOfKindSibling( + $index, + 1, + [[T_ABSTRACT], [T_COMMENT], [T_FINAL], [T_FUNCTION], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_STATIC], [T_WHITESPACE]], + ); + $annotationScope = $tokens[$targetIndex]->isGivenKind(T_CLASS) ? 'class' : 'method'; + + $docBlock = new DocBlock($tokens[$index]->getContent()); + + $presentAttributes = []; + foreach (array_reverse($docBlock->getAnnotations()) as $annotation) { + $annotationName = $annotation->getTag()->getName(); + + if (!isset($this->fixingMap[$annotationName])) { + continue; + } + if (!self::shouldBeFixed($annotationName, $annotationScope)) { + continue; + } + + /** @phpstan-ignore-next-line */ + $tokensToInsert = self::{$this->fixingMap[$annotationName]}($tokens, $index, $annotation); + + if (!isset($presentAttributes[$annotationName])) { + $presentAttributes[$annotationName] = self::isAttributeAlreadyPresent($tokens, $index, $tokensToInsert); + } + + if ($presentAttributes[$annotationName]) { + continue; + } + + if ([] === $tokensToInsert) { + continue; + } + + $tokens->insertSlices([$index + 1 => $tokensToInsert]); + + if (!$this->configuration['keep_annotations']) { + $annotation->remove(); + } + } + + if ('' === $docBlock->getContent()) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } else { + $tokens[$index] = new Token([T_DOC_COMMENT, $docBlock->getContent()]); + } + } + } + + private function prepareFixingMap(): void + { + // annotations that map to attribute without parameters + foreach ([ + 'after', + 'afterClass', + 'before', + 'beforeClass', + 'coversNothing', + 'doesNotPerformAssertions', + 'large', + 'medium', + 'runInSeparateProcess', + 'runTestsInSeparateProcesses', + 'small', + 'test', + 'preCondition', + 'postCondition', + ] as $annotation) { + $this->fixingMap[$annotation] = 'fixWithoutParameters'; + } + + // annotations that map to attribute with single string value + foreach (['group', 'testDox', 'ticket'] as $annotation) { + $this->fixingMap[$annotation] = 'fixWithSingleStringValue'; + } + + // annotations that map from 'enabled'/'disabled' value to attribute with boolean value + foreach (['backupGlobals', 'backupStaticAttributes', 'preserveGlobalState'] as $annotation) { + $this->fixingMap[$annotation] = 'fixWithEnabledDisabledValue'; + } + + // annotations that has custom mapping function + $this->fixingMap['covers'] = 'fixCovers'; + $this->fixingMap['dataProvider'] = 'fixDataProvider'; + $this->fixingMap['depends'] = 'fixDepends'; + $this->fixingMap['requires'] = 'fixRequires'; + $this->fixingMap['testWith'] = 'fixTestWith'; + $this->fixingMap['uses'] = 'fixUses'; + } + + private static function shouldBeFixed(string $annotationName, string $annotationScope): bool + { + if ( + 'method' === $annotationScope + && \in_array($annotationName, ['covers', 'large', 'medium', 'runTestsInSeparateProcesses', 'small', 'uses'], true) + ) { + return false; + } + + if ( + 'class' === $annotationScope + && \in_array($annotationName, ['after', 'afterClass', 'before', 'beforeClass', 'dataProvider', 'depends', 'postCondition', 'preCondition', 'runInSeparateProcess', 'test', 'testWith'], true) + ) { + return false; + } + + return true; + } + + /** + * @param list $tokensToInsert + */ + private static function isAttributeAlreadyPresent(Tokens $tokens, int $index, array $tokensToInsert): bool + { + $attributeIndex = $tokens->getNextMeaningfulToken($index); + if (!$tokens[$attributeIndex]->isGivenKind(T_ATTRIBUTE)) { + return false; + } + + $insertedClassName = ''; + foreach (\array_slice($tokensToInsert, 3) as $token) { + if ($token->equals('(') || $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + break; + } + $insertedClassName .= $token->getContent(); + } + + // @TODO: refactor OrderedAttributesFixer::determineAttributeFullyQualifiedName to shared analyzer + static $determineAttributeFullyQualifiedName = null; + static $orderedAttributesFixer = null; + if (null === $determineAttributeFullyQualifiedName) { + $orderedAttributesFixer = new OrderedAttributesFixer(); + $reflection = new \ReflectionObject($orderedAttributesFixer); + $determineAttributeFullyQualifiedName = $reflection->getMethod('determineAttributeFullyQualifiedName'); + $determineAttributeFullyQualifiedName->setAccessible(true); + } + + foreach (AttributeAnalyzer::collect($tokens, $attributeIndex) as $attributeAnalysis) { + foreach ($attributeAnalysis->getAttributes() as $attribute) { + $className = ltrim($determineAttributeFullyQualifiedName->invokeArgs( + $orderedAttributesFixer, + [$tokens, + $attribute['name'], + $attribute['start']], + ), '\\'); + + if ($insertedClassName === $className) { + return true; + } + } + } + + return false; + } + + /** + * @return list + */ + private static function fixWithoutParameters(Tokens $tokens, int $index, Annotation $annotation): array + { + return self::createAttributeTokens($tokens, $index, self::getAttributeNameForAnnotation($annotation)); + } + + /** + * @return list + */ + private static function fixWithSingleStringValue(Tokens $tokens, int $index, Annotation $annotation): array + { + Preg::match( + \sprintf('/@%s\s+(.*\S)(?:\R|\s*\*+\/$)/', $annotation->getTag()->getName()), + $annotation->getContent(), + $matches, + ); + if (!isset($matches[1])) { + return []; + } + + return self::createAttributeTokens( + $tokens, + $index, + self::getAttributeNameForAnnotation($annotation), + self::createEscapedStringToken($matches[1]), + ); + } + + /** + * @return list + */ + private static function fixWithEnabledDisabledValue(Tokens $tokens, int $index, Annotation $annotation): array + { + $matches = self::getMatches($annotation); + if (!isset($matches[1])) { + return []; + } + + return self::createAttributeTokens( + $tokens, + $index, + self::getAttributeNameForAnnotation($annotation), + new Token([T_STRING, isset($matches[1]) && 'enabled' === $matches[1] ? 'true' : 'false']), + ); + } + + /** + * @return list + */ + private static function fixCovers(Tokens $tokens, int $index, Annotation $annotation): array + { + $matches = self::getMatches($annotation); + \assert(isset($matches[1])); + + if (str_starts_with($matches[1], '::')) { + return self::createAttributeTokens($tokens, $index, 'CoversFunction', self::createEscapedStringToken(substr($matches[1], 2))); + } + if (!str_contains($matches[1], '::')) { + return self::createAttributeTokens( + $tokens, + $index, + 'CoversClass', + ...self::toClassConstant($matches[1]), + ); + } + + return []; + } + + /** + * @return list + */ + private static function fixDataProvider(Tokens $tokens, int $index, Annotation $annotation): array + { + $matches = self::getMatches($annotation); + if (!isset($matches[1])) { + return []; + } + + if (str_contains($matches[1], '::')) { + // @phpstan-ignore offsetAccess.notFound + [$class, $method] = explode('::', $matches[1]); + + return self::createAttributeTokens( + $tokens, + $index, + 'DataProviderExternal', + ...[ + ...self::toClassConstant($class), + new Token(','), + new Token([T_WHITESPACE, ' ']), + self::createEscapedStringToken($method), + ], + ); + } + + return self::createAttributeTokens($tokens, $index, 'DataProvider', self::createEscapedStringToken($matches[1])); + } + + /** + * @return list + */ + private static function fixDepends(Tokens $tokens, int $index, Annotation $annotation): array + { + $matches = self::getMatches($annotation); + if (!isset($matches[1])) { + return []; + } + + $nameSuffix = ''; + $depended = $matches[1]; + if (isset($matches[2])) { + if ('clone' === $matches[1]) { + $nameSuffix = 'UsingDeepClone'; + $depended = $matches[2]; + } elseif ('shallowClone' === $matches[1]) { + $nameSuffix = 'UsingShallowClone'; + $depended = $matches[2]; + } + } + + $class = null; + $method = $depended; + if (str_contains($depended, '::')) { + // @phpstan-ignore offsetAccess.notFound + [$class, $method] = explode('::', $depended); + + if ('class' === $method) { + $method = null; + $nameSuffix = '' === $nameSuffix ? 'OnClass' : ('OnClass'.$nameSuffix); + } else { + $nameSuffix = ('External'.$nameSuffix); + } + } + + $attributeTokens = []; + if (null !== $class) { + $attributeTokens = self::toClassConstant($class); + } + if (null !== $method) { + if ([] !== $attributeTokens) { + $attributeTokens[] = new Token(','); + $attributeTokens[] = new Token([T_WHITESPACE, ' ']); + } + $attributeTokens[] = self::createEscapedStringToken($method); + } + + return self::createAttributeTokens($tokens, $index, 'Depends'.$nameSuffix, ...$attributeTokens); + } + + /** + * @return list + */ + private static function fixRequires(Tokens $tokens, int $index, Annotation $annotation): array + { + $matches = self::getMatches($annotation); + \assert(isset($matches[1])); + + $map = [ + 'extension' => 'RequiresPhpExtension', + 'function' => 'RequiresFunction', + 'PHP' => 'RequiresPhp', + 'PHPUnit' => 'RequiresPhpunit', + 'OS' => 'RequiresOperatingSystem', + 'OSFAMILY' => 'RequiresOperatingSystemFamily', + 'setting' => 'RequiresSetting', + ]; + + if (!isset($matches[2]) || !isset($map[$matches[1]])) { + return []; + } + + $attributeName = $map[$matches[1]]; + + if ('RequiresFunction' === $attributeName && str_contains($matches[2], '::')) { + // @phpstan-ignore offsetAccess.notFound + [$class, $method] = explode('::', $matches[2]); + + $attributeName = 'RequiresMethod'; + $attributeTokens = [ + ...self::toClassConstant($class), + new Token(','), + new Token([T_WHITESPACE, ' ']), + self::createEscapedStringToken($method), + ]; + } elseif ('RequiresPhp' === $attributeName && isset($matches[3])) { + $attributeTokens = [self::createEscapedStringToken($matches[2].' '.$matches[3])]; + } else { + $attributeTokens = [self::createEscapedStringToken($matches[2])]; + } + + if (isset($matches[3]) && 'RequiresPhp' !== $attributeName) { + $attributeTokens[] = new Token(','); + $attributeTokens[] = new Token([T_WHITESPACE, ' ']); + $attributeTokens[] = self::createEscapedStringToken($matches[3]); + } + + return self::createAttributeTokens($tokens, $index, $attributeName, ...$attributeTokens); + } + + /** + * @return list + */ + private static function fixTestWith(Tokens $tokens, int $index, Annotation $annotation): array + { + $content = $annotation->getContent(); + $content = Preg::replace('/@testWith\s+/', '', $content); + $content = Preg::replace('/(^|\R)\s+\**\s*/', "\n", $content); + $content = trim($content); + if ('' === $content) { + return []; + } + + $attributeTokens = []; + foreach (explode("\n", $content) as $json) { + $attributeTokens = array_merge( + $attributeTokens, + self::createAttributeTokens($tokens, $index, 'TestWithJson', self::createEscapedStringToken($json)), + ); + } + + return $attributeTokens; + } + + /** + * @return list + */ + private static function fixUses(Tokens $tokens, int $index, Annotation $annotation): array + { + $matches = self::getMatches($annotation); + if (!isset($matches[1])) { + return []; + } + + if (str_starts_with($matches[1], '::')) { + $attributeName = 'UsesFunction'; + $attributeTokens = [self::createEscapedStringToken(substr($matches[1], 2))]; + } elseif (Preg::match('/^[a-zA-Z\d\\\]+$/', $matches[1])) { + $attributeName = 'UsesClass'; + $attributeTokens = self::toClassConstant($matches[1]); + } else { + return []; + } + + return self::createAttributeTokens($tokens, $index, $attributeName, ...$attributeTokens); + } + + /** + * @return list + */ + private static function getMatches(Annotation $annotation): array + { + Preg::match( + \sprintf('/@%s\s+(\S+)(?:\s+(\S+))?(?:\s+(.+\S))?\s*(?:\R|\*+\/$)/', $annotation->getTag()->getName()), + $annotation->getContent(), + $matches, + ); + + \assert(array_is_list($matches)); // preg_match matches is not well typed, it depends on used regex, let's assure the type to instruct SCA + + return $matches; + } + + private static function getAttributeNameForAnnotation(Annotation $annotation): string + { + $annotationName = $annotation->getTag()->getName(); + + return 'backupStaticAttributes' === $annotationName + ? 'BackupStaticProperties' + : ucfirst($annotationName); + } + + /** + * @return list + */ + private static function createAttributeTokens( + Tokens $tokens, + int $index, + string $className, + Token ...$attributeTokens + ): array { + if ([] !== $attributeTokens) { + $attributeTokens = [ + new Token('('), + ...$attributeTokens, + new Token(')'), + ]; + } + + return [ + clone $tokens[$index + 1], + new Token([T_ATTRIBUTE, '#[']), + new Token([T_NS_SEPARATOR, '\\']), + new Token([T_STRING, 'PHPUnit']), + new Token([T_NS_SEPARATOR, '\\']), + new Token([T_STRING, 'Framework']), + new Token([T_NS_SEPARATOR, '\\']), + new Token([T_STRING, 'Attributes']), + new Token([T_NS_SEPARATOR, '\\']), + new Token([T_STRING, $className]), + ...$attributeTokens, + new Token([CT::T_ATTRIBUTE_CLOSE, ']']), + ]; + } + + /** + * @param class-string $name + * + * @return list + */ + private static function toClassConstant(string $name): array + { + return [ + ...ImportProcessor::tokenizeName($name), + new Token([T_DOUBLE_COLON, '::']), + new Token([CT::T_CLASS_CONSTANT, 'class']), + ]; + } + + private static function createEscapedStringToken(string $value): Token + { + return new Token([T_CONSTANT_ENCAPSED_STRING, "'".str_replace("'", "\\'", $value)."'"]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php index 104f97d76d..c0b6efaede 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitConstructFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -29,30 +30,26 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * assertions?: list<'assertEquals'|'assertNotEquals'|'assertNotSame'|'assertSame'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * assertions: list<'assertEquals'|'assertNotEquals'|'assertNotSame'|'assertSame'> + * } */ final class PhpUnitConstructFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { - /** - * @var array - */ - private static array $assertionFixers = [ - 'assertSame' => 'fixAssertPositive', - 'assertEquals' => 'fixAssertPositive', - 'assertNotEquals' => 'fixAssertNegative', - 'assertNotSame' => 'fixAssertNegative', - ]; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -100,20 +97,24 @@ public function getPriority(): int } /** - * {@inheritdoc} + * @uses fixAssertNegative() + * @uses fixAssertPositive() */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { // no assertions to be fixed - fast return - if (empty($this->configuration['assertions'])) { + if ([] === $this->configuration['assertions']) { return; } foreach ($this->configuration['assertions'] as $assertionMethod) { - $assertionFixer = self::$assertionFixers[$assertionMethod]; - for ($index = $startIndex; $index < $endIndex; ++$index) { - $index = $this->{$assertionFixer}($tokens, $index, $assertionMethod); + $index = \call_user_func_array( + \in_array($assertionMethod, ['assertSame', 'assertEquals'], true) + ? [$this, 'fixAssertPositive'] + : [$this, 'fixAssertNegative'], + [$tokens, $index, $assertionMethod] + ); if (null === $index) { break; @@ -122,21 +123,20 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { + $assertMethods = [ + 'assertEquals', + 'assertSame', + 'assertNotEquals', + 'assertNotSame', + ]; + return new FixerConfigurationResolver([ (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) - ->setAllowedTypes(['array']) - ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionFixers))]) - ->setDefault([ - 'assertEquals', - 'assertSame', - 'assertNotEquals', - 'assertNotSame', - ]) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset($assertMethods)]) + ->setDefault($assertMethods) ->getOption(), ]); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php new file mode 100644 index 0000000000..3da661d3f3 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php @@ -0,0 +1,187 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * prefix?: string, + * suffix?: string + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * prefix: string, + * suffix: string + * } + */ +final class PhpUnitDataProviderNameFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Data provider names must match the name of the test.', + [ + new CodeSample( + ' 'data_', + 'suffix' => '', + ] + ), + new CodeSample( + ' 'provides', + 'suffix' => 'Data', + ] + ), + ], + null, + 'Fixer could be risky if one is calling data provider by name as function.' + ); + } + + public function getPriority(): int + { + return 0; + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('prefix', 'Prefix that replaces "test".')) + ->setAllowedTypes(['string']) + ->setDefault('provide') + ->getOption(), + (new FixerOptionBuilder('suffix', 'Suffix to be present at the end.')) + ->setAllowedTypes(['string']) + ->setDefault('Cases') + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $dataProviderAnalyzer = new DataProviderAnalyzer(); + foreach ($dataProviderAnalyzer->getDataProviders($tokens, $startIndex, $endIndex) as $dataProviderAnalysis) { + if (\count($dataProviderAnalysis->getUsageIndices()) > 1) { + continue; + } + + $usageIndex = $dataProviderAnalysis->getUsageIndices()[0]; + if (substr_count($tokens[$usageIndex]->getContent(), '@dataProvider') > 1) { + continue; + } + + $testNameIndex = $tokens->getNextTokenOfKind($usageIndex, [[T_STRING]]); + + $dataProviderNewName = $this->getProviderNameForTestName($tokens[$testNameIndex]->getContent()); + if (null !== $tokens->findSequence([[T_FUNCTION], [T_STRING, $dataProviderNewName]], $startIndex, $endIndex)) { + continue; + } + + $tokens[$dataProviderAnalysis->getNameIndex()] = new Token([T_STRING, $dataProviderNewName]); + + $newCommentContent = Preg::replace( + \sprintf('/(@dataProvider\s+)%s/', $dataProviderAnalysis->getName()), + \sprintf('$1%s', $dataProviderNewName), + $tokens[$usageIndex]->getContent(), + ); + + $tokens[$usageIndex] = new Token([T_DOC_COMMENT, $newCommentContent]); + } + } + + private function getProviderNameForTestName(string $name): string + { + $name = Preg::replace('/^test_*/i', '', $name); + + if ('' === $this->configuration['prefix']) { + $name = lcfirst($name); + } elseif ('_' !== substr($this->configuration['prefix'], -1)) { + $name = ucfirst($name); + } + + return $this->configuration['prefix'].$name.$this->configuration['suffix']; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderReturnTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderReturnTypeFixer.php new file mode 100644 index 0000000000..4c077585d0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderReturnTypeFixer.php @@ -0,0 +1,120 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class PhpUnitDataProviderReturnTypeFixer extends AbstractPhpUnitFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'The return type of PHPUnit data provider must be `iterable`.', + [ + new CodeSample( + 'getDataProviders($tokens, $startIndex, $endIndex)) as $dataProviderAnalysis) { + $typeAnalysis = $functionsAnalyzer->getFunctionReturnType($tokens, $dataProviderAnalysis->getNameIndex()); + + if (null === $typeAnalysis) { + $argumentsStart = $tokens->getNextTokenOfKind($dataProviderAnalysis->getNameIndex(), ['(']); + $argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart); + + $tokens->insertAt( + $argumentsEnd + 1, + [ + new Token([CT::T_TYPE_COLON, ':']), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'iterable']), + ], + ); + + continue; + } + + if ('iterable' === $typeAnalysis->getName()) { + continue; + } + + $typeStartIndex = $tokens->getNextMeaningfulToken($typeAnalysis->getStartIndex() - 1); + $typeEndIndex = $typeAnalysis->getEndIndex(); + + // @TODO: drop condition and it's body when PHP 8+ is required + if ($tokens->generatePartialCode($typeStartIndex, $typeEndIndex) !== $typeAnalysis->getName()) { + continue; + } + + $tokens->overrideRange($typeStartIndex, $typeEndIndex, [new Token([T_STRING, 'iterable'])]); + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderStaticFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderStaticFixer.php new file mode 100644 index 0000000000..d9ba966e0b --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDataProviderStaticFixer.php @@ -0,0 +1,146 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\AbstractPhpUnitFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\DataProviderAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Kuba Werłos + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * force?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * force: bool + * } + */ +final class PhpUnitDataProviderStaticFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Data providers must be static.', + [ + new CodeSample( + 'getData1(); } + public function provideSomethingCases2() { self::getData2(); } +} +', + ['force' => true] + ), + new CodeSample( + 'getData1(); } + public function provideSomething2Cases() { self::getData2(); } +} +', + ['force' => false] + ), + ], + null, + 'Fixer could be risky if one is calling data provider function dynamically.' + ); + } + + public function isRisky(): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'force', + 'Whether to make the data providers static even if they have a dynamic class call' + .' (may introduce fatal error "using $this when not in object context",' + .' and you may have to adjust the code manually by converting dynamic calls to static ones).' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void + { + $dataProviderAnalyzer = new DataProviderAnalyzer(); + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $inserts = []; + foreach ($dataProviderAnalyzer->getDataProviders($tokens, $startIndex, $endIndex) as $dataProviderDefinitionIndex) { + $methodStartIndex = $tokens->getNextTokenOfKind($dataProviderDefinitionIndex->getNameIndex(), ['{']); + if (null !== $methodStartIndex) { + $methodEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStartIndex); + + if (false === $this->configuration['force'] && null !== $tokens->findSequence([[T_VARIABLE, '$this']], $methodStartIndex, $methodEndIndex)) { + continue; + } + } + + /** @var int $functionIndex */ + $functionIndex = $tokens->getPrevTokenOfKind($dataProviderDefinitionIndex->getNameIndex(), [[T_FUNCTION]]); + + $methodAttributes = $tokensAnalyzer->getMethodAttributes($functionIndex); + if (false !== $methodAttributes['static']) { + continue; + } + + $inserts[$functionIndex] = [new Token([T_STATIC, 'static']), new Token([T_WHITESPACE, ' '])]; + } + $tokens->insertSlices($inserts); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php index 024f0a49d7..07688253e0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -23,18 +24,29 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; -use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * target?: '3.0'|'3.5'|'5.0'|'5.6'|'newest' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * target: '3.0'|'3.5'|'5.0'|'5.6'|'newest' + * } */ final class PhpUnitDedicateAssertFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var array + * @var array */ private static array $fixMap = [ 'array_key_exists' => [ @@ -109,17 +121,65 @@ final class PhpUnitDedicateAssertFixer extends AbstractPhpUnitFixer implements C ]; /** - * @var string[] + * @var list */ private array $functions = []; + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.', + [ + new CodeSample( + 'assertTrue(is_float( $a), "my message"); + $this->assertTrue(is_nan($a)); + } +} +' + ), + new CodeSample( + 'assertTrue(is_dir($a)); + $this->assertTrue(is_writable($a)); + $this->assertTrue(is_readable($a)); + } +} +', + ['target' => PhpUnitTargetVersion::VERSION_5_6] + ), + ], + null, + 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' + ); + } + /** * {@inheritdoc} + * + * Must run before NoUnusedImportsFixer, PhpUnitAssertNewNamesFixer, PhpUnitDedicateAssertInternalTypeFixer. + * Must run after ModernizeStrposFixer, NoAliasFunctionsFixer, PhpUnitConstructFixer. */ - public function configure(array $configuration): void + public function getPriority(): int { - parent::configure($configuration); + return -9; + } + protected function configurePostNormalisation(): void + { // assertions added in 3.0: assertArrayNotHasKey assertArrayHasKey assertFileNotExists assertFileExists assertNotNull, assertNull $this->functions = [ 'array_key_exists', @@ -174,96 +234,28 @@ public function configure(array $configuration): void } } - /** - * {@inheritdoc} - */ - public function isRisky(): bool - { - return true; - } - - /** - * {@inheritdoc} - */ - public function getDefinition(): FixerDefinitionInterface - { - return new FixerDefinition( - 'PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.', - [ - new CodeSample( - 'assertTrue(is_float( $a), "my message"); - $this->assertTrue(is_nan($a)); - } -} -' - ), - new CodeSample( - 'assertTrue(is_dir($a)); - $this->assertTrue(is_writable($a)); - $this->assertTrue(is_readable($a)); - } -} -', - ['target' => PhpUnitTargetVersion::VERSION_5_6] - ), - ], - null, - 'Fixer could be risky if one is overriding PHPUnit\'s native methods.' - ); - } - - /** - * {@inheritdoc} - * - * Must run before NoUnusedImportsFixer, PhpUnitDedicateAssertInternalTypeFixer. - * Must run after ModernizeStrposFixer, NoAliasFunctionsFixer, PhpUnitConstructFixer. - */ - public function getPriority(): int - { - return -9; - } - - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); foreach ($this->getPreviousAssertCall($tokens, $startIndex, $endIndex) as $assertCall) { // test and fix for assertTrue/False to dedicated asserts - if ('asserttrue' === $assertCall['loweredName'] || 'assertfalse' === $assertCall['loweredName']) { + if (\in_array($assertCall['loweredName'], ['asserttrue', 'assertfalse'], true)) { $this->fixAssertTrueFalse($tokens, $argumentsAnalyzer, $assertCall); continue; } - if ( - 'assertsame' === $assertCall['loweredName'] - || 'assertnotsame' === $assertCall['loweredName'] - || 'assertequals' === $assertCall['loweredName'] - || 'assertnotequals' === $assertCall['loweredName'] - ) { + if (\in_array( + $assertCall['loweredName'], + ['assertsame', 'assertnotsame', 'assertequals', 'assertnotequals'], + true + )) { $this->fixAssertSameEquals($tokens, $assertCall); - - continue; } } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -281,6 +273,14 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ private function fixAssertTrueFalse(Tokens $tokens, ArgumentsAnalyzer $argumentsAnalyzer, array $assertCall): void { $testDefaultNamespaceTokenIndex = null; @@ -375,6 +375,14 @@ private function fixAssertTrueFalse(Tokens $tokens, ArgumentsAnalyzer $arguments } } + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ private function fixAssertTrueFalseInstanceof(Tokens $tokens, array $assertCall, int $testIndex): bool { if ($tokens[$testIndex]->equals('!')) { @@ -405,7 +413,7 @@ private function fixAssertTrueFalseInstanceof(Tokens $tokens, array $assertCall, if ($tokens[$classEndIndex]->equalsAny([',', ')'])) { // do the fixing array_pop($classPartTokens); - $isInstanceOfVar = (reset($classPartTokens))->isGivenKind(T_VARIABLE); + $isInstanceOfVar = reset($classPartTokens)->isGivenKind(T_VARIABLE); $insertIndex = $testIndex - 1; $newTokens = []; @@ -435,6 +443,14 @@ private function fixAssertTrueFalseInstanceof(Tokens $tokens, array $assertCall, return true; } + /** + * @param array{ + * index: int, + * loweredName: string, + * openBraceIndex: int, + * closeBraceIndex: int, + * } $assertCall + */ private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void { // @ $this->/self::assertEquals/Same([$nextIndex]) @@ -475,7 +491,7 @@ private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void $lowerContent = strtolower($tokens[$countCallIndex]->getContent()); - if ('count' !== $lowerContent && 'sizeof' !== $lowerContent) { + if (!\in_array($lowerContent, ['count', 'sizeof'], true)) { return; // not a call to "count" or "sizeOf" } @@ -507,44 +523,6 @@ private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void ]); } - private function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $endIndex): iterable - { - $functionsAnalyzer = new FunctionsAnalyzer(); - - for ($index = $endIndex; $index > $startIndex; --$index) { - $index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]); - - if (null === $index) { - return; - } - - // test if "assert" something call - $loweredContent = strtolower($tokens[$index]->getContent()); - - if (!str_starts_with($loweredContent, 'assert')) { - continue; - } - - // test candidate for simple calls like: ([\]+'some fixable call'(...)) - $openBraceIndex = $tokens->getNextMeaningfulToken($index); - - if (!$tokens[$openBraceIndex]->equals('(')) { - continue; - } - - if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) { - continue; - } - - yield [ - 'index' => $index, - 'loweredName' => $loweredContent, - 'openBraceIndex' => $openBraceIndex, - 'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex), - ]; - } - } - private function removeFunctionCall(Tokens $tokens, ?int $callNSIndex, int $callIndex, int $openIndex, int $closeIndex): void { $tokens->clearTokenAndMergeSurroundingWhitespace($callIndex); @@ -564,6 +542,9 @@ private function removeFunctionCall(Tokens $tokens, ?int $callNSIndex, int $call $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); } + /** + * @param array $argumentsIndices + */ private function swapArguments(Tokens $tokens, array $argumentsIndices): void { [$firstArgumentIndex, $secondArgumentIndex] = array_keys($argumentsIndices); @@ -587,6 +568,9 @@ private function swapArguments(Tokens $tokens, array $argumentsIndices): void $tokens->insertAt($firstArgumentIndex, $secondClone); } + /** + * @return list + */ private function cloneAndClearTokens(Tokens $tokens, int $start, int $end): array { $clone = []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php index 0f98f272dd..ece43be2a9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitDedicateAssertInternalTypeFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,9 +29,24 @@ /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * target?: '7.5'|'newest' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * target: '7.5'|'newest' + * } */ final class PhpUnitDedicateAssertInternalTypeFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** + * @var array + */ private array $typeToDedicatedAssertMap = [ 'array' => 'assertIsArray', 'boolean' => 'assertIsBool', @@ -50,9 +66,6 @@ final class PhpUnitDedicateAssertInternalTypeFixer extends AbstractPhpUnitFixer 'iterable' => 'assertIsIterable', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -89,9 +102,6 @@ public function testMe() ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -100,16 +110,13 @@ public function isRisky(): bool /** * {@inheritdoc} * - * Must run after PhpUnitDedicateAssertFixer. + * Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer, PhpUnitDedicateAssertFixer. */ public function getPriority(): int { return -16; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -121,9 +128,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $anonymousClassIndices = []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php index 7f2c3530bc..24ef2b7971 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -30,38 +31,26 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * target?: '5.2'|'5.6'|'8.4'|'newest' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * target: '5.2'|'5.6'|'8.4'|'newest' + * } */ final class PhpUnitExpectationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var array */ private array $methodMap = []; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->methodMap = [ - 'setExpectedException' => 'expectExceptionMessage', - ]; - - if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { - $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp'; - } - - if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_8_4)) { - $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageMatches'; - $this->methodMap['expectExceptionMessageRegExp'] = 'expectExceptionMessageMatches'; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -158,17 +147,27 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->methodMap = [ + 'setExpectedException' => 'expectExceptionMessage', + ]; + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) { + $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp'; + } + + if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_8_4)) { + $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageMatches'; + $this->methodMap['expectExceptionMessageRegExp'] = 'expectExceptionMessageMatches'; + } + } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -180,9 +179,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { foreach (Token::getObjectOperatorKinds() as $objectOperator) { @@ -234,6 +230,10 @@ private function applyPhpUnitClassFixWithObjectOperator(Tokens $tokens, int $sta $argStart = array_keys($arguments)[$cnt]; $argBefore = $tokens->getPrevMeaningfulToken($argStart); + if (!isset($argumentsReplacements[$cnt])) { + throw new \LogicException(\sprintf('Unexpected index %d to find replacement method.', $cnt)); + } + if ('expectExceptionMessage' === $argumentsReplacements[$cnt]) { $paramIndicatorIndex = $tokens->getNextMeaningfulToken($argBefore); $afterParamIndicatorIndex = $tokens->getNextMeaningfulToken($paramIndicatorIndex); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php index dd440ecd0a..46bb9a7da4 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php @@ -27,9 +27,6 @@ */ final class PhpUnitFqcnAnnotationFixer extends AbstractPhpUnitFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -63,9 +60,6 @@ public function getPriority(): int return -9; } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $prevDocCommentIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_DOC_COMMENT]]); @@ -83,7 +77,7 @@ private function fixPhpUnitClass(Tokens $tokens, int $startIndex, int $endIndex) if ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace( '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(?!(?:self|static)::)(\w.*)$~m', - '$1\\\\$2', + '$1\\\$2', $tokens[$index]->getContent() )]); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php index b05f052fa3..1a50197f3e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php @@ -14,10 +14,9 @@ namespace PhpCsFixer\Fixer\PhpUnit; -use PhpCsFixer\DocBlock\DocBlock; -use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; @@ -26,18 +25,26 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; -use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * @author Gert de Pagter + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * types?: list<'abstract'|'final'|'normal'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * types: list<'abstract'|'final'|'normal'> + * } */ final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -55,32 +62,26 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before FinalInternalClassFixer. + * Must run before FinalInternalClassFixer, PhpdocSeparationFixer. */ public function getPriority(): int { return 68; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $types = ['normal', 'final', 'abstract']; return new FixerConfigurationResolver([ - (new FixerOptionBuilder('types', 'What types of classes to mark as internal')) - ->setAllowedValues([(new AllowedValueSubset($types))]) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('types', 'What types of classes to mark as internal.')) + ->setAllowedValues([new AllowedValueSubset($types)]) + ->setAllowedTypes(['string[]']) ->setDefault(['normal', 'final']) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); @@ -89,77 +90,28 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en return; } - $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex); - - if ($this->isPHPDoc($tokens, $docBlockIndex)) { - $this->updateDocBlockIfNeeded($tokens, $docBlockIndex); - } else { - $this->createDocBlock($tokens, $docBlockIndex); - } + $this->ensureIsDocBlockWithAnnotation( + $tokens, + $classIndex, + 'internal', + ['internal'], + [], + ); } - private function isAllowedByConfiguration(Tokens $tokens, int $i): bool + private function isAllowedByConfiguration(Tokens $tokens, int $index): bool { - $typeIndex = $tokens->getPrevMeaningfulToken($i); - if ($tokens[$typeIndex]->isGivenKind(T_FINAL)) { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $modifiers = $tokensAnalyzer->getClassyModifiers($index); + + if (isset($modifiers['final'])) { return \in_array('final', $this->configuration['types'], true); } - if ($tokens[$typeIndex]->isGivenKind(T_ABSTRACT)) { + if (isset($modifiers['abstract'])) { return \in_array('abstract', $this->configuration['types'], true); } return \in_array('normal', $this->configuration['types'], true); } - - private function createDocBlock(Tokens $tokens, int $docBlockIndex): void - { - $lineEnd = $this->whitespacesConfig->getLineEnding(); - $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); - $toInsert = [ - new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @internal".$lineEnd."{$originalIndent} */"]), - new Token([T_WHITESPACE, $lineEnd.$originalIndent]), - ]; - $index = $tokens->getNextMeaningfulToken($docBlockIndex); - $tokens->insertAt($index, $toInsert); - } - - private function updateDocBlockIfNeeded(Tokens $tokens, int $docBlockIndex): void - { - $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); - if (!empty($doc->getAnnotationsOfType('internal'))) { - return; - } - $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex); - $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex); - $lines = implode('', $lines); - - $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); - } - - /** - * @return Line[] - */ - private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex): array - { - $lines = $docBlock->getLines(); - $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); - $lineEnd = $this->whitespacesConfig->getLineEnding(); - array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @internal'.$lineEnd); - - return $lines; - } - - private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex): DocBlock - { - $lines = $doc->getLines(); - if (1 === \count($lines) && empty($doc->getAnnotationsOfType('internal'))) { - $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); - $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding()); - - return $doc; - } - - return $doc; - } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php index 949b4008c6..230535c80c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -32,9 +33,21 @@ /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case?: 'camel_case'|'snake_case' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case: 'camel_case'|'snake_case' + * } */ final class PhpUnitMethodCasingFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -45,9 +58,6 @@ final class PhpUnitMethodCasingFixer extends AbstractPhpUnitFixer implements Con */ public const SNAKE_CASE = 'snake_case'; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -55,7 +65,7 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( 'setAllowedValues([self::CAMEL_CASE, self::SNAKE_CASE]) ->setDefault(self::CAMEL_CASE) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { for ($index = $endIndex - 1; $index > $startIndex; --$index) { @@ -162,8 +166,7 @@ private function isTestMethod(Tokens $tokens, int $index): bool return $this->isPHPDoc($tokens, $docBlockIndex) // If the function doesn't have test in its name, and no doc block, it's not a test - && str_contains($tokens[$docBlockIndex]->getContent(), '@test') - ; + && str_contains($tokens[$docBlockIndex]->getContent(), '@test'); } private function isMethod(Tokens $tokens, int $index): bool @@ -185,14 +188,12 @@ private function updateDocBlock(Tokens $tokens, int $docBlockIndex): void continue; } - $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', function (array $matches): string { - return sprintf( - '%s%s%s', - $matches[1], - $this->updateMethodCasing($matches[2]), - $matches[3] - ); - }, $lineContent); + $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', fn (array $matches): string => \sprintf( + '%s%s%s', + $matches[1], + $this->updateMethodCasing($matches[2]), + $matches[3] + ), $lineContent); if ($newLineContent !== $lineContent) { $lines[$inc] = new Line($newLineContent); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php index 8c5794e9ac..5e54192619 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,17 +29,26 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * target?: '5.4'|'5.5'|'newest' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * target: '5.4'|'5.5'|'newest' + * } */ final class PhpUnitMockFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var bool */ private $fixCreatePartialMock; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -77,27 +87,16 @@ public function testFoo() ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - $this->fixCreatePartialMock = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_5); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); @@ -126,9 +125,6 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php index ed25317521..3350b669b1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitMockShortWillReturnFixer.php @@ -37,9 +37,6 @@ final class PhpUnitMockShortWillReturnFixer extends AbstractPhpUnitFixer 'returnvaluemap' => 'willReturnMap', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -65,17 +62,11 @@ public function testSomeTest() ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -110,10 +101,6 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en $openingBraceIndex = $tokens->getNextMeaningfulToken($functionToRemoveIndex); - if (!$tokens[$openingBraceIndex]->equals('(')) { - continue; - } - if ($tokens[$tokens->getNextMeaningfulToken($openingBraceIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) { continue; } @@ -126,6 +113,11 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en $tokens->clearTokenAndMergeSurroundingWhitespace($functionToRemoveIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($openingBraceIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($closingBraceIndex); + + $commaAfterClosingBraceIndex = $tokens->getNextMeaningfulToken($closingBraceIndex); + if ($tokens[$commaAfterClosingBraceIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($commaAfterClosingBraceIndex); + } } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php index f374f9c8b3..0a582b09fc 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -23,14 +24,27 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\ClassyAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * target?: '4.8'|'5.7'|'6.0'|'newest' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * target: '4.8'|'5.7'|'6.0'|'newest' + * } */ final class PhpUnitNamespacedFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var string */ @@ -44,13 +58,10 @@ final class PhpUnitNamespacedFixer extends AbstractFixer implements Configurable * space class and need a dedicated translation table. This trans- * lation table is defined in @see configure. * - * @var array|string[] Class Mappings + * @var array Class Mappings */ private $classMap; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { $codeSample = 'isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_6_0)) { $this->originalClassRegEx = '/^PHPUnit_\w+$/i'; // @noinspection ClassConstantCanBeUsedInspection @@ -133,7 +133,7 @@ public function configure(array $configuration): void 'PHPUnit_Util_XML' => 'PHPUnit\Util\Xml', ]; } elseif (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_7)) { - $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase|PHPUnit_Framework_Assert|PHPUnit_Framework_BaseTestListener|PHPUnit_Framework_TestListener$/i'; + $this->originalClassRegEx = '/^PHPUnit_Framework_(TestCase|Assert|BaseTestListener|TestListener)+$/i'; $this->classMap = []; } else { $this->originalClassRegEx = '/^PHPUnit_Framework_TestCase$/i'; @@ -141,9 +141,6 @@ public function configure(array $configuration): void } } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $importedOriginalClassesMap = []; @@ -163,8 +160,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $originalClass = $tokens[$currIndex]->getContent(); + $allowedReplacementScenarios = (new ClassyAnalyzer())->isClassyInvocation($tokens, $currIndex) + || $this->isImport($tokens, $currIndex); - if (1 !== Preg::match($this->originalClassRegEx, $originalClass)) { + if (!$allowedReplacementScenarios || !Preg::match($this->originalClassRegEx, $originalClass)) { ++$currIndex; continue; @@ -191,9 +190,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -210,21 +206,33 @@ private function generateReplacement(string $originalClassName): Tokens $delimiter = '_'; $string = $originalClassName; - if (isset($this->classMap[$originalClassName])) { + $map = array_change_key_case($this->classMap); + if (isset($map[strtolower($originalClassName)])) { $delimiter = '\\'; - $string = $this->classMap[$originalClassName]; + $string = $map[strtolower($originalClassName)]; } $parts = explode($delimiter, $string); $tokensArray = []; - while (!empty($parts)) { + while ([] !== $parts) { $tokensArray[] = new Token([T_STRING, array_shift($parts)]); - if (!empty($parts)) { + if ([] !== $parts) { $tokensArray[] = new Token([T_NS_SEPARATOR, '\\']); } } return Tokens::fromArray($tokensArray); } + + private function isImport(Tokens $tokens, int $currIndex): bool + { + $prevIndex = $tokens->getPrevMeaningfulToken($currIndex); + + if ($tokens[$prevIndex]->isGivenKind([T_NS_SEPARATOR])) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + return $tokens[$prevIndex]->isGivenKind([T_USE]); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php index fc729f3ed3..f50a7c5eef 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -33,27 +34,28 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * target?: '3.2'|'4.3'|'newest', + * use_class_const?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * target: '3.2'|'4.3'|'newest', + * use_class_const: bool + * } */ final class PhpUnitNoExpectationAnnotationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @var bool */ private $fixMessageRegExp; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->fixMessageRegExp = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_4_3); - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -116,17 +118,16 @@ public function getPriority(): int return 10; } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->fixMessageRegExp = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_4_3); + } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -142,9 +143,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -189,6 +187,7 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en if (!isset($annotations['expectedException'])) { continue; } + if (!$this->fixMessageRegExp && isset($annotations['expectedExceptionMessageRegExp'])) { continue; } @@ -230,7 +229,7 @@ private function extractContentFromAnnotation(Annotation $annotation): string { $tag = $annotation->getTag()->getName(); - if (1 !== Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches)) { + if (!Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches)) { return ''; } @@ -243,13 +242,20 @@ private function extractContentFromAnnotation(Annotation $annotation): string return rtrim($content); } + /** + * @param array $annotations + * + * @return list + */ private function annotationsToParamList(array $annotations): array { $params = []; $exceptionClass = ltrim($annotations['expectedException'], '\\'); + if (str_contains($exceptionClass, '*')) { $exceptionClass = substr($exceptionClass, 0, strpos($exceptionClass, '*')); } + $exceptionClass = trim($exceptionClass); if (true === $this->configuration['use_class_const']) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php index 3db4a3e094..3fbd98ce4d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSetUpTearDownVisibilityFixer.php @@ -27,9 +27,6 @@ */ final class PhpUnitSetUpTearDownVisibilityFixer extends AbstractPhpUnitFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -59,17 +56,11 @@ public function tearDown() ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $counter = 0; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php index 6bb4333dc0..669bb78aae 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php @@ -14,11 +14,9 @@ namespace PhpCsFixer\Fixer\PhpUnit; -use PhpCsFixer\DocBlock\Annotation; -use PhpCsFixer\DocBlock\DocBlock; -use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -26,18 +24,27 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; -use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Jefersson Nathan + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * group?: 'large'|'medium'|'small' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * group: 'large'|'medium'|'small' + * } */ final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + private const SIZES = ['small', 'medium', 'large']; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -52,20 +59,24 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} + * + * Must run before PhpdocSeparationFixer. */ + public function getPriority(): int + { + return 0; + } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('group', 'Define a specific group to be used in case no group is already in use')) - ->setAllowedValues(['small', 'medium', 'large']) + (new FixerOptionBuilder('group', 'Define a specific group to be used in case no group is already in use.')) + ->setAllowedValues(self::SIZES) ->setDefault('small') ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); @@ -74,13 +85,17 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en return; } - $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex); - - if ($this->isPHPDoc($tokens, $docBlockIndex)) { - $this->updateDocBlockIfNeeded($tokens, $docBlockIndex); - } else { - $this->createDocBlock($tokens, $docBlockIndex); - } + $this->ensureIsDocBlockWithAnnotation( + $tokens, + $classIndex, + $this->configuration['group'], + self::SIZES, + [ + 'phpunit\framework\attributes\small', + 'phpunit\framework\attributes\medium', + 'phpunit\framework\attributes\large', + ], + ); } private function isAbstractClass(Tokens $tokens, int $i): bool @@ -89,112 +104,4 @@ private function isAbstractClass(Tokens $tokens, int $i): bool return $tokens[$typeIndex]->isGivenKind(T_ABSTRACT); } - - private function createDocBlock(Tokens $tokens, int $docBlockIndex): void - { - $lineEnd = $this->whitespacesConfig->getLineEnding(); - $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); - $group = $this->configuration['group']; - $toInsert = [ - new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @".$group.$lineEnd."{$originalIndent} */"]), - new Token([T_WHITESPACE, $lineEnd.$originalIndent]), - ]; - $index = $tokens->getNextMeaningfulToken($docBlockIndex); - $tokens->insertAt($index, $toInsert); - } - - private function updateDocBlockIfNeeded(Tokens $tokens, int $docBlockIndex): void - { - $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); - - if (0 !== \count($this->filterDocBlock($doc))) { - return; - } - - $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex); - $lines = $this->addSizeAnnotation($doc, $tokens, $docBlockIndex); - $lines = implode('', $lines); - - $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); - } - - /** - * @return Line[] - */ - private function addSizeAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex): array - { - $lines = $docBlock->getLines(); - $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); - $lineEnd = $this->whitespacesConfig->getLineEnding(); - $group = $this->configuration['group']; - array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @'.$group.$lineEnd); - - return $lines; - } - - private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex): DocBlock - { - $lines = $doc->getLines(); - - if (1 === \count($lines) && 0 === \count($this->filterDocBlock($doc))) { - $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); - - return new DocBlock(implode('', $lines)); - } - - return $doc; - } - - /** - * Take a one line doc block, and turn it into a multi line doc block. - * - * @param Line[] $lines - * - * @return Line[] - */ - private function splitUpDocBlock(array $lines, Tokens $tokens, int $docBlockIndex): array - { - $lineContent = $this->getSingleLineDocBlockEntry($lines[0]); - $lineEnd = $this->whitespacesConfig->getLineEnding(); - $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex)); - - return [ - new Line('/**'.$lineEnd), - new Line($originalIndent.' * '.$lineContent.$lineEnd), - new Line($originalIndent.' */'), - ]; - } - - /** - * @todo check whether it's doable to use \PhpCsFixer\DocBlock\DocBlock::getSingleLineDocBlockEntry instead - */ - private function getSingleLineDocBlockEntry(Line $line): string - { - $line = $line->getContent(); - $line = str_replace('*/', '', $line); - $line = trim($line); - $line = str_split($line); - $i = \count($line); - do { - --$i; - } while ('*' !== $line[$i] && '*' !== $line[$i - 1] && '/' !== $line[$i - 2]); - if (' ' === $line[$i]) { - ++$i; - } - $line = \array_slice($line, $i); - - return implode('', $line); - } - - /** - * @return Annotation[][] - */ - private function filterDocBlock(DocBlock $doc): array - { - return array_filter([ - $doc->getAnnotationsOfType('small'), - $doc->getAnnotationsOfType('large'), - $doc->getAnnotationsOfType('medium'), - ]); - } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php index 914e1b0f3a..c9f9552d48 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitStrictFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -30,11 +31,23 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * assertions?: list<'assertAttributeEquals'|'assertAttributeNotEquals'|'assertEquals'|'assertNotEquals'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * assertions: list<'assertAttributeEquals'|'assertAttributeNotEquals'|'assertEquals'|'assertNotEquals'> + * } */ final class PhpUnitStrictFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var array + * @var array */ private static array $assertionMap = [ 'assertAttributeEquals' => 'assertAttributeSame', @@ -43,9 +56,6 @@ final class PhpUnitStrictFixer extends AbstractPhpUnitFixer implements Configura 'assertNotEquals' => 'assertNotSame', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -86,17 +96,11 @@ public function testSomeTest() ); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); @@ -132,14 +136,11 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('assertions', 'List of assertion methods to fix.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionMap))]) ->setDefault([ 'assertAttributeEquals', diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php index c8122c67de..8ecc1c2db8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTargetVersion.php @@ -37,16 +37,15 @@ final class PhpUnitTargetVersion public const VERSION_6_0 = '6.0'; public const VERSION_7_5 = '7.5'; public const VERSION_8_4 = '8.4'; + public const VERSION_9_1 = '9.1'; public const VERSION_NEWEST = 'newest'; - private function __construct() - { - } + private function __construct() {} public static function fulfills(string $candidate, string $target): bool { if (self::VERSION_NEWEST === $target) { - throw new \LogicException(sprintf('Parameter `target` shall not be provided as "%s", determine proper target for tested PHPUnit feature instead.', self::VERSION_NEWEST)); + throw new \LogicException(\sprintf('Parameter `target` shall not be provided as "%s", determine proper target for tested PHPUnit feature instead.', self::VERSION_NEWEST)); } if (self::VERSION_NEWEST === $candidate) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php index 83e408b6c7..8b52434600 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -33,34 +34,40 @@ /** * @author Gert de Pagter + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * style?: 'annotation'|'prefix' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * style: 'annotation'|'prefix' + * } */ final class PhpUnitTestAnnotationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Adds or removes @test annotations from tests, following configuration.', [ new CodeSample('whitespacesConfig->getLineEnding()), new CodeSample('whitespacesConfig->getLineEnding(), ['style' => 'annotation']), ], @@ -80,9 +87,6 @@ public function getPriority(): int return 10; } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { if ('annotation' === $this->configuration['style']) { @@ -92,9 +96,6 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -185,8 +186,7 @@ private function isTestMethod(Tokens $tokens, int $index): bool // If the function doesn't have test in its name, and no doc block, it is not a test return $this->isPHPDoc($tokens, $docBlockIndex) - && str_contains($tokens[$docBlockIndex]->getContent(), '@test') - ; + && str_contains($tokens[$docBlockIndex]->getContent(), '@test'); } private function isMethod(Tokens $tokens, int $index): bool @@ -206,7 +206,7 @@ private function hasProperTestAnnotation(Tokens $tokens, int $index): bool $docBlockIndex = $this->getDocBlockIndex($tokens, $index); $doc = $tokens[$docBlockIndex]->getContent(); - return 1 === Preg::match('/\*\s+@test\b/', $doc); + return Preg::match('/\*\s+@test\b/', $doc); } private function removeTestPrefix(string $functionName): string @@ -238,7 +238,7 @@ private function createDocBlock(Tokens $tokens, int $docBlockIndex): void } /** - * @return Line[] + * @return list */ private function updateDocBlock(Tokens $tokens, int $docBlockIndex): array { @@ -249,9 +249,9 @@ private function updateDocBlock(Tokens $tokens, int $docBlockIndex): array } /** - * @param Line[] $lines + * @param list $lines * - * @return Line[] + * @return list */ private function updateLines(array $lines, Tokens $tokens, int $docBlockIndex): array { @@ -291,9 +291,9 @@ private function updateLines(array $lines, Tokens $tokens, int $docBlockIndex): /** * Take a one line doc block, and turn it into a multi line doc block. * - * @param Line[] $lines + * @param non-empty-list $lines * - * @return Line[] + * @return list */ private function splitUpDocBlock(array $lines, Tokens $tokens, int $docBlockIndex): array { @@ -311,7 +311,7 @@ private function splitUpDocBlock(array $lines, Tokens $tokens, int $docBlockInde /** * @todo check whether it's doable to use \PhpCsFixer\DocBlock\DocBlock::getSingleLineDocBlockEntry instead * - * @param Line[] $lines + * @param non-empty-list $lines */ private function getSingleLineDocBlockEntry(array $lines): string { @@ -375,22 +375,24 @@ private function addTestPrefixToDependsAnnotation(Line $line): Line /** * Helps to find where the function name in the doc block starts. + * + * @param list $line */ private function findWhereDependsFunctionNameStarts(array $line): int { - $counter = \count($line); + $index = stripos(implode('', $line), '@depends') + 8; - do { - --$counter; - } while (' ' !== $line[$counter]); + while (' ' === $line[$index]) { + ++$index; + } - return $counter + 1; + return $index; } /** - * @param Line[] $lines + * @param list $lines * - * @return Line[] + * @return list */ private function addTestAnnotation(array $lines, Tokens $tokens, int $docBlockIndex): array { @@ -400,7 +402,8 @@ private function addTestAnnotation(array $lines, Tokens $tokens, int $docBlockIn $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex); $lineEnd = $this->whitespacesConfig->getLineEnding(); - array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @test'.$lineEnd); + array_splice($lines, -1, 0, [new Line($originalIndent.' *'.$lineEnd.$originalIndent.' * @test'.$lineEnd)]); + \assert(array_is_list($lines)); // we know it's list, but we need to tell PHPStan } return $lines; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php index 056addf13d..10b68b2858 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -27,13 +28,28 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Filippo Tessarotto + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * call_type?: 'self'|'static'|'this', + * methods?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * call_type: 'self'|'static'|'this', + * methods: list + * } */ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -50,22 +66,17 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i public const CALL_TYPE_STATIC = 'static'; /** - * @var array + * @var array */ - private array $allowedValues = [ - self::CALL_TYPE_THIS => true, - self::CALL_TYPE_SELF => true, - self::CALL_TYPE_STATIC => true, - ]; - - /** - * @var array - */ - private array $staticMethods = [ + private const STATIC_METHODS = [ // Assert methods 'anything' => true, 'arrayHasKey' => true, 'assertArrayHasKey' => true, + 'assertArrayIsEqualToArrayIgnoringListOfKeys' => true, + 'assertArrayIsEqualToArrayOnlyConsideringListOfKeys' => true, + 'assertArrayIsIdenticalToArrayIgnoringListOfKeys' => true, + 'assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys' => true, 'assertArrayNotHasKey' => true, 'assertArraySubset' => true, 'assertAttributeContains' => true, @@ -108,11 +119,11 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'assertDirectoryNotIsWritable' => true, 'assertDoesNotMatchRegularExpression' => true, 'assertEmpty' => true, - 'assertEqualXMLStructure' => true, 'assertEquals' => true, 'assertEqualsCanonicalizing' => true, 'assertEqualsIgnoringCase' => true, 'assertEqualsWithDelta' => true, + 'assertEqualXMLStructure' => true, 'assertFalse' => true, 'assertFileDoesNotExist' => true, 'assertFileEquals' => true, @@ -123,6 +134,8 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'assertFileIsNotWritable' => true, 'assertFileIsReadable' => true, 'assertFileIsWritable' => true, + 'assertFileMatchesFormat' => true, + 'assertFileMatchesFormatFile' => true, 'assertFileNotEquals' => true, 'assertFileNotEqualsCanonicalizing' => true, 'assertFileNotEqualsIgnoringCase' => true, @@ -142,6 +155,7 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'assertIsFloat' => true, 'assertIsInt' => true, 'assertIsIterable' => true, + 'assertIsList' => true, 'assertIsNotArray' => true, 'assertIsNotBool' => true, 'assertIsNotCallable' => true, @@ -196,17 +210,22 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'assertNull' => true, 'assertObjectEquals' => true, 'assertObjectHasAttribute' => true, + 'assertObjectHasProperty' => true, + 'assertObjectNotEquals' => true, 'assertObjectNotHasAttribute' => true, + 'assertObjectNotHasProperty' => true, 'assertRegExp' => true, 'assertSame' => true, 'assertSameSize' => true, 'assertStringContainsString' => true, 'assertStringContainsStringIgnoringCase' => true, + 'assertStringContainsStringIgnoringLineEndings' => true, 'assertStringEndsNotWith' => true, 'assertStringEndsWith' => true, 'assertStringEqualsFile' => true, 'assertStringEqualsFileCanonicalizing' => true, 'assertStringEqualsFileIgnoringCase' => true, + 'assertStringEqualsStringIgnoringLineEndings' => true, 'assertStringMatchesFormat' => true, 'assertStringMatchesFormatFile' => true, 'assertStringNotContainsString' => true, @@ -256,6 +275,7 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'isInfinite' => true, 'isInstanceOf' => true, 'isJson' => true, + 'isList' => true, 'isNan' => true, 'isNull' => true, 'isReadable' => true, @@ -278,6 +298,7 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'resetCount' => true, 'stringContains' => true, 'stringEndsWith' => true, + 'stringEqualsStringIgnoringLineEndings' => true, 'stringStartsWith' => true, // TestCase methods @@ -300,15 +321,24 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer i 'throwException' => true, ]; + /** + * @var array + */ + private const ALLOWED_VALUES = [ + self::CALL_TYPE_THIS => true, + self::CALL_TYPE_SELF => true, + self::CALL_TYPE_STATIC => true, + ]; + + /** + * @var array> + */ private array $conversionMap = [ self::CALL_TYPE_THIS => [[T_OBJECT_OPERATOR, '->'], [T_VARIABLE, '$this']], self::CALL_TYPE_SELF => [[T_DOUBLE_COLON, '::'], [T_STRING, 'self']], self::CALL_TYPE_STATIC => [[T_DOUBLE_COLON, '::'], [T_STATIC, 'static']], ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { $codeSample = 'setAllowedTypes(['string']) - ->setAllowedValues(array_keys($this->allowedValues)) + ->setAllowedValues(array_keys(self::ALLOWED_VALUES)) ->setDefault('static') ->getOption(), (new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.')) - ->setAllowedTypes(['array']) - ->setAllowedValues([static function (array $option) use ($thisFixer): bool { + ->setAllowedTypes(['string[]']) + ->setAllowedValues([static function (array $option): bool { foreach ($option as $method => $value) { - if (!isset($thisFixer->staticMethods[$method])) { + if (!isset(self::STATIC_METHODS[$method])) { throw new InvalidOptionsException( - sprintf( - 'Unexpected "methods" key, expected any of "%s", got "%s".', - implode('", "', array_keys($thisFixer->staticMethods)), + \sprintf( + 'Unexpected "methods" key, expected any of %s, got "%s".', + Utils::naturalLanguageJoin(array_keys(self::STATIC_METHODS)), \gettype($method).'#'.$method ) ); } - if (!isset($thisFixer->allowedValues[$value])) { + if (!isset(self::ALLOWED_VALUES[$value])) { throw new InvalidOptionsException( - sprintf( - 'Unexpected value for method "%s", expected any of "%s", got "%s".', + \sprintf( + 'Unexpected value for method "%s", expected any of %s, got "%s".', $method, - implode('", "', array_keys($thisFixer->allowedValues)), + Utils::naturalLanguageJoin(array_keys(self::ALLOWED_VALUES)), \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) ) ); @@ -398,9 +420,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $analyzer = new TokensAnalyzer($tokens); @@ -435,7 +454,7 @@ protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $en } } - if (!$tokens[$index]->isGivenKind(T_STRING) || !isset($this->staticMethods[$tokens[$index]->getContent()])) { + if (!$tokens[$index]->isGivenKind(T_STRING) || !isset(self::STATIC_METHODS[$tokens[$index]->getContent()])) { continue; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php index 39f7a846d7..b95abb75f7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php @@ -14,25 +14,19 @@ namespace PhpCsFixer\Fixer\PhpUnit; -use PhpCsFixer\DocBlock\DocBlock; -use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\AbstractPhpUnitFixer; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; /** * @author Dariusz Rumiński */ final class PhpUnitTestClassRequiresCoversFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -55,81 +49,38 @@ public function testSomeTest() /** * {@inheritdoc} + * + * Must run before PhpdocSeparationFixer. */ + public function getPriority(): int + { + return 0; + } + protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void { $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); - $prevIndex = $tokens->getPrevMeaningfulToken($classIndex); - - // don't add `@covers` annotation for abstract base classes - if ($tokens[$prevIndex]->isGivenKind(T_ABSTRACT)) { - return; - } - - $index = $tokens[$prevIndex]->isGivenKind(T_FINAL) ? $prevIndex : $classIndex; - - $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE) - ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent()) - : ''; - - $prevIndex = $tokens->getPrevNonWhitespace($index); - if ($tokens[$prevIndex]->isGivenKind(T_DOC_COMMENT)) { - $docIndex = $prevIndex; - $docContent = $tokens[$docIndex]->getContent(); + $tokensAnalyzer = new TokensAnalyzer($tokens); + $modifiers = $tokensAnalyzer->getClassyModifiers($classIndex); - // ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations - if (!str_contains($docContent, "\n")) { - return; - } - - $doc = new DocBlock($docContent); + if (isset($modifiers['abstract'])) { + return; // don't add `@covers` annotation for abstract base classes + } - // skip if already has annotation - if (0 !== \count($doc->getAnnotationsOfType([ + $this->ensureIsDocBlockWithAnnotation( + $tokens, + $classIndex, + 'coversNothing', + [ 'covers', 'coversDefaultClass', 'coversNothing', - ]))) { - return; - } - } else { - $docIndex = $index; - $tokens->insertAt($docIndex, [ - new Token([T_DOC_COMMENT, sprintf('/**%s%s */', $this->whitespacesConfig->getLineEnding(), $indent)]), - new Token([T_WHITESPACE, sprintf('%s%s', $this->whitespacesConfig->getLineEnding(), $indent)]), - ]); - - if (!$tokens[$docIndex - 1]->isGivenKind(T_WHITESPACE)) { - $extraNewLines = $this->whitespacesConfig->getLineEnding(); - - if (!$tokens[$docIndex - 1]->isGivenKind(T_OPEN_TAG)) { - $extraNewLines .= $this->whitespacesConfig->getLineEnding(); - } - - $tokens->insertAt($docIndex, [ - new Token([T_WHITESPACE, $extraNewLines.$indent]), - ]); - ++$docIndex; - } - - $doc = new DocBlock($tokens[$docIndex]->getContent()); - } - - $lines = $doc->getLines(); - array_splice( - $lines, - \count($lines) - 1, - 0, + ], [ - new Line(sprintf( - '%s * @coversNothing%s', - $indent, - $this->whitespacesConfig->getLineEnding() - )), - ] + 'phpunit\framework\attributes\coversclass', + 'phpunit\framework\attributes\coversnothing', + ], ); - - $tokens[$docIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php index 16872747cf..ff32cd2fe0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -30,30 +31,26 @@ /** * @author Filippo Tessarotto * @author Julien Falque + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * comment_type?: 'all_multiline'|'phpdocs_like'|'phpdocs_only' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * comment_type: 'all_multiline'|'phpdocs_like'|'phpdocs_only' + * } */ final class AlignMultilineCommentFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * @var null|int[] - */ - private $tokenKinds; + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; /** - * {@inheritdoc} + * @var null|list */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->tokenKinds = [T_DOC_COMMENT]; - if ('phpdocs_only' !== $this->configuration['comment_type']) { - $this->tokenKinds[] = T_COMMENT; - } - } + private $tokenKinds; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -93,7 +90,7 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run before CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. * Must run after ArrayIndentationFixer. */ public function getPriority(): int @@ -101,17 +98,19 @@ public function getPriority(): int return 27; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound($this->tokenKinds); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->tokenKinds = [T_DOC_COMMENT]; + if ('phpdocs_only' !== $this->configuration['comment_type']) { + $this->tokenKinds[] = T_COMMENT; + } + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $lineEnding = $this->whitespacesConfig->getLineEnding(); @@ -132,11 +131,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $whitespace = Preg::replace('/\S/', '', $tokens[$previousIndex]->getContent()).$whitespace; } - if (1 !== Preg::match('/\R(\h*)$/', $whitespace, $matches)) { + if (!Preg::match('/\R(\h*)$/', $whitespace, $matches)) { continue; } - if ($token->isGivenKind(T_COMMENT) && 'all_multiline' !== $this->configuration['comment_type'] && 1 === Preg::match('/\R(?:\R|\s*[^\s\*])/', $token->getContent())) { + if ($token->isGivenKind(T_COMMENT) && 'all_multiline' !== $this->configuration['comment_type'] && Preg::match('/\R(?:\R|\s*[^\s\*])/', $token->getContent())) { continue; } @@ -167,9 +166,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php index 8920d5626d..18b66a3521 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php @@ -15,8 +15,10 @@ namespace PhpCsFixer\Fixer\Phpdoc; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,12 +31,23 @@ /** * @author Graham Campbell * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * annotations?: list, + * case_sensitive?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * annotations: list, + * case_sensitive: bool + * } */ final class GeneralPhpdocAnnotationRemoveFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,6 +58,7 @@ public function getDefinition(): FixerDefinitionInterface /** * @internal * @author John Doe + * @AuThOr Jane Doe */ function foo() {} ', @@ -52,6 +66,17 @@ function foo() {} ), new CodeSample( ' ['author'], 'case_sensitive' => false] + ), + new CodeSample( + 'isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (0 === \count($this->configuration['annotations'])) { @@ -100,7 +119,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $doc = new DocBlock($token->getContent()); - $annotations = $doc->getAnnotationsOfType($this->configuration['annotations']); + $annotations = $this->getAnnotationsToRemove($doc); // nothing to do if there are no annotations if (0 === \count($annotations)) { @@ -119,16 +138,40 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('annotations', 'List of annotations to remove, e.g. `["author"]`.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setDefault([]) ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Should annotations be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), ]); } + + /** + * @return list + */ + private function getAnnotationsToRemove(DocBlock $doc): array + { + if (true === $this->configuration['case_sensitive']) { + return $doc->getAnnotationsOfType($this->configuration['annotations']); + } + + $typesToSearchFor = array_map(static fn (string $type): string => strtolower($type), $this->configuration['annotations']); + + $annotations = []; + + foreach ($doc->getAnnotations() as $annotation) { + $tagName = strtolower($annotation->getTag()->getName()); + if (\in_array($tagName, $typesToSearchFor, true)) { + $annotations[] = $annotation; + } + } + + return $annotations; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php index b2948be3a8..4926a4b58c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,11 +29,27 @@ use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool, + * fix_annotation?: bool, + * fix_inline?: bool, + * replacements?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool, + * fix_annotation: bool, + * fix_inline: bool, + * replacements: array + * } + */ final class GeneralPhpdocTagRenameFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -77,17 +94,11 @@ public function getPriority(): int return 11; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -100,8 +111,8 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setDefault(true) ->getOption(), (new FixerOptionBuilder('replacements', 'A map of tags to replace.')) - ->setAllowedTypes(['array']) - ->setNormalizer(static function (Options $options, $value): array { + ->setAllowedTypes(['array']) + ->setNormalizer(static function (Options $options, array $value): array { $normalizedValue = []; foreach ($value as $from => $to) { @@ -109,15 +120,8 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn throw new InvalidOptionsException('Tag to replace must be a string.'); } - if (!\is_string($to)) { - throw new InvalidOptionsException(sprintf( - 'Tag to replace to from "%s" must be a string.', - $from - )); - } - - if (1 !== Preg::match('#^\S+$#', $to) || str_contains($to, '*/')) { - throw new InvalidOptionsException(sprintf( + if (!Preg::match('#^\S+$#', $to) || str_contains($to, '*/')) { + throw new InvalidOptionsException(\sprintf( 'Tag "%s" cannot be replaced by invalid tag "%s".', $from, $to @@ -127,11 +131,11 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn $from = trim($from); $to = trim($to); - if (!$options['case_sensitive']) { + if (false === $options['case_sensitive']) { $lowercaseFrom = strtolower($from); if (isset($normalizedValue[$lowercaseFrom]) && $normalizedValue[$lowercaseFrom] !== $to) { - throw new InvalidOptionsException(sprintf( + throw new InvalidOptionsException(\sprintf( 'Tag "%s" cannot be configured to be replaced with several different tags when case sensitivity is off.', $from )); @@ -145,7 +149,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn foreach ($normalizedValue as $from => $to) { if (isset($normalizedValue[$to]) && $normalizedValue[$to] !== $to) { - throw new InvalidOptionsException(sprintf( + throw new InvalidOptionsException(\sprintf( 'Cannot change tag "%1$s" to tag "%2$s", as the tag "%2$s" is configured to be replaced to "%3$s".', $from, $to, @@ -165,9 +169,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (0 === \count($this->configuration['replacements'])) { @@ -175,18 +176,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } if (true === $this->configuration['fix_annotation']) { - if ($this->configuration['fix_inline']) { - $regex = '/"[^"]*"(*SKIP)(*FAIL)|\b(?<=@)(%s)\b/'; - } else { - $regex = '/"[^"]*"(*SKIP)(*FAIL)|(?configuration['fix_inline'] + ? '/"[^"]*"(*SKIP)(*FAIL)|\b(?<=@)(%s)\b/' + : '/"[^"]*"(*SKIP)(*FAIL)|(?configuration['case_sensitive']; $replacements = $this->configuration['replacements']; - $regex = sprintf($regex, implode('|', array_keys($replacements))); + $regex = \sprintf($regex, implode('|', array_keys($replacements))); if ($caseInsensitive) { $regex .= 'i'; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php index ec3dadda84..33fb2d0e67 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php @@ -26,17 +26,11 @@ */ final class NoBlankLinesAfterPhpdocFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -68,9 +62,6 @@ public function getPriority(): int return -20; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { static $forbiddenSuccessors = [ @@ -80,7 +71,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void T_DECLARE, T_DOC_COMMENT, T_GOTO, + T_INCLUDE, + T_INCLUDE_ONCE, T_NAMESPACE, + T_REQUIRE, + T_REQUIRE_ONCE, T_RETURN, T_THROW, T_USE, diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php index 843bf01daa..27d7af6457 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoEmptyPhpdocFixer.php @@ -19,13 +19,11 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; final class NoEmptyPhpdocFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,25 +35,19 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, PhpdocAlignFixer. - * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, NoSuperfluousPhpdocTagsFixer, PhpUnitNoExpectationAnnotationFixer, PhpUnitTestAnnotationFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer, PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, NoSuperfluousPhpdocTagsFixer, PhpUnitNoExpectationAnnotationFixer, PhpUnitTestAnnotationFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int { return 3; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -63,9 +55,30 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - if (Preg::match('#^/\*\*[\s\*]*\*/$#', $token->getContent())) { - $tokens->clearTokenAndMergeSurroundingWhitespace($index); + if (!Preg::match('#^/\*\*[\s\*]*\*/$#', $token->getContent())) { + continue; } + + if ( + $tokens[$index - 1]->isGivenKind([T_OPEN_TAG, T_WHITESPACE]) + && substr_count($tokens[$index - 1]->getContent(), "\n") > 0 + && $tokens[$index + 1]->isGivenKind(T_WHITESPACE) + && Preg::match('/^\R/', $tokens[$index + 1]->getContent()) + ) { + $tokens[$index - 1] = new Token([ + $tokens[$index - 1]->getId(), + Preg::replace('/\h*$/', '', $tokens[$index - 1]->getContent()), + ]); + + $newContent = Preg::replace('/^\R/', '', $tokens[$index + 1]->getContent()); + if ('' === $newContent) { + $tokens->clearAt($index + 1); + } else { + $tokens[$index + 1] = new Token([T_WHITESPACE, $newContent]); + } + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php index 618a2ba9f1..ab32d7eef6 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php @@ -17,7 +17,9 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -25,22 +27,59 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; - +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @phpstan-type _TypeInfo array{ + * types: list, + * allows_null: bool, + * } + * @phpstan-type _DocumentElement array{ + * index: int, + * type: 'classy'|'function'|'property', + * modifiers: array, + * types: array, + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * allow_hidden_params?: bool, + * allow_mixed?: bool, + * allow_unused_params?: bool, + * remove_inheritdoc?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * allow_hidden_params: bool, + * allow_mixed: bool, + * allow_unused_params: bool, + * remove_inheritdoc: bool + * } + */ final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** @var _TypeInfo */ + private const NO_TYPE_INFO = [ + 'types' => [], + 'allows_null' => true, + ]; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Removes `@param`, `@return` and `@var` tags that don\'t provide any useful information.', [ - new CodeSample(' true]), - new CodeSample(' true], + ), + new CodeSample( + ' true]), - new CodeSample(' true], + ), + new CodeSample( + ' true]), +', + ['allow_hidden_params' => true], + ), + new CodeSample( + ' true], + ), ] ); } @@ -93,46 +157,76 @@ public function getPriority(): int return 6; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $namespaceUseAnalyzer = new NamespaceUsesAnalyzer(); $shortNames = []; + $currentSymbol = null; + $currentSymbolEndIndex = null; foreach ($namespaceUseAnalyzer->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) { - $shortNames[strtolower($namespaceUseAnalysis->getShortName())] = '\\'.strtolower($namespaceUseAnalysis->getFullName()); + $shortNames[strtolower($namespaceUseAnalysis->getShortName())] = strtolower($namespaceUseAnalysis->getFullName()); + } + + $symbolKinds = [T_CLASS, T_INTERFACE]; + if (\defined('T_ENUM')) { // @TODO drop the condition when requiring PHP 8.1+ + $symbolKinds[] = T_ENUM; } foreach ($tokens as $index => $token) { + if ($index === $currentSymbolEndIndex) { + $currentSymbol = null; + $currentSymbolEndIndex = null; + + continue; + } + + if ($token->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) { + continue; + } + + if ($token->isGivenKind($symbolKinds)) { + $currentSymbol = $tokens[$tokens->getNextMeaningfulToken($index)]->getContent(); + $currentSymbolEndIndex = $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_CURLY_BRACE, + $tokens->getNextTokenOfKind($index, ['{']), + ); + + continue; + } + if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } - $content = $initialContent = $token->getContent(); $documentedElement = $this->findDocumentedElement($tokens, $index); if (null === $documentedElement) { continue; } + $content = $initialContent = $token->getContent(); + if (true === $this->configuration['remove_inheritdoc']) { $content = $this->removeSuperfluousInheritDoc($content); } + $namespace = (new NamespacesAnalyzer())->getNamespaceAt($tokens, $index)->getFullName(); + if ('' === $namespace) { + $namespace = null; + } + if ('function' === $documentedElement['type']) { - $content = $this->fixFunctionDocComment($content, $tokens, $documentedElement, $shortNames); + $content = $this->fixFunctionDocComment($content, $tokens, $documentedElement, $namespace, $currentSymbol, $shortNames); } elseif ('property' === $documentedElement['type']) { - $content = $this->fixPropertyDocComment($content, $tokens, $documentedElement, $shortNames); + $content = $this->fixPropertyDocComment($content, $tokens, $documentedElement, $namespace, $currentSymbol, $shortNames); } elseif ('classy' === $documentedElement['type']) { $content = $this->fixClassDocComment($content, $documentedElement); } else { @@ -149,27 +243,31 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('allow_mixed', 'Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`)')) + (new FixerOptionBuilder('allow_mixed', 'Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`).')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), - (new FixerOptionBuilder('remove_inheritdoc', 'Remove `@inheritDoc` tags')) + (new FixerOptionBuilder('remove_inheritdoc', 'Remove `@inheritDoc` tags.')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), - (new FixerOptionBuilder('allow_unused_params', 'Whether `param` annotation without actual signature is allowed (`true`) or considered superfluous (`false`)')) + (new FixerOptionBuilder('allow_hidden_params', 'Whether `param` annotation for hidden params in method signature are allowed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO set to `true` on 4.0 + ->getOption(), + (new FixerOptionBuilder('allow_unused_params', 'Whether `param` annotation without actual signature is allowed (`true`) or considered superfluous (`false`).')) ->setAllowedTypes(['bool']) ->setDefault(false) ->getOption(), ]); } + /** + * @return null|_DocumentElement + */ private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?array { $modifierKinds = [ @@ -201,6 +299,14 @@ private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?a $index = $tokens->getNextMeaningfulToken($docCommentIndex); + // @TODO: drop condition when PHP 8.0+ is required + if (null !== $index && \defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) { + do { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + $index = $tokens->getNextMeaningfulToken($index); + } while (null !== $index && $tokens[$index]->isGivenKind(T_ATTRIBUTE)); + } + while (true) { if (null === $index) { break; @@ -213,7 +319,7 @@ private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?a return $element; } - if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + if ($tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { $element['index'] = $index; $element['type'] = 'function'; @@ -241,8 +347,19 @@ private function findDocumentedElement(Tokens $tokens, int $docCommentIndex): ?a return null; } - private function fixFunctionDocComment(string $content, Tokens $tokens, array $element, array $shortNames): string - { + /** + * @param _DocumentElement&array{type: 'function'} $element + * @param null|non-empty-string $namespace + * @param array $shortNames + */ + private function fixFunctionDocComment( + string $content, + Tokens $tokens, + array $element, + ?string $namespace, + ?string $currentSymbol, + array $shortNames + ): string { $docBlock = new DocBlock($content); $openingParenthesisIndex = $tokens->getNextTokenOfKind($element['index'], ['(']); @@ -258,6 +375,10 @@ private function fixFunctionDocComment(string $content, Tokens $tokens, array $e $argumentName = $annotation->getVariableName(); if (null === $argumentName) { + if ($this->annotationIsSuperfluous($annotation, self::NO_TYPE_INFO, $namespace, $currentSymbol, $shortNames)) { + $annotation->remove(); + } + continue; } @@ -265,7 +386,7 @@ private function fixFunctionDocComment(string $content, Tokens $tokens, array $e continue; } - if (!isset($argumentsInfo[$argumentName]) || $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $shortNames)) { + if (!isset($argumentsInfo[$argumentName]) || $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $namespace, $currentSymbol, $shortNames)) { $annotation->remove(); } } @@ -273,7 +394,7 @@ private function fixFunctionDocComment(string $content, Tokens $tokens, array $e $returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex); foreach ($docBlock->getAnnotationsOfType('return') as $annotation) { - if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $shortNames)) { + if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $namespace, $currentSymbol, $shortNames)) { $annotation->remove(); } } @@ -283,21 +404,29 @@ private function fixFunctionDocComment(string $content, Tokens $tokens, array $e return $docBlock->getContent(); } - private function fixPropertyDocComment(string $content, Tokens $tokens, array $element, array $shortNames): string - { + /** + * @param _DocumentElement&array{type: 'property'} $element + * @param null|non-empty-string $namespace + * @param array $shortNames + */ + private function fixPropertyDocComment( + string $content, + Tokens $tokens, + array $element, + ?string $namespace, + ?string $currentSymbol, + array $shortNames + ): string { if (\count($element['types']) > 0) { $propertyTypeInfo = $this->parseTypeHint($tokens, array_key_first($element['types'])); } else { - $propertyTypeInfo = [ - 'types' => [], - 'allows_null' => true, - ]; + $propertyTypeInfo = self::NO_TYPE_INFO; } $docBlock = new DocBlock($content); foreach ($docBlock->getAnnotationsOfType('var') as $annotation) { - if ($this->annotationIsSuperfluous($annotation, $propertyTypeInfo, $shortNames)) { + if ($this->annotationIsSuperfluous($annotation, $propertyTypeInfo, $namespace, $currentSymbol, $shortNames)) { $annotation->remove(); } } @@ -305,6 +434,9 @@ private function fixPropertyDocComment(string $content, Tokens $tokens, array $e return $docBlock->getContent(); } + /** + * @param _DocumentElement&array{type: 'classy'} $element + */ private function fixClassDocComment(string $content, array $element): string { $docBlock = new DocBlock($content); @@ -315,7 +447,7 @@ private function fixClassDocComment(string $content, array $element): string } /** - * @return array + * @return array */ private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array { @@ -334,10 +466,7 @@ private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array if ($typeIndex !== $index) { $info = $this->parseTypeHint($tokens, $typeIndex); } else { - $info = [ - 'types' => [], - 'allows_null' => true, - ]; + $info = self::NO_TYPE_INFO; } if (!$info['allows_null']) { @@ -353,39 +482,61 @@ private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array $argumentsInfo[$token->getContent()] = $info; } + // virtualise "hidden params" as if they would be regular ones + if (true === $this->configuration['allow_hidden_params']) { + $paramsString = $tokens->generatePartialCode($start, $end); + Preg::matchAll('|/\*[^$]*(\$\w+)[^*]*\*/|', $paramsString, $matches); + + /** @var non-empty-string $match */ + foreach ($matches[1] as $match) { + $argumentsInfo[$match] = self::NO_TYPE_INFO; // HINT: one could try to extract actual type for hidden param, for now we only indicate it's existence + } + } + return $argumentsInfo; } + /** + * @return _TypeInfo + */ private function getReturnTypeInfo(Tokens $tokens, int $closingParenthesisIndex): array { $colonIndex = $tokens->getNextMeaningfulToken($closingParenthesisIndex); - if ($tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)) { - return $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex)); - } - return [ - 'types' => [], - 'allows_null' => true, - ]; + return $tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON) + ? $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex)) + : self::NO_TYPE_INFO; } /** * @param int $index The index of the first token of the type hint + * + * @return _TypeInfo */ private function parseTypeHint(Tokens $tokens, int $index): array { $allowsNull = false; - if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { - $allowsNull = true; - $index = $tokens->getNextMeaningfulToken($index); - } - $types = []; while (true) { $type = ''; + if (\defined('T_READONLY') && $tokens[$index]->isGivenKind(T_READONLY)) { // @TODO: simplify condition when PHP 8.1+ is required + $index = $tokens->getNextMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE])) { + $index = $tokens->getNextMeaningfulToken($index); + + continue; + } + + if ($tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + $allowsNull = true; + $index = $tokens->getNextMeaningfulToken($index); + } + while ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STATIC, T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE])) { $type .= $tokens[$index]->getContent(); $index = $tokens->getNextMeaningfulToken($index); @@ -411,25 +562,43 @@ private function parseTypeHint(Tokens $tokens, int $index): array } /** + * @param _TypeInfo $info + * @param null|non-empty-string $namespace * @param array $symbolShortNames */ - private function annotationIsSuperfluous(Annotation $annotation, array $info, array $symbolShortNames): bool - { + private function annotationIsSuperfluous( + Annotation $annotation, + array $info, + ?string $namespace, + ?string $currentSymbol, + array $symbolShortNames + ): bool { if ('param' === $annotation->getTag()->getName()) { - $regex = '/@param\s+[^\$]+\s(?:\&\s*)?(?:\.{3}\s*)?\$\S+\s+\S/'; + $regex = '{\*\h*@param(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+(?:\&\h*)?(?:\.{3}\h*)?\$\S+)?(?:\s+(?(?!\*+\/)\S+))?}s'; } elseif ('var' === $annotation->getTag()->getName()) { - $regex = '/@var\s+\S+(\s+\$\S+)?(\s+)(?!\*+\/)([^$\s]+)/'; + $regex = '{\*\h*@var(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\h+\$\S+)?(?:\s+(?(?!\*\/)\S+))?}s'; } else { - $regex = '/@return\s+\S+\s+\S/'; + $regex = '{\*\h*@return(?:\h+'.TypeExpression::REGEX_TYPES.')?(?!\S)(?:\s+(?(?!\*\/)\S+))?}s'; + } + + if (!Preg::match($regex, $annotation->getContent(), $matches)) { + // Unable to match the annotation, it must be malformed or has unsupported format. + // Either way we don't want to tinker with it. + return false; } - if (Preg::match($regex, $annotation->getContent())) { + if (isset($matches['description'])) { return false; } - $annotationTypes = $this->toComparableNames($annotation->getTypes(), $symbolShortNames); + if (!isset($matches['types']) || '' === $matches['types']) { + // If there's no type info in the annotation, further checks make no sense, exit early. + return true; + } + + $annotationTypes = $this->toComparableNames($annotation->getTypes(), $namespace, $currentSymbol, $symbolShortNames); - if (['null'] === $annotationTypes) { + if (['null'] === $annotationTypes && ['null'] !== $info['types']) { return false; } @@ -443,7 +612,18 @@ private function annotationIsSuperfluous(Annotation $annotation, array $info, ar $actualTypes[] = 'null'; } - return $annotationTypes === $this->toComparableNames($actualTypes, $symbolShortNames); + $actualTypes = $this->toComparableNames($actualTypes, $namespace, $currentSymbol, $symbolShortNames); + + if ($annotationTypes === $actualTypes) { + return true; + } + + // retry comparison with annotation type unioned with null + // phpstan implies the null presence from the native type + $annotationTypes = array_merge($annotationTypes, ['null']); + sort($annotationTypes); + + return $actualTypes === $annotationTypes; } /** @@ -452,25 +632,50 @@ private function annotationIsSuperfluous(Annotation $annotation, array $info, ar * Converts given types to lowercase, replaces imports aliases with * their matching FQCN, and finally sorts the result. * - * @param string[] $types The types to normalize + * @param list $types The types to normalize + * @param null|non-empty-string $namespace * @param array $symbolShortNames The imports aliases * - * @return array The normalized types + * @return list The normalized types */ - private function toComparableNames(array $types, array $symbolShortNames): array + private function toComparableNames(array $types, ?string $namespace, ?string $currentSymbol, array $symbolShortNames): array { - $normalized = array_map( - static function (string $type) use ($symbolShortNames): string { - $type = strtolower($type); + if (isset($types[0][0]) && '?' === $types[0][0]) { + $types = [ + substr($types[0], 1), + 'null', + ]; + } + $normalized = array_map( + function (string $type) use ($namespace, $currentSymbol, $symbolShortNames): string { if (str_contains($type, '&')) { $intersects = explode('&', $type); - sort($intersects); + + $intersects = $this->toComparableNames($intersects, $namespace, $currentSymbol, $symbolShortNames); return implode('&', $intersects); } - return $symbolShortNames[$type] ?? $type; + if ('self' === $type && null !== $currentSymbol) { + $type = $currentSymbol; + } + + $type = strtolower($type); + + if (isset($symbolShortNames[$type])) { + return $symbolShortNames[$type]; // always FQCN /wo leading backslash and in lower-case + } + + if (str_starts_with($type, '\\')) { + return substr($type, 1); + } + + if (null !== $namespace && !(new TypeAnalysis($type))->isReservedType()) { + $type = strtolower($namespace).'\\'.$type; + } + + return $type; }, $types ); @@ -531,6 +736,9 @@ private function removeSuperfluousInheritDoc(string $docComment): string ~ix', '$1$2', $docComment); } + /** + * @param _DocumentElement $element + */ private function removeSuperfluousModifierAnnotation(DocBlock $docBlock, array $element): void { foreach (['abstract' => T_ABSTRACT, 'final' => T_FINAL] as $annotationType => $modifierToken) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php index 38e627816f..20bf2118cd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -27,17 +28,27 @@ use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * only_untyped?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * only_untyped: bool + * } */ final class PhpdocAddMissingParamAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -90,17 +101,11 @@ public function getPriority(): int return 10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); @@ -137,7 +142,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void T_PROTECTED, T_PUBLIC, T_STATIC, - T_VAR, ])) { $index = $tokens->getNextMeaningfulToken($index); } @@ -169,7 +173,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void foreach ($doc->getAnnotationsOfType('param') as $annotation) { $pregMatched = Preg::match('/^[^$]+(\$\w+).*$/s', $annotation->getContent(), $matches); - if (1 === $pregMatched) { + if ($pregMatched) { unset($arguments[$matches[1]]); } @@ -189,13 +193,13 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $newLines = []; foreach ($arguments as $argument) { - $type = $argument['type'] ?: 'mixed'; + $type = '' !== $argument['type'] ? $argument['type'] : 'mixed'; if (!str_starts_with($type, '?') && 'null' === strtolower($argument['default'])) { $type = 'null|'.$type; } - $newLines[] = new Line(sprintf( + $newLines[] = new Line(\sprintf( '%s* @param %s %s%s', $indent, $type, @@ -206,7 +210,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void array_splice( $lines, - $lastParamLine ? $lastParamLine + 1 : $linesCount - 1, + $lastParamLine > 0 ? $lastParamLine + 1 : $linesCount - 1, 0, $newLines ); @@ -215,9 +219,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -228,6 +229,9 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } + /** + * @return array{default: string, name: string, type: string} + */ private function prepareArgumentInformation(Tokens $tokens, int $start, int $end): array { $info = [ @@ -241,7 +245,16 @@ private function prepareArgumentInformation(Tokens $tokens, int $start, int $end for ($index = $start; $index <= $end; ++$index) { $token = $tokens[$index]; - if ($token->isComment() || $token->isWhitespace()) { + if ( + $token->isComment() + || $token->isWhitespace() + || $token->isGivenKind([ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]) + || (\defined('T_READONLY') && $token->isGivenKind(T_READONLY)) + ) { continue; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php index f72635d348..a511f8d502 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAlignFixer.php @@ -18,8 +18,8 @@ use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; -use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,6 +29,7 @@ use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Fabien Potencier @@ -36,9 +37,26 @@ * @author Sebastiaan Stok * @author Graham Campbell * @author Dariusz Rumiński + * @author Jakub Kwaśniewski + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * align?: 'left'|'vertical', + * spacing?: array|int, + * tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * align: 'left'|'vertical', + * spacing: array|int, + * tags: list + * } */ final class PhpdocAlignFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -49,16 +67,14 @@ final class PhpdocAlignFixer extends AbstractFixer implements ConfigurableFixerI */ public const ALIGN_VERTICAL = 'vertical'; - private const ALIGNABLE_TAGS = [ + private const DEFAULT_TAGS = [ + 'method', 'param', 'property', - 'property-read', - 'property-write', 'return', 'throws', 'type', 'var', - 'method', ]; private const TAGS_WITH_NAME = [ @@ -66,95 +82,82 @@ final class PhpdocAlignFixer extends AbstractFixer implements ConfigurableFixerI 'property', 'property-read', 'property-write', + 'phpstan-param', + 'phpstan-property', + 'phpstan-property-read', + 'phpstan-property-write', + 'phpstan-assert', + 'phpstan-assert-if-true', + 'phpstan-assert-if-false', + 'psalm-param', + 'psalm-param-out', + 'psalm-property', + 'psalm-property-read', + 'psalm-property-write', + 'psalm-assert', + 'psalm-assert-if-true', + 'psalm-assert-if-false', ]; private const TAGS_WITH_METHOD_SIGNATURE = [ 'method', + 'phpstan-method', + 'psalm-method', ]; - /** - * @var string - */ - private $regex; + private const DEFAULT_SPACING = 1; - /** - * @var string - */ - private $regexCommentLine; + private const DEFAULT_SPACING_KEY = '_default'; - /** - * @var string - */ - private $align; + private string $regex; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $tagsWithNameToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_NAME); - $tagsWithMethodSignatureToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_METHOD_SIGNATURE); - $tagsWithoutNameToAlign = array_diff($this->configuration['tags'], $tagsWithNameToAlign, $tagsWithMethodSignatureToAlign); - $types = []; + private string $regexCommentLine; - $indent = '(?P(?:\ {2}|\t)*)'; - - // e.g. @param <$var> - if ([] !== $tagsWithNameToAlign) { - $types[] = '(?P'.implode('|', $tagsWithNameToAlign).')\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?)\s+(?P(?:&|\.{3})?\$\S+)'; - } - - // e.g. @return - if ([] !== $tagsWithoutNameToAlign) { - $types[] = '(?P'.implode('|', $tagsWithoutNameToAlign).')\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?)'; - } - - // e.g. @method - if ([] !== $tagsWithMethodSignatureToAlign) { - $types[] = '(?P'.implode('|', $tagsWithMethodSignatureToAlign).')(\s+(?P[^\s(]+)|)\s+(?P.+\))'; - } - - // optional - $desc = '(?:\s+(?P\V*))'; - - $this->regex = '/^'.$indent.'\ \*\ @(?J)(?:'.implode('|', $types).')'.$desc.'\s*$/ux'; - $this->regexCommentLine = '/^'.$indent.' \*(?! @)(?:\s+(?P\V+))(?align = $this->configuration['align']; - } + private string $align; /** - * {@inheritdoc} + * same spacing for all or specific for different tags. + * + * @var array|int */ + private $spacing = 1; + public function getDefinition(): FixerDefinitionInterface { $code = <<<'EOF' - self::ALIGN_VERTICAL]), new CodeSample($code, ['align' => self::ALIGN_LEFT]), - ] + new CodeSample($code, ['align' => self::ALIGN_LEFT, 'spacing' => 2]), + new CodeSample($code, ['align' => self::ALIGN_LEFT, 'spacing' => ['param' => 2]]), + ], ); } /** * {@inheritdoc} * - * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocScalarFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToCommentFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. */ public function getPriority(): int { @@ -168,17 +171,45 @@ public function getPriority(): int return -42; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $tagsWithNameToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_NAME); + $tagsWithMethodSignatureToAlign = array_intersect($this->configuration['tags'], self::TAGS_WITH_METHOD_SIGNATURE); + $tagsWithoutNameToAlign = array_diff($this->configuration['tags'], $tagsWithNameToAlign, $tagsWithMethodSignatureToAlign); + + $indentRegex = '^(?P(?:\ {2}|\t)*)\ ?'; + + $types = []; + + // e.g. @param <$var> + if ([] !== $tagsWithNameToAlign) { + $types[] = '(?P'.implode('|', $tagsWithNameToAlign).')\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?)\s*(?P(?:&|\.{3})?\$\S+)'; + } + + // e.g. @return + if ([] !== $tagsWithoutNameToAlign) { + $types[] = '(?P'.implode('|', $tagsWithoutNameToAlign).')\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?)'; + } + + // e.g. @method + if ([] !== $tagsWithMethodSignatureToAlign) { + $types[] = '(?P'.implode('|', $tagsWithMethodSignatureToAlign).')(\s+(?Pstatic))?(\s+(?P(?:'.TypeExpression::REGEX_TYPES.')?))\s+(?P.+\))'; + } + + // optional + $desc = '(?:\s+(?P\V*))'; + + $this->regex = '/'.$indentRegex.'\*\h*@(?J)(?:'.implode('|', $types).')'.$desc.'\h*\r?$/'; + $this->regexCommentLine = '/'.$indentRegex.'\*(?!\h?+@)(?:\s+(?P\V+))(?align = $this->configuration['align']; + $this->spacing = $this->configuration['spacing']; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -196,34 +227,45 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - $tags = new FixerOptionBuilder('tags', 'The tags that should be aligned.'); + $allowPositiveIntegers = static function ($value) { + $spacings = \is_array($value) ? $value : [$value]; + foreach ($spacings as $val) { + if (\is_int($val) && $val <= 0) { + throw new InvalidOptionsException('The option "spacing" is invalid. All spacings must be greater than zero.'); + } + } + + return true; + }; + + $tags = new FixerOptionBuilder( + 'tags', + 'The tags that should be aligned. Allowed values are tags with name (`\''.implode('\', \'', self::TAGS_WITH_NAME).'\'`), tags with method signature (`\''.implode('\', \'', self::TAGS_WITH_METHOD_SIGNATURE).'\'`) and any custom tag with description (e.g. `@tag `).' + ); $tags - ->setAllowedTypes(['array']) - ->setAllowedValues([new AllowedValueSubset(self::ALIGNABLE_TAGS)]) - ->setDefault([ - 'method', - 'param', - 'property', - 'return', - 'throws', - 'type', - 'var', - ]) + ->setAllowedTypes(['string[]']) + ->setDefault(self::DEFAULT_TAGS) ; - $align = new FixerOptionBuilder('align', 'Align comments'); + $align = new FixerOptionBuilder('align', 'How comments should be aligned.'); $align ->setAllowedTypes(['string']) ->setAllowedValues([self::ALIGN_LEFT, self::ALIGN_VERTICAL]) ->setDefault(self::ALIGN_VERTICAL) ; - return new FixerConfigurationResolver([$tags->getOption(), $align->getOption()]); + $spacing = new FixerOptionBuilder( + 'spacing', + 'Spacing between tag, hint, comment, signature, etc. You can set same spacing for all tags using a positive integer or different spacings for different tags using an associative array of positive integers `[\'tagA\' => spacingForA, \'tagB\' => spacingForB]`. If you want to define default spacing to more than 1 space use `_default` key in config array, e.g.: `[\'tagA\' => spacingForA, \'tagB\' => spacingForB, \'_default\' => spacingForAllOthers]`.' + ); + $spacing->setAllowedTypes(['int', 'array']) + ->setAllowedValues([$allowPositiveIntegers]) + ->setDefault(self::DEFAULT_SPACING) + ; + + return new FixerConfigurationResolver([$tags->getOption(), $align->getOption(), $spacing->getOption()]); } private function fixDocBlock(DocBlock $docBlock): void @@ -231,7 +273,6 @@ private function fixDocBlock(DocBlock $docBlock): void $lineEnding = $this->whitespacesConfig->getLineEnding(); for ($i = 0, $l = \count($docBlock->getLines()); $i < $l; ++$i) { - $items = []; $matches = $this->getMatches($docBlock->getLine($i)->getContent()); if (null === $matches) { @@ -239,7 +280,7 @@ private function fixDocBlock(DocBlock $docBlock): void } $current = $i; - $items[] = $matches; + $items = [$matches]; while (true) { if (null === $docBlock->getLine(++$i)) { @@ -255,6 +296,7 @@ private function fixDocBlock(DocBlock $docBlock): void } // compute the max length of the tag, hint and variables + $hasStatic = false; $tagMax = 0; $hintMax = 0; $varMax = 0; @@ -264,99 +306,142 @@ private function fixDocBlock(DocBlock $docBlock): void continue; } + $hasStatic |= '' !== $item['static']; $tagMax = max($tagMax, \strlen($item['tag'])); $hintMax = max($hintMax, \strlen($item['hint'])); $varMax = max($varMax, \strlen($item['var'])); } + $itemOpeningLine = null; + $currTag = null; + $spacingForTag = $this->spacingForTag($currTag); // update foreach ($items as $j => $item) { if (null === $item['tag']) { if ('@' === $item['desc'][0]) { - $docBlock->getLine($current + $j)->setContent($item['indent'].' * '.$item['desc'].$lineEnding); + $line = $item['indent'].' * '.$item['desc']; + $docBlock->getLine($current + $j)->setContent($line.$lineEnding); continue; } - $extraIndent = 2; + $extraIndent = 2 * $spacingForTag; - if (\in_array($currTag, self::TAGS_WITH_NAME, true) || \in_array($currTag, self::TAGS_WITH_METHOD_SIGNATURE, true)) { - $extraIndent = 3; + if (\in_array($itemOpeningLine['tag'], self::TAGS_WITH_NAME, true) || \in_array($itemOpeningLine['tag'], self::TAGS_WITH_METHOD_SIGNATURE, true)) { + $extraIndent += $varMax + $spacingForTag; + } + + if ($hasStatic) { + $extraIndent += 7; // \strlen('static '); } $line = $item['indent'] - .' * ' + .' * ' + .('' !== $itemOpeningLine['hint'] ? ' ' : '') .$this->getIndent( - $tagMax + $hintMax + $varMax + $extraIndent, + $tagMax + $hintMax + $extraIndent, $this->getLeftAlignedDescriptionIndent($items, $j) ) - .$item['desc'] - .$lineEnding; + .$item['desc']; - $docBlock->getLine($current + $j)->setContent($line); + $docBlock->getLine($current + $j)->setContent($line.$lineEnding); continue; } $currTag = $item['tag']; + $spacingForTag = $this->spacingForTag($currTag); + + $itemOpeningLine = $item; + $line = $item['indent'] .' * @' - .$item['tag'] - .$this->getIndent( - $tagMax - \strlen($item['tag']) + 1, - $item['hint'] ? 1 : 0 + .$item['tag']; + + if ($hasStatic) { + $line .= + $this->getIndent( + $tagMax - \strlen($item['tag']) + $spacingForTag, + '' !== $item['static'] ? $spacingForTag : 0 + ) + .('' !== $item['static'] ? $item['static'] : $this->getIndent(6 /* \strlen('static') */, 0)); + $hintVerticalAlignIndent = $spacingForTag; + } else { + $hintVerticalAlignIndent = $tagMax - \strlen($item['tag']) + $spacingForTag; + } + + $line .= + $this->getIndent( + $hintVerticalAlignIndent, + '' !== $item['hint'] ? $spacingForTag : 0 ) - .$item['hint'] - ; + .$item['hint']; - if (!empty($item['var'])) { + if ('' !== $item['var']) { $line .= - $this->getIndent(($hintMax ?: -1) - \strlen($item['hint']) + 1) + $this->getIndent((0 !== $hintMax ? $hintMax : -1) - \strlen($item['hint']) + $spacingForTag, $spacingForTag) .$item['var'] .( - !empty($item['desc']) - ? $this->getIndent($varMax - \strlen($item['var']) + 1).$item['desc'].$lineEnding - : $lineEnding - ) - ; - } elseif (!empty($item['desc'])) { - $line .= $this->getIndent($hintMax - \strlen($item['hint']) + 1).$item['desc'].$lineEnding; - } else { - $line .= $lineEnding; + '' !== $item['desc'] + ? $this->getIndent($varMax - \strlen($item['var']) + $spacingForTag, $spacingForTag).$item['desc'] + : '' + ); + } elseif ('' !== $item['desc']) { + $line .= $this->getIndent($hintMax - \strlen($item['hint']) + $spacingForTag, $spacingForTag).$item['desc']; } - $docBlock->getLine($current + $j)->setContent($line); + $docBlock->getLine($current + $j)->setContent($line.$lineEnding); } } } + private function spacingForTag(?string $tag): int + { + return (\is_int($this->spacing)) + ? $this->spacing + : ($this->spacing[$tag] ?? $this->spacing[self::DEFAULT_SPACING_KEY] ?? self::DEFAULT_SPACING); + } + /** - * @return null|array + * @TODO Introduce proper DTO instead of an array + * + * @return null|array{indent: null|string, tag: null|string, hint: string, var: null|string, static: string, desc?: null|string} */ private function getMatches(string $line, bool $matchCommentOnly = false): ?array { if (Preg::match($this->regex, $line, $matches)) { - if (!empty($matches['tag2'])) { + if (isset($matches['tag2']) && '' !== $matches['tag2']) { $matches['tag'] = $matches['tag2']; $matches['hint'] = $matches['hint2']; $matches['var'] = ''; } - if (!empty($matches['tag3'])) { + if (isset($matches['tag3']) && '' !== $matches['tag3']) { $matches['tag'] = $matches['tag3']; $matches['hint'] = $matches['hint3']; $matches['var'] = $matches['signature']; + + // Since static can be both a return type declaration & a keyword that defines static methods + // we assume it's a type declaration when only one value is present + if ('' === $matches['hint'] && '' !== $matches['static']) { + $matches['hint'] = $matches['static']; + $matches['static'] = ''; + } } if (isset($matches['hint'])) { $matches['hint'] = trim($matches['hint']); } + if (!isset($matches['static'])) { + $matches['static'] = ''; + } + return $matches; } @@ -364,6 +449,7 @@ private function getMatches(string $line, bool $matchCommentOnly = false): ?arra $matches['tag'] = null; $matches['var'] = ''; $matches['hint'] = ''; + $matches['static'] = ''; return $matches; } @@ -379,7 +465,7 @@ private function getIndent(int $verticalAlignIndent, int $leftAlignIndent = 1): } /** - * @param array[] $items + * @param non-empty-list $items */ private function getLeftAlignedDescriptionIndent(array $items, int $index): int { @@ -401,17 +487,20 @@ private function getLeftAlignedDescriptionIndent(array $items, int $index): int return 0; } + $spacingForTag = $this->spacingForTag($item['tag']); + // Indent according to existing values: return - $this->getSentenceIndent($item['tag']) + - $this->getSentenceIndent($item['hint']) + - $this->getSentenceIndent($item['var']); + $this->getSentenceIndent($item['static'], $spacingForTag) + + $this->getSentenceIndent($item['tag'], $spacingForTag) + + $this->getSentenceIndent($item['hint'], $spacingForTag) + + $this->getSentenceIndent($item['var'], $spacingForTag); } /** * Get indent for sentence. */ - private function getSentenceIndent(?string $sentence): int + private function getSentenceIndent(?string $sentence, int $spacingForTag = 1): int { if (null === $sentence) { return 0; @@ -419,6 +508,6 @@ private function getSentenceIndent(?string $sentence): int $length = \strlen($sentence); - return 0 === $length ? 0 : $length + 1; + return 0 === $length ? 0 : $length + $spacingForTag; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php index 02451992fe..756086b577 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php @@ -29,13 +29,10 @@ final class PhpdocAnnotationWithoutDotFixer extends AbstractFixer { /** - * @var string[] + * @var list */ private array $tags = ['throws', 'return', 'param', 'internal', 'deprecated', 'var', 'type']; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -52,7 +49,7 @@ function foo ($bar) {} /** * {@inheritdoc} * - * Must run before PhpdocAlignFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer. + * Must run before PhpdocAlignFixer. * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocToCommentFixer. */ public function getPriority(): int @@ -60,17 +57,11 @@ public function getPriority(): int return 17; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -104,8 +95,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $content = $annotation->getContent(); if ( - 1 !== Preg::match('/[.。]\h*$/u', $content) - || 0 !== Preg::match('/[.。](?!\h*$)/u', $content, $matches) + !Preg::match('/[.。]\h*$/u', $content) + || Preg::match('/[.。](?!\h*$)/u', $content, $matches) ) { continue; } @@ -115,13 +106,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $startLine = $doc->getLine($annotation->getStart()); $optionalTypeRegEx = $annotation->supportTypes() - ? sprintf('(?:%s\s+(?:\$\w+\s+)?)?', preg_quote(implode('|', $annotation->getTypes()), '/')) + ? \sprintf('(?:%s\s+(?:\$\w+\s+)?)?', preg_quote(implode('|', $annotation->getTypes()), '/')) : ''; $content = Preg::replaceCallback( '/^(\s*\*\s*@\w+\s+'.$optionalTypeRegEx.')(\p{Lu}?(?=\p{Ll}|\p{Zs}))(.*)$/', - static function (array $matches): string { - return $matches[1].mb_strtolower($matches[2]).$matches[3]; - }, + static fn (array $matches): string => $matches[1].mb_strtolower($matches[2]).$matches[3], $startLine->getContent(), 1 ); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocArrayTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocArrayTypeFixer.php new file mode 100644 index 0000000000..f3f64c11ff --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocArrayTypeFixer.php @@ -0,0 +1,92 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +final class PhpdocArrayTypeFixer extends AbstractPhpdocTypesFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc `array` type must be used instead of `T[]`.', + [ + new CodeSample(<<<'PHP' + ', $level); + }, + $type, + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php index d926dbbff1..818595d264 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocIndentFixer.php @@ -29,9 +29,6 @@ */ final class PhpdocIndentFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,7 +48,7 @@ class DocBlocks /** * {@inheritdoc} * - * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. * Must run after IndentationTypeFixer, PhpdocToCommentFixer. */ public function getPriority(): int @@ -59,20 +56,16 @@ public function getPriority(): int return 20; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - foreach ($tokens as $index => $token) { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + if (!$token->isGivenKind(T_DOC_COMMENT)) { continue; } @@ -104,7 +97,13 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $newPrevContent = $this->fixWhitespaceBeforeDocblock($prevToken->getContent(), $indent); - if ('' !== $newPrevContent) { + $tokens[$index] = new Token([T_DOC_COMMENT, $this->fixDocBlock($token->getContent(), $indent)]); + + if (!$prevToken->isWhitespace()) { + if ('' !== $indent) { + $tokens->insertAt($index, new Token([T_WHITESPACE, $indent])); + } + } elseif ('' !== $newPrevContent) { if ($prevToken->isArray()) { $tokens[$prevIndex] = new Token([$prevToken->getId(), $newPrevContent]); } else { @@ -113,8 +112,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } else { $tokens->clearAt($prevIndex); } - - $tokens[$index] = new Token([T_DOC_COMMENT, $this->fixDocBlock($token->getContent(), $indent)]); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php index 19b40c43a2..a37b9b837c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocInlineTagNormalizerFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,19 +27,26 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * tags: list + * } + */ final class PhpdocInlineTagNormalizerFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -66,9 +74,6 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (0 === \count($this->configuration['tags'])) { @@ -84,11 +89,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void // remove spaces between '{' and '@', remove white space between end // of text and closing bracket and between the tag and inline comment. $content = Preg::replaceCallback( - sprintf( - '#(?:@{+|{+\h*@)\h*(%s)s?([^}]*)(?:}+)#i', - implode('|', array_map(static function (string $tag): string { - return preg_quote($tag, '/'); - }, $this->configuration['tags'])) + \sprintf( + '#(?:@{+|{+\h*@)\h*(%s)\b([^}]*)(?:}+)#i', + implode('|', array_map(static fn (string $tag): string => preg_quote($tag, '/'), $this->configuration['tags'])) ), static function (array $matches): string { $doc = trim($matches[2]); @@ -106,14 +109,11 @@ static function (array $matches): string { } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('tags', 'The list of tags to normalize')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('tags', 'The list of tags to normalize.')) + ->setAllowedTypes(['string[]']) ->setDefault(['example', 'id', 'internal', 'inheritdoc', 'inheritdocs', 'link', 'source', 'toc', 'tutorial']) ->getOption(), ]); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php index cc2c843192..9a639ae19a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocLineSpanFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -32,12 +33,25 @@ /** * @author Gert de Pagter + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * const?: 'multi'|'single'|null, + * method?: 'multi'|'single'|null, + * property?: 'multi'|'single'|null + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * const: 'multi'|'single'|null, + * method: 'multi'|'single'|null, + * property: 'multi'|'single'|null + * } */ final class PhpdocLineSpanFixer extends AbstractFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -63,29 +77,23 @@ public function getPriority(): int return 7; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('const', 'Whether const blocks should be single or multi line')) + (new FixerOptionBuilder('const', 'Whether const blocks should be single or multi line.')) ->setAllowedValues(['single', 'multi', null]) ->setDefault('multi') ->getOption(), - (new FixerOptionBuilder('property', 'Whether property doc blocks should be single or multi line')) + (new FixerOptionBuilder('property', 'Whether property doc blocks should be single or multi line.')) ->setAllowedValues(['single', 'multi', null]) ->setDefault('multi') ->getOption(), - (new FixerOptionBuilder('method', 'Whether method doc blocks should be single or multi line')) + (new FixerOptionBuilder('method', 'Whether method doc blocks should be single or multi line.')) ->setAllowedValues(['single', 'multi', null]) ->setDefault('multi') ->getOption(), @@ -144,12 +152,20 @@ private function getDocBlockIndex(Tokens $tokens, int $index): int CT::T_NULLABLE_TYPE, ]; + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $propertyPartKinds[] = T_ATTRIBUTE; + } + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required $propertyPartKinds[] = T_READONLY; } do { $index = $tokens->getPrevNonWhitespace($index); + + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]); + } } while ($tokens[$index]->isGivenKind($propertyPartKinds)); return $index; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocListTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocListTypeFixer.php new file mode 100644 index 0000000000..9d2cdff9be --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocListTypeFixer.php @@ -0,0 +1,70 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Tokens; + +final class PhpdocListTypeFixer extends AbstractPhpdocTypesFixer +{ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return true; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'PHPDoc `list` type must be used instead of `array` without a key.', + [ + new CodeSample(<<<'PHP' + $x + * @param array> $y + */ + + PHP), + ], + null, + 'Risky when `array` key should be present, but is missing.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer, PhpdocTypesOrderFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return 1; + } + + protected function normalize(string $type): string + { + return Preg::replace('/array(?=<(?:[^,<]|<[^>]+>)+(>|{|\())/i', 'list', $type); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php index a168a9137f..e5f899ff1b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAccessFixer.php @@ -25,9 +25,6 @@ */ final class PhpdocNoAccessFixer extends AbstractProxyFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -60,13 +57,14 @@ public function getPriority(): int return parent::getPriority(); } - /** - * {@inheritdoc} - */ protected function createProxyFixers(): array { $fixer = new GeneralPhpdocAnnotationRemoveFixer(); - $fixer->configure(['annotations' => ['access']]); + $fixer->configure( + ['annotations' => ['access'], + 'case_sensitive' => true, + ] + ); return [$fixer]; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php index c4ec368921..d50e42d152 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoAliasTagFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\ConfigurationException\InvalidConfigurationException; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -31,12 +32,21 @@ * * @author Graham Campbell * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * replacements?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * replacements: array + * } */ final class PhpdocNoAliasTagFixer extends AbstractProxyFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -84,10 +94,8 @@ public function getPriority(): int return parent::getPriority(); } - public function configure(array $configuration): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - /** @var GeneralPhpdocTagRenameFixer $generalPhpdocTagRenameFixer */ $generalPhpdocTagRenameFixer = $this->proxyFixers['general_phpdoc_tag_rename']; @@ -107,14 +115,11 @@ public function configure(array $configuration): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('replacements', 'Mapping between replaced annotations with new ones.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['array']) ->setDefault([ 'property-read' => 'property', 'property-write' => 'property', @@ -125,9 +130,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function createProxyFixers(): array { return [new GeneralPhpdocTagRenameFixer()]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php index b318aa6959..3b22bad975 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php @@ -28,17 +28,11 @@ */ final class PhpdocNoEmptyReturnFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -75,9 +69,6 @@ public function getPriority(): int return 4; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php index d7da1d1aeb..3e769762d3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoPackageFixer.php @@ -25,9 +25,6 @@ */ final class PhpdocNoPackageFixer extends AbstractProxyFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -60,13 +57,13 @@ public function getPriority(): int return parent::getPriority(); } - /** - * {@inheritdoc} - */ protected function createProxyFixers(): array { $fixer = new GeneralPhpdocAnnotationRemoveFixer(); - $fixer->configure(['annotations' => ['package', 'subpackage']]); + $fixer->configure([ + 'annotations' => ['package', 'subpackage'], + 'case_sensitive' => true, + ]); return [$fixer]; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php index 65d420fab0..b1e88a3afc 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php @@ -28,9 +28,6 @@ */ final class PhpdocNoUselessInheritdocFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,17 +50,11 @@ public function getPriority(): int return 6; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // min. offset 4 as minimal candidate is @: ' '.$matches[2], $tokens[$tokenIndex]->getContent(), -1, $count ); - if ($count) { + if ($count > 0) { $tokens[$tokenIndex] = new Token([T_DOC_COMMENT, $content]); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php index 28bee07066..74f0eb4098 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -32,16 +33,25 @@ /** * @author Filippo Tessarotto * @author Andreas Möller + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * annotations?: list<'author'|'covers'|'coversNothing'|'dataProvider'|'depends'|'group'|'internal'|'method'|'mixin'|'property'|'property-read'|'property-write'|'requires'|'throws'|'uses'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * annotations: array{'author'?: 'author', 'covers'?: 'covers', 'coversNothing'?: 'coversnothing', 'dataProvider'?: 'dataprovider', 'depends'?: 'depends', 'group'?: 'group', 'internal'?: 'internal', 'method'?: 'method', 'mixin'?: 'mixin', 'property'?: 'property', 'property-read'?: 'property-read', 'property-write'?: 'property-write', 'requires'?: 'requires', 'throws'?: 'throws', 'uses'?: 'uses'} + * } */ final class PhpdocOrderByValueFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Order phpdoc tags by value.', + 'Order PHPDoc tags by value.', [ new CodeSample( 'isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if ([] === $this->configuration['annotations']) { @@ -102,7 +106,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void for ($index = $tokens->count() - 1; $index > 0; --$index) { foreach ($this->configuration['annotations'] as $type => $typeLowerCase) { - $findPattern = sprintf( + $findPattern = \sprintf( '/@%s\s.+@%s\s/s', $type, $type @@ -110,7 +114,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void if ( !$tokens[$index]->isGivenKind(T_DOC_COMMENT) - || 0 === Preg::match($findPattern, $tokens[$index]->getContent()) + || !Preg::match($findPattern, $tokens[$index]->getContent()) ) { continue; } @@ -121,8 +125,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $annotationMap = []; if (\in_array($type, ['property', 'property-read', 'property-write'], true)) { - $replacePattern = sprintf( - '/(?s)\*\s*@%s\s+(?P.+\s+)?\$(?P[^\s]+).*/', + $replacePattern = \sprintf( + '/(?s)\*\s*@%s\s+(?P.+\s+)?\$(?P\S+).*/', $type ); @@ -131,7 +135,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $replacePattern = '/(?s)\*\s*@method\s+(?P.+\s+)?(?P.+)\(.*/'; $replacement = '\2'; } else { - $replacePattern = sprintf( + $replacePattern = \sprintf( '/\*\s*@%s\s+(?P.+)/', $typeLowerCase ); @@ -186,6 +190,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn 'group', 'internal', 'method', + 'mixin', 'property', 'property-read', 'property-write', @@ -196,13 +201,11 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn return new FixerConfigurationResolver([ (new FixerOptionBuilder('annotations', 'List of annotations to order, e.g. `["covers"]`.')) - ->setAllowedTypes([ - 'array', - ]) + ->setAllowedTypes(['string[]']) ->setAllowedValues([ new AllowedValueSubset($allowedValues), ]) - ->setNormalizer(static function (Options $options, $value): array { + ->setNormalizer(static function (Options $options, array $value): array { $normalized = []; foreach ($value as $annotation) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php index b9eac52a46..d7423f5e97 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocOrderFixer.php @@ -16,50 +16,75 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Graham Campbell + * @author Jakub Kwaśniewski + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * order?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * order: list + * } */ -final class PhpdocOrderFixer extends AbstractFixer +final class PhpdocOrderFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool - { - return $tokens->isTokenKindFound(T_DOC_COMMENT); - } + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; /** - * {@inheritdoc} + * @const string[] + * + * @TODO: 4.0 - change default to ['param', 'return', 'throws'] */ + private const ORDER_DEFAULT = ['param', 'throws', 'return']; + public function getDefinition(): FixerDefinitionInterface { + $code = <<<'EOF' + self::ORDER_DEFAULT]), + new CodeSample($code, ['order' => ['param', 'return', 'throws']]), + new CodeSample($code, ['order' => ['param', 'custom', 'throws', 'return']]), + ], ); } + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + /** * {@inheritdoc} * @@ -71,9 +96,23 @@ public function getPriority(): int return -2; } - /** - * {@inheritdoc} - */ + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('order', 'Sequence in which annotations in PHPDoc should be ordered.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([static function (array $order): bool { + if (\count($order) < 2) { + throw new InvalidOptionsException('The option "order" value is invalid. Minimum two tags are required.'); + } + + return true; + }]) + ->setDefault(self::ORDER_DEFAULT) + ->getOption(), + ]); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -81,38 +120,53 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } + // assuming annotations are already grouped by tags $content = $token->getContent(); - // move param to start, return to end, leave throws in the middle - $content = $this->moveParamAnnotations($content); - // we're parsing the content again to make sure the internal + + // sort annotations + /** @var list */ + $successors = $this->configuration['order']; + while (\count($successors) >= 3) { + $predecessor = array_shift($successors); + $content = $this->moveAnnotationsBefore($predecessor, $successors, $content); + } + + // we're parsing the content last time to make sure the internal // state of the docblock is correct after the modifications - $content = $this->moveReturnAnnotations($content); + /** @var list */ + $predecessors = $this->configuration['order']; + $last = array_pop($predecessors); + $content = $this->moveAnnotationsAfter($last, $predecessors, $content); + // persist the content at the end $tokens[$index] = new Token([T_DOC_COMMENT, $content]); } } /** - * Move all param annotations in before throws and return annotations. + * Move all given annotations in before given set of annotations. + * + * @param string $move Tag of annotations that should be moved + * @param list $before Tags of annotations that should moved annotations be placed before */ - private function moveParamAnnotations(string $content): string + private function moveAnnotationsBefore(string $move, array $before, string $content): string { $doc = new DocBlock($content); - $params = $doc->getAnnotationsOfType('param'); + $toBeMoved = $doc->getAnnotationsOfType($move); - // nothing to do if there are no param annotations - if (0 === \count($params)) { + // nothing to do if there are no annotations to be moved + if (0 === \count($toBeMoved)) { return $content; } - $others = $doc->getAnnotationsOfType(['throws', 'return']); + $others = $doc->getAnnotationsOfType($before); if (0 === \count($others)) { return $content; } - // get the index of the final line of the final param annotation - $end = end($params)->getEnd(); + // get the index of the final line of the final toBoMoved annotation + $end = end($toBeMoved)->getEnd(); $line = $doc->getLine($end); @@ -129,27 +183,30 @@ private function moveParamAnnotations(string $content): string } /** - * Move all return annotations after param and throws annotations. + * Move all given annotations after given set of annotations. + * + * @param string $move Tag of annotations that should be moved + * @param list $after Tags of annotations that should moved annotations be placed after */ - private function moveReturnAnnotations(string $content): string + private function moveAnnotationsAfter(string $move, array $after, string $content): string { $doc = new DocBlock($content); - $returns = $doc->getAnnotationsOfType('return'); + $toBeMoved = $doc->getAnnotationsOfType($move); - // nothing to do if there are no return annotations - if (0 === \count($returns)) { + // nothing to do if there are no annotations to be moved + if (0 === \count($toBeMoved)) { return $content; } - $others = $doc->getAnnotationsOfType(['param', 'throws']); + $others = $doc->getAnnotationsOfType($after); // nothing to do if there are no other annotations if (0 === \count($others)) { return $content; } - // get the index of the first line of the first return annotation - $start = $returns[0]->getStart(); + // get the index of the first line of the first toBeMoved annotation + $start = $toBeMoved[0]->getStart(); $line = $doc->getLine($start); // move stuff about if required diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocParamOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocParamOrderFixer.php new file mode 100644 index 0000000000..dbed46eb4d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocParamOrderFixer.php @@ -0,0 +1,273 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Phpdoc; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\Annotation; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jonathan Gruber + */ +final class PhpdocParamOrderFixer extends AbstractFixer +{ + private const PARAM_TAG = 'param'; + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_DOC_COMMENT); + } + + /** + * {@inheritdoc} + * + * Must run before PhpdocAlignFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + */ + public function getPriority(): int + { + return parent::getPriority(); + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Orders all `@param` annotations in DocBlocks according to method signature.', + [ + new CodeSample( + ' $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + // Check for function / closure token + $nextFunctionToken = $tokens->getNextTokenOfKind($index, [[T_FUNCTION], [T_FN]]); + if (null === $nextFunctionToken) { + return; + } + + // Find start index of param block (opening parenthesis) + $paramBlockStart = $tokens->getNextTokenOfKind($index, ['(']); + if (null === $paramBlockStart) { + return; + } + + $doc = new DocBlock($tokens[$index]->getContent()); + $paramAnnotations = $doc->getAnnotationsOfType(self::PARAM_TAG); + + if ([] === $paramAnnotations) { + continue; + } + + $paramNames = $this->getFunctionParamNames($tokens, $paramBlockStart); + $doc = $this->rewriteDocBlock($doc, $paramNames, $paramAnnotations); + + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); + } + } + + /** + * @return list + */ + private function getFunctionParamNames(Tokens $tokens, int $paramBlockStart): array + { + $paramBlockEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $paramBlockStart); + + $paramNames = []; + for ( + $i = $tokens->getNextTokenOfKind($paramBlockStart, [[T_VARIABLE]]); + null !== $i && $i < $paramBlockEnd; + $i = $tokens->getNextTokenOfKind($i, [[T_VARIABLE]]) + ) { + $paramNames[] = $tokens[$i]; + } + + return $paramNames; + } + + /** + * Overwrite the param annotations in order. + * + * @param list $paramNames + * @param list $paramAnnotations + */ + private function rewriteDocBlock(DocBlock $doc, array $paramNames, array $paramAnnotations): DocBlock + { + $orderedAnnotations = $this->sortParamAnnotations($paramNames, $paramAnnotations); + $otherAnnotations = $this->getOtherAnnotationsBetweenParams($doc, $paramAnnotations); + + // Append annotations found between param ones + if ([] !== $otherAnnotations) { + array_push($orderedAnnotations, ...$otherAnnotations); + } + + // Overwrite all annotations between first and last @param tag in order + $paramsStart = reset($paramAnnotations)->getStart(); + $paramsEnd = end($paramAnnotations)->getEnd(); + + foreach ($doc->getAnnotations() as $annotation) { + if ($annotation->getStart() < $paramsStart || $annotation->getEnd() > $paramsEnd) { + continue; + } + + $annotation->remove(); + $doc + ->getLine($annotation->getStart()) + ->setContent(current($orderedAnnotations)) + ; + + next($orderedAnnotations); + } + + return $doc; + } + + /** + * Sort the param annotations according to the function parameters. + * + * @param list $funcParamNames + * @param list $paramAnnotations + * + * @return list + */ + private function sortParamAnnotations(array $funcParamNames, array $paramAnnotations): array + { + $validParams = []; + foreach ($funcParamNames as $paramName) { + $indices = $this->findParamAnnotationByIdentifier($paramAnnotations, $paramName->getContent()); + + // Found an exactly matching @param annotation + if (\is_array($indices)) { + foreach ($indices as $index) { + $validParams[$index] = $paramAnnotations[$index]->getContent(); + } + } + } + + // Detect superfluous annotations + /** @var list $invalidParams */ + $invalidParams = array_values( + array_diff_key($paramAnnotations, $validParams) + ); + + // Append invalid parameters to the (ordered) valid ones + $orderedParams = array_values($validParams); + foreach ($invalidParams as $params) { + $orderedParams[] = $params->getContent(); + } + + return $orderedParams; + } + + /** + * Fetch all annotations except the param ones. + * + * @param list $paramAnnotations + * + * @return list + */ + private function getOtherAnnotationsBetweenParams(DocBlock $doc, array $paramAnnotations): array + { + if (0 === \count($paramAnnotations)) { + return []; + } + + $paramsStart = reset($paramAnnotations)->getStart(); + $paramsEnd = end($paramAnnotations)->getEnd(); + + $otherAnnotations = []; + foreach ($doc->getAnnotations() as $annotation) { + if ($annotation->getStart() < $paramsStart || $annotation->getEnd() > $paramsEnd) { + continue; + } + + if (self::PARAM_TAG !== $annotation->getTag()->getName()) { + $otherAnnotations[] = $annotation->getContent(); + } + } + + return $otherAnnotations; + } + + /** + * Return the indices of the lines of a specific parameter annotation. + * + * @param list $paramAnnotations + * + * @return ?list + */ + private function findParamAnnotationByIdentifier(array $paramAnnotations, string $identifier): ?array + { + $blockLevel = 0; + $blockMatch = false; + $blockIndices = []; + + $paramRegex = '/\*\h*@param\h*(?:|'.TypeExpression::REGEX_TYPES.'\h*)&?(?=\$\b)'.preg_quote($identifier).'\b/'; + + foreach ($paramAnnotations as $i => $param) { + $blockStart = Preg::match('/\s*{\s*/', $param->getContent()); + $blockEndMatches = Preg::matchAll('/}[\*\s\n]*/', $param->getContent()); + + if (0 === $blockLevel && Preg::match($paramRegex, $param->getContent())) { + if ($blockStart) { + $blockMatch = true; // Start of a nested block + } else { + return [$i]; // Top level match + } + } + + if ($blockStart) { + ++$blockLevel; + } + + if (0 !== $blockEndMatches) { + $blockLevel -= $blockEndMatches; + } + + if ($blockMatch) { + $blockIndices[] = $i; + if (0 === $blockLevel) { + return $blockIndices; + } + } + } + + return null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php index ea32309609..d0cd912d95 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,13 +27,27 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * replacements?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * replacements: array + * } + */ final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var string[] + * @var list */ private static array $toTypes = [ '$this', @@ -40,9 +55,6 @@ final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements Conf 'self', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -97,9 +109,6 @@ public function test2() ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return \count($tokens) > 10 && $tokens->isAllTokenKindsFound([T_DOC_COMMENT, T_FUNCTION]) && $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); @@ -116,9 +125,6 @@ public function getPriority(): int return 10; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokensAnalyzer = new TokensAnalyzer($tokens); @@ -130,9 +136,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $default = [ @@ -146,7 +149,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn return new FixerConfigurationResolver([ (new FixerOptionBuilder('replacements', 'Mapping between replaced return types with new ones.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['array']) ->setNormalizer(static function (Options $options, array $value) use ($default): array { $normalizedValue = []; @@ -156,18 +159,18 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn } if (!isset($default[$from])) { - throw new InvalidOptionsException(sprintf( - 'Unknown key "%s", expected any of "%s".', + throw new InvalidOptionsException(\sprintf( + 'Unknown key "%s", expected any of %s.', \gettype($from).'#'.$from, - implode('", "', array_keys($default)) + Utils::naturalLanguageJoin(array_keys($default)) )); } if (!\in_array($to, self::$toTypes, true)) { - throw new InvalidOptionsException(sprintf( - 'Unknown value "%s", expected any of "%s".', + throw new InvalidOptionsException(\sprintf( + 'Unknown value "%s", expected any of %s.', \is_object($to) ? \get_class($to) : \gettype($to).(\is_resource($to) ? '' : '#'.$to), - implode('", "', self::$toTypes) + Utils::naturalLanguageJoin(self::$toTypes) )); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php index a77761b9c4..d047b457a5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocScalarFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractPhpdocTypesFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -26,13 +27,25 @@ /** * @author Graham Campbell + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * types?: list<'boolean'|'callback'|'double'|'integer'|'real'|'str'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * types: list<'boolean'|'callback'|'double'|'integer'|'real'|'str'> + * } */ final class PhpdocScalarFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * The types to fix. */ - private static array $types = [ + private const TYPES_MAP = [ 'boolean' => 'bool', 'callback' => 'callable', 'double' => 'float', @@ -41,9 +54,6 @@ final class PhpdocScalarFixer extends AbstractPhpdocTypesFixer implements Config 'str' => 'string', ]; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -83,7 +93,7 @@ function sample($a, $b, $c) /** * {@inheritdoc} * - * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocArrayTypeFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer. * Must run after PhpdocTypesFixer. */ public function getPriority(): int @@ -100,12 +110,9 @@ public function getPriority(): int return 15; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { - $types = array_keys(self::$types); + $types = array_keys(self::TYPES_MAP); return new FixerConfigurationResolver([ (new FixerOptionBuilder('types', 'A list of types to fix.')) @@ -115,15 +122,18 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function normalize(string $type): string { + $suffix = ''; + while (str_ends_with($type, '[]')) { + $type = substr($type, 0, -2); + $suffix .= '[]'; + } + if (\in_array($type, $this->configuration['types'], true)) { - return self::$types[$type]; + $type = self::TYPES_MAP[$type]; } - return $type; + return $type.$suffix; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php index b8140f6264..7c2f1d91d8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSeparationFixer.php @@ -17,41 +17,107 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; -use PhpCsFixer\DocBlock\TagComparator; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; /** * @author Graham Campbell + * @author Jakub Kwaśniewski + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * groups?: list>, + * skip_unlisted_annotations?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * groups: list>, + * skip_unlisted_annotations: bool + * } */ -final class PhpdocSeparationFixer extends AbstractFixer +final class PhpdocSeparationFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * {@inheritdoc} + * @internal + * + * @var list> + */ + public const OPTION_GROUPS_DEFAULT = [ + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ['deprecated', 'link', 'see', 'since'], + ]; + + /** + * @var list> */ + private array $groups; + public function getDefinition(): FixerDefinitionInterface { + $code = <<<'EOF' + [ + ['deprecated', 'link', 'see', 'since'], + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ['param', 'return'], + ]]), + new CodeSample($code, ['groups' => [ + ['author', 'throws', 'custom'], + ['return', 'param'], + ]]), new CodeSample( - ' [['ORM\*'], ['Assert\*']]], ), - ] + new CodeSample($code, ['skip_unlisted_annotations' => true]), + ], ); } @@ -59,24 +125,23 @@ function fnc($foo, $bar) {} * {@inheritdoc} * * Must run before PhpdocAlignFixer. - * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpUnitAttributesFixer, PhpUnitInternalClassFixer, PhpUnitSizeClassFixer, PhpUnitTestClassRequiresCoversFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int { return -3; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $this->groups = $this->configuration['groups']; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -92,6 +157,45 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + $allowTagToBelongToOnlyOneGroup = static function (array $groups): bool { + $tags = []; + foreach ($groups as $groupIndex => $group) { + foreach ($group as $member) { + if (isset($tags[$member])) { + if ($groupIndex === $tags[$member]) { + throw new InvalidOptionsException( + 'The option "groups" value is invalid. '. + 'The "'.$member.'" tag is specified more than once.' + ); + } + + throw new InvalidOptionsException( + 'The option "groups" value is invalid. '. + 'The "'.$member.'" tag belongs to more than one group.' + ); + } + $tags[$member] = $groupIndex; + } + } + + return true; + }; + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('groups', 'Sets of annotation types to be grouped together. Use `*` to match any tag character.')) + ->setAllowedTypes(['string[][]']) + ->setDefault(self::OPTION_GROUPS_DEFAULT) + ->setAllowedValues([$allowTagToBelongToOnlyOneGroup]) + ->getOption(), + (new FixerOptionBuilder('skip_unlisted_annotations', 'Whether to skip annotations that are not listed in any group.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO 4.0: set to `true`. + ->getOption(), + ]); + } + /** * Make sure the description is separated from the annotations. */ @@ -126,12 +230,12 @@ private function fixAnnotations(DocBlock $doc): void break; } - if (true === $next->getTag()->valid()) { - if (TagComparator::shouldBeTogether($annotation->getTag(), $next->getTag())) { - $this->ensureAreTogether($doc, $annotation, $next); - } else { - $this->ensureAreSeparate($doc, $annotation, $next); - } + $shouldBeTogether = $this->shouldBeTogether($annotation, $next, $this->groups); + + if (true === $shouldBeTogether) { + $this->ensureAreTogether($doc, $annotation, $next); + } elseif (false === $shouldBeTogether || false === $this->configuration['skip_unlisted_annotations']) { + $this->ensureAreSeparate($doc, $annotation, $next); } } } @@ -144,7 +248,7 @@ private function ensureAreTogether(DocBlock $doc, Annotation $first, Annotation $pos = $first->getEnd(); $final = $second->getStart(); - for ($pos = $pos + 1; $pos < $final; ++$pos) { + for (++$pos; $pos < $final; ++$pos) { $doc->getLine($pos)->remove(); } } @@ -164,8 +268,66 @@ private function ensureAreSeparate(DocBlock $doc, Annotation $first, Annotation return; } - for ($pos = $pos + 1; $pos < $final; ++$pos) { + for (++$pos; $pos < $final; ++$pos) { $doc->getLine($pos)->remove(); } } + + /** + * @param list> $groups + */ + private function shouldBeTogether(Annotation $first, Annotation $second, array $groups): ?bool + { + $firstName = $this->tagName($first); + $secondName = $this->tagName($second); + + // A tag could not be read. + if (null === $firstName || null === $secondName) { + return null; + } + + if ($firstName === $secondName) { + return true; + } + + foreach ($groups as $group) { + $firstTagIsInGroup = $this->isInGroup($firstName, $group); + $secondTagIsInGroup = $this->isInGroup($secondName, $group); + + if ($firstTagIsInGroup) { + return $secondTagIsInGroup; + } + + if ($secondTagIsInGroup) { + return false; + } + } + + return null; + } + + private function tagName(Annotation $annotation): ?string + { + Preg::match('/@([a-zA-Z0-9_\\\-]+(?=\s|$|\())/', $annotation->getContent(), $matches); + + return $matches[1] ?? null; + } + + /** + * @param list $group + */ + private function isInGroup(string $tag, array $group): bool + { + foreach ($group as $tagInGroup) { + $tagInGroup = str_replace('*', '\*', $tagInGroup); + $tagInGroup = preg_quote($tagInGroup, '/'); + $tagInGroup = str_replace('\\\\\*', '.*?', $tagInGroup); + + if (Preg::match("/^{$tagInGroup}$/", $tag)) { + return true; + } + } + + return false; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php index 8ccd3d74ea..9c2bda1d0f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php @@ -27,9 +27,6 @@ */ final class PhpdocSingleLineVarSpacingFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -49,17 +46,11 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { /** @var Token $token */ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php index 090ac88f33..65406b37e1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocSummaryFixer.php @@ -29,9 +29,6 @@ */ final class PhpdocSummaryFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -56,17 +53,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -81,7 +72,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $line = $doc->getLine($end); $content = rtrim($line->getContent()); - if (!$this->isCorrectlyFormatted($content)) { + if ( + // final line of Description is NOT properly formatted + !$this->isCorrectlyFormatted($content) + // and first line of Description, if different than final line, does NOT indicate a list + && (1 === $end || ($doc->isMultiLine() && ':' !== substr(rtrim($doc->getLine(1)->getContent()), -1))) + ) { $line->setContent($content.'.'.$this->whitespacesConfig->getLineEnding()); $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } @@ -98,6 +94,6 @@ private function isCorrectlyFormatted(string $content): bool return true; } - return $content !== rtrim($content, '.。!?¡¿!?'); + return $content !== rtrim($content, '.:。!?¡¿!?'); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php index b9608320f7..16ef2e8898 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagCasingFixer.php @@ -18,6 +18,7 @@ use PhpCsFixer\ConfigurationException\InvalidConfigurationException; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,11 +27,21 @@ use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * tags: list + * } + */ final class PhpdocTagCasingFixer extends AbstractProxyFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -55,10 +66,8 @@ public function getPriority(): int return parent::getPriority(); } - public function configure(array $configuration): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - $replacements = []; foreach ($this->configuration['tags'] as $tag) { $replacements[$tag] = $tag; @@ -69,10 +78,10 @@ public function configure(array $configuration): void try { $generalPhpdocTagRenameFixer->configure([ + 'case_sensitive' => false, 'fix_annotation' => true, 'fix_inline' => true, 'replacements' => $replacements, - 'case_sensitive' => false, ]); } catch (InvalidConfigurationException $exception) { throw new InvalidFixerConfigurationException( @@ -83,22 +92,16 @@ public function configure(array $configuration): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('tags', 'List of tags to fix with their expected casing.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setDefault(['inheritDoc']) ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function createProxyFixers(): array { return [new GeneralPhpdocTagRenameFixer()]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php index 216d9f984b..150115c670 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTagTypeFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,8 +29,21 @@ use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; +/** + * @phpstan-type _AutogeneratedInputConfiguration array{ + * tags?: array + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * tags: array + * } + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + */ final class PhpdocTagTypeFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + private const TAG_REGEX = '/^(?: (? (?:@(?.+?)(?:\s.+)?) @@ -40,17 +54,11 @@ final class PhpdocTagTypeFixer extends AbstractFixer implements ConfigurableFixe )} )$/x'; - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -78,21 +86,16 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { if (0 === \count($this->configuration['tags'])) { return; } - $regularExpression = sprintf( + $regularExpression = \sprintf( '/({?@(?:%s).*?(?:(?=\s\*\/)|(?=\n)}?))/i', implode('|', array_map( - static function (string $tag): string { - return preg_quote($tag, '/'); - }, + static fn (string $tag): string => preg_quote($tag, '/'), array_keys($this->configuration['tags']) )) ); @@ -142,14 +145,11 @@ static function (string $tag): string { } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('tags', 'The list of tags to fix')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('tags', 'The list of tags to fix.')) + ->setAllowedTypes(["array"]) ->setAllowedValues([static function (array $value): bool { foreach ($value as $type) { if (!\in_array($type, ['annotation', 'inline'], true)) { @@ -182,7 +182,7 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn 'var' => 'annotation', 'version' => 'annotation', ]) - ->setNormalizer(static function (Options $options, $value): array { + ->setNormalizer(static function (Options $options, array $value): array { $normalized = []; foreach ($value as $tag => $type) { @@ -195,12 +195,14 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } + /** + * @param array $parts + */ private function tagIsSurroundedByText(array $parts, int $index): bool { return Preg::match('/(^|\R)\h*[^@\s]\N*/', $this->cleanComment($parts[$index - 1])) - || Preg::match('/^.*?\R\s*[^@\s]/', $this->cleanComment($parts[$index + 1])) - ; + || Preg::match('/^.*?\R\s*[^@\s]/', $this->cleanComment($parts[$index + 1])); } private function cleanComment(string $comment): string diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php index a536afa900..08e3574059 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocToCommentFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -30,17 +31,29 @@ /** * @author Ceeram * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * allow_before_return_statement?: bool, + * ignored_tags?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * allow_before_return_statement: bool, + * ignored_tags: list + * } */ final class PhpdocToCommentFixer extends AbstractFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var string[] + * @var list */ private array $ignoredTags = []; + private bool $allowBeforeReturnStatement = false; - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); @@ -49,7 +62,7 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} * - * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer, SingleLineCommentSpacingFixer, SingleLineCommentStyleFixer. + * Must run before GeneralPhpdocAnnotationRemoveFixer, GeneralPhpdocTagRenameFixer, NoBlankLinesAfterPhpdocFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAddMissingParamAnnotationFixer, PhpdocAlignFixer, PhpdocAnnotationWithoutDotFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocInlineTagNormalizerFixer, PhpdocLineSpanFixer, PhpdocListTypeFixer, PhpdocNoAccessFixer, PhpdocNoAliasTagFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocNoUselessInheritdocFixer, PhpdocOrderByValueFixer, PhpdocOrderFixer, PhpdocParamOrderFixer, PhpdocReadonlyClassCommentToKeywordFixer, PhpdocReturnSelfReferenceFixer, PhpdocSeparationFixer, PhpdocSingleLineVarSpacingFixer, PhpdocSummaryFixer, PhpdocTagCasingFixer, PhpdocTagTypeFixer, PhpdocToParamTypeFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer, PhpdocTrimConsecutiveBlankLineSeparationFixer, PhpdocTrimFixer, PhpdocTypesOrderFixer, PhpdocVarAnnotationCorrectOrderFixer, PhpdocVarWithoutNameFixer, SingleLineCommentSpacingFixer, SingleLineCommentStyleFixer. * Must run after CommentToPhpdocFixer. */ public function getPriority(): int @@ -62,9 +75,6 @@ public function getPriority(): int return 25; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -96,41 +106,50 @@ public function getDefinition(): FixerDefinitionInterface ', ['ignored_tags' => ['todo']] ), + new CodeSample( + ' $sqlite) { + $sqlite->open($path); +} + +function returnClassName() { + /** @var class-string */ + return \StdClass::class; +} +', + ['allow_before_return_statement' => true] + ), ] ); } - /** - * {@inheritdoc} - */ - public function configure(array $configuration = null): void + protected function configurePostNormalisation(): void { - parent::configure($configuration); - $this->ignoredTags = array_map( - static function (string $tag): string { - return strtolower($tag); - }, + static fn (string $tag): string => strtolower($tag), $this->configuration['ignored_tags'] ); + + $this->allowBeforeReturnStatement = true === $this->configuration['allow_before_return_statement']; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('ignored_tags', 'List of ignored tags (matched case insensitively)')) - ->setAllowedTypes(['array']) + (new FixerOptionBuilder('ignored_tags', 'List of ignored tags (matched case insensitively).')) + ->setAllowedTypes(['string[]']) ->setDefault([]) ->getOption(), + (new FixerOptionBuilder('allow_before_return_statement', 'Whether to allow PHPDoc before return statement.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO 4.0: set to `true` + ->getOption(), ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $commentsAnalyzer = new CommentsAnalyzer(); @@ -144,11 +163,15 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } + if ($this->allowBeforeReturnStatement && $commentsAnalyzer->isBeforeReturn($tokens, $index)) { + continue; + } + if ($commentsAnalyzer->isBeforeStructuralElement($tokens, $index)) { continue; } - if (0 < Preg::matchAll('~\@([a-zA-Z0-9_\\\\-]+)\b~', $token->getContent(), $matches)) { + if (0 < Preg::matchAll('~\@([a-zA-Z0-9_\\\-]+)\b~', $token->getContent(), $matches)) { foreach ($matches[1] as $match) { if (\in_array(strtolower($match), $this->ignoredTags, true)) { continue 2; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php index 3d41bdb859..6b107af1b8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php @@ -30,9 +30,6 @@ */ final class PhpdocTrimConsecutiveBlankLineSeparationFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -66,24 +63,18 @@ function fnc($foo) {} * {@inheritdoc} * * Must run before PhpdocAlignFixer. - * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpUnitAttributesFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int { return -41; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -174,13 +165,16 @@ private function findNonBlankLine(DocBlock $doc, int $after): ?int private function findFirstAnnotationOrEnd(DocBlock $doc): int { - $index = null; foreach ($doc->getLines() as $index => $line) { if ($line->containsATag()) { return $index; } } + if (!isset($index)) { + throw new \LogicException('PHPDoc has empty lines collection.'); + } + return $index; // no Annotation, return the last line } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php index d835fb337e..d6b2391ee6 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTrimFixer.php @@ -27,9 +27,6 @@ */ final class PhpdocTrimFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -50,24 +47,18 @@ final class Foo {} * {@inheritdoc} * * Must run before PhpdocAlignFixer. - * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpUnitTestAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, GeneralPhpdocAnnotationRemoveFixer, PhpUnitAttributesFixer, PhpUnitTestAnnotationFixer, PhpdocIndentFixer, PhpdocNoAccessFixer, PhpdocNoEmptyReturnFixer, PhpdocNoPackageFixer, PhpdocOrderFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int { return -5; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesFixer.php index b447d1e3ea..e26e8a0c3b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractPhpdocTypesFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -28,13 +29,25 @@ /** * @author Graham Campbell * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * groups?: list<'alias'|'meta'|'simple'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * groups: list<'alias'|'meta'|'simple'> + * } */ final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * Available types, grouped. * - * @var array + * @var array> */ private const POSSIBLE_TYPES = [ 'simple' => [ @@ -50,10 +63,8 @@ final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements Configu ], 'alias' => [ 'boolean', - 'callback', 'double', 'integer', - 'real', ], 'meta' => [ '$this', @@ -69,36 +80,9 @@ final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements Configu ], ]; - private string $patternToFix = ''; + /** @var array */ + private array $typesSetToFix; - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $typesToFix = array_merge(...array_map(static function (string $group): array { - return self::POSSIBLE_TYPES[$group]; - }, $this->configuration['groups'])); - - $this->patternToFix = sprintf( - '/(? self::POSSIBLE_TYPES[$group], $this->configuration['groups'])); + + $this->typesSetToFix = array_combine($typesToFix, array_fill(0, \count($typesToFix), true)); + } + protected function normalize(string $type): string { + $typeLower = strtolower($type); + if (isset($this->typesSetToFix[$typeLower])) { + $type = $typeLower; + } + + // normalize shape/callable/generic identifiers too + // TODO parse them as inner types and this will be not needed then return Preg::replaceCallback( - $this->patternToFix, - function (array $matches): string { - return strtolower($matches[0]); - }, + '/^(\??\s*)([^()[\]{}<>\'"]+)(?])/', + fn ($matches) => $matches[1].$this->normalize($matches[2]).$matches[3], $type ); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $possibleGroups = array_keys(self::POSSIBLE_TYPES); return new FixerConfigurationResolver([ (new FixerOptionBuilder('groups', 'Type groups to fix.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($possibleGroups)]) ->setDefault($possibleGroups) ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php index 6cff3474b8..c8fddacbe0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php @@ -19,6 +19,7 @@ use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,11 +30,25 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * case_sensitive?: bool, + * null_adjustment?: 'always_first'|'always_last'|'none', + * sort_algorithm?: 'alpha'|'none' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * case_sensitive: bool, + * null_adjustment: 'always_first'|'always_last'|'none', + * sort_algorithm: 'alpha'|'none' + * } + */ final class PhpdocTypesOrderFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -84,6 +99,14 @@ public function getDefinition(): FixerDefinitionInterface 'null_adjustment' => 'none', ] ), + new CodeSample( + ' true] + ), ] ); } @@ -92,24 +115,18 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before PhpdocAlignFixer. - * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocAnnotationWithoutDotFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. + * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocArrayTypeFixer, PhpdocIndentFixer, PhpdocListTypeFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int { return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -121,6 +138,10 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setAllowedValues(['always_first', 'always_last', 'none']) ->setDefault('always_first') ->getOption(), + (new FixerOptionBuilder('case_sensitive', 'Whether the sorting should be case sensitive.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), ]); } @@ -140,20 +161,20 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void foreach ($annotations as $annotation) { // fix main types - $annotation->setTypes( - $this->sortTypes( - $annotation->getTypeExpression() - ) - ); + if (null !== $annotation->getTypeExpression()) { + $annotation->setTypes( + $this->sortTypes( + $annotation->getTypeExpression() + ) + ); + } // fix @method parameters types $line = $doc->getLine($annotation->getStart()); - $line->setContent(Preg::replaceCallback('/(@method\s+.+?\s+\w+\()(.*)\)/', function (array $matches) { - $sorted = Preg::replaceCallback('/([^\s,]+)([\s]+\$[^\s,]+)/', function (array $matches): string { - return $this->sortJoinedTypes($matches[1]).$matches[2]; - }, $matches[2]); + $line->setContent(Preg::replaceCallback('/\*\h*@method\h+'.TypeExpression::REGEX_TYPES.'\h+\K(?&callable)/', function (array $matches) { + $typeExpression = new TypeExpression($matches[0], null, []); - return $matches[1].$sorted.')'; + return implode('|', $this->sortTypes($typeExpression)); }, $line->getContent())); } @@ -162,13 +183,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } /** - * @return string[] + * @return list */ private function sortTypes(TypeExpression $typeExpression): array { - $normalizeType = static function (string $type): string { - return Preg::replace('/^\\??\\\?/', '', $type); - }; + $normalizeType = static fn (string $type): string => Preg::replace('/^\(*\??\\\?/', '', $type); $typeExpression->sortTypes( function (TypeExpression $a, TypeExpression $b) use ($normalizeType): int { @@ -187,7 +206,7 @@ function (TypeExpression $a, TypeExpression $b) use ($normalizeType): int { } if ('alpha' === $this->configuration['sort_algorithm']) { - return strcasecmp($a, $b); + return true === $this->configuration['case_sensitive'] ? $a <=> $b : strcasecmp($a, $b); } return 0; @@ -196,11 +215,4 @@ function (TypeExpression $a, TypeExpression $b) use ($normalizeType): int { return $typeExpression->getTypes(); } - - private function sortJoinedTypes(string $types): string - { - $typeExpression = new TypeExpression($types, null, []); - - return implode('|', $this->sortTypes($typeExpression)); - } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php index 80200cf9ba..712b07283b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\DocBlock\TypeExpression; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -30,9 +31,6 @@ */ final class PhpdocVarWithoutNameFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -65,17 +63,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_TRAIT]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -122,17 +114,17 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void private function fixLine(Line $line): void { - $content = $line->getContent(); + Preg::matchAll('/ \$'.TypeExpression::REGEX_IDENTIFIER.'(?getContent(), $matches); - Preg::matchAll('/ \$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $content, $matches); - - if (isset($matches[0][0])) { - $line->setContent(str_replace($matches[0][0], '', $content)); + if (isset($matches[0])) { + foreach ($matches[0] as $match) { + $line->setContent(str_replace($match, '', $line->getContent())); + } } } /** - * @return Line[] + * @return array */ private function getFirstLevelLines(DocBlock $docBlock): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php index 7656f950a0..aba3072c59 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/NoUselessReturnFixer.php @@ -22,17 +22,11 @@ final class NoUselessReturnFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_FUNCTION, T_RETURN]); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -55,17 +49,14 @@ function example($b) { /** * {@inheritdoc} * - * Must run before BlankLineBeforeStatementFixer, NoExtraBlankLinesFixer, NoWhitespaceInBlankLineFixer, SingleLineCommentStyleFixer. - * Must run after NoEmptyStatementFixer, NoUnneededCurlyBracesFixer, NoUselessElseFixer, SimplifiedNullReturnFixer. + * Must run before BlankLineBeforeStatementFixer, NoExtraBlankLinesFixer, NoWhitespaceInBlankLineFixer, SingleLineCommentStyleFixer, SingleLineEmptyBodyFixer. + * Must run after NoEmptyStatementFixer, NoUnneededBracesFixer, NoUnneededCurlyBracesFixer, NoUselessElseFixer, SimplifiedNullReturnFixer. */ public function getPriority(): int { return -18; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php index b4a673b013..1e883382b0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php @@ -25,14 +25,8 @@ final class ReturnAssignmentFixer extends AbstractFixer { - /** - * @var TokensAnalyzer - */ - private $tokensAnalyzer; + private TokensAnalyzer $tokensAnalyzer; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,24 +39,18 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before BlankLineBeforeStatementFixer. - * Must run after NoEmptyStatementFixer, NoUnneededCurlyBracesFixer. + * Must run after NoEmptyStatementFixer, NoUnneededBracesFixer, NoUnneededCurlyBracesFixer. */ public function getPriority(): int { return -15; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAllTokenKindsFound([T_FUNCTION, T_RETURN, T_VARIABLE]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $tokenCount = \count($tokens); @@ -73,6 +61,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } + $next = $tokens->getNextMeaningfulToken($index); + if ($tokens[$next]->isGivenKind(CT::T_RETURN_REF)) { + continue; + } + $functionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']); if ($tokens[$functionOpenIndex]->equals(';')) { // abstract function $index = $functionOpenIndex - 1; @@ -91,10 +84,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $functionCloseIndex ); + $functionCloseIndex += $tokensAdded; $totalTokensAdded += $tokensAdded; } while ($tokensAdded > 0); - $index = $functionCloseIndex + $totalTokensAdded; + $index = $functionCloseIndex; $tokenCount += $totalTokensAdded; } } @@ -116,13 +110,16 @@ private function fixFunction(Tokens $tokens, int $functionIndex, int $functionOp T_INCLUDE_ONCE, // " T_REQUIRE, // " T_REQUIRE_ONCE, // " - T_STATIC, ]; $inserted = 0; $candidates = []; $isRisky = false; + if ($tokens[$tokens->getNextMeaningfulToken($functionIndex)]->isGivenKind(CT::T_RETURN_REF)) { + $isRisky = true; + } + // go through the function declaration and check if references are passed // - check if it will be risky to fix return statements of this function for ($index = $functionIndex + 1; $index < $functionOpenIndex; ++$index) { @@ -186,6 +183,16 @@ private function fixFunction(Tokens $tokens, int $functionIndex, int $functionOp continue; } + if ($tokens[$index]->isGivenKind(T_STATIC)) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + + if (!$tokens[$nextIndex]->isGivenKind(T_FUNCTION)) { + $isRisky = true; // "static $a" case + + continue; + } + } + if ($tokens[$index]->equals('$')) { $nextIndex = $tokens->getNextMeaningfulToken($index); if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { @@ -240,10 +247,20 @@ private function fixFunction(Tokens $tokens, int $functionIndex, int $functionOp $assignVarOperatorIndex = $tokens->getPrevTokenOfKind( $assignVarEndIndex, - ['=', ';', '{', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]] + ['=', ';', '{', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]] ); - if (null === $assignVarOperatorIndex || !$tokens[$assignVarOperatorIndex]->equals('=')) { + if ($tokens[$assignVarOperatorIndex]->equals('}')) { + $startIndex = $this->isCloseBracePartOfDefinition($tokens, $assignVarOperatorIndex); // test for `anonymous class`, `lambda` and `match` + + if (null === $startIndex) { + continue; + } + + $assignVarOperatorIndex = $tokens->getPrevMeaningfulToken($startIndex); + } + + if (!$tokens[$assignVarOperatorIndex]->equals('=')) { continue; } @@ -259,6 +276,11 @@ private function fixFunction(Tokens $tokens, int $functionIndex, int $functionOp continue; } + // Check if there is a `catch` or `finally` block between the assignment and the return + if ($this->isUsedInCatchOrFinally($tokens, $returnVarIndex, $functionOpenIndex, $functionCloseIndex)) { + continue; + } + // Note: here we are @ "[;{}] $a = [^;{] ; return $a;" $inserted += $this->simplifyReturnStatement( $tokens, @@ -285,8 +307,7 @@ private function simplifyReturnStatement( $inserted = 0; $originalIndent = $tokens[$assignVarIndex - 1]->isWhitespace() ? $tokens[$assignVarIndex - 1]->getContent() - : null - ; + : null; // remove the return statement if ($tokens[$returnVarEndIndex]->equals(';')) { // do not remove PHP close tags @@ -303,8 +324,7 @@ private function simplifyReturnStatement( $fistLinebreakPos = strrpos($content, "\n"); $content = false === $fistLinebreakPos ? ' ' - : substr($content, $fistLinebreakPos) - ; + : substr($content, $fistLinebreakPos); $tokens[$returnIndex - 1] = new Token([T_WHITESPACE, $content]); } @@ -349,4 +369,159 @@ private function clearIfSave(Tokens $tokens, int $index): void $tokens->clearTokenAndMergeSurroundingWhitespace($index); } + + /** + * @param int $index open brace index + * + * @return null|int index of the first token of a definition (lambda, anonymous class or match) or `null` if not an anonymous + */ + private function isCloseBracePartOfDefinition(Tokens $tokens, int $index): ?int + { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + $candidateIndex = $this->isOpenBraceOfLambda($tokens, $index); + + if (null !== $candidateIndex) { + return $candidateIndex; + } + + $candidateIndex = $this->isOpenBraceOfAnonymousClass($tokens, $index); + + return $candidateIndex ?? $this->isOpenBraceOfMatch($tokens, $index); + } + + /** + * @param int $index open brace index + * + * @return null|int index of T_NEW of anonymous class or `null` if not an anonymous + */ + private function isOpenBraceOfAnonymousClass(Tokens $tokens, int $index): ?int + { + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while ($tokens[$index]->equalsAny([',', [T_STRING], [T_IMPLEMENTS], [T_EXTENDS], [T_NS_SEPARATOR]])); + + if ($tokens[$index]->equals(')')) { // skip constructor braces and content within + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->tokensAnalyzer->isAnonymousClass($index)) { + return null; + } + + return $tokens->getPrevTokenOfKind($index, [[T_NEW]]); + } + + /** + * @param int $index open brace index + * + * @return null|int index of T_FUNCTION or T_STATIC of lambda or `null` if not a lambda + */ + private function isOpenBraceOfLambda(Tokens $tokens, int $index): ?int + { + $index = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$index]->equals(')')) { + return null; + } + + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$index]->isGivenKind(CT::T_USE_LAMBDA)) { + $index = $tokens->getPrevTokenOfKind($index, [')']); + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + } + + if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) { + $index = $tokens->getPrevMeaningfulToken($index); + } + + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + return null; + } + + $staticCandidate = $tokens->getPrevMeaningfulToken($index); + + return $tokens[$staticCandidate]->isGivenKind(T_STATIC) ? $staticCandidate : $index; + } + + /** + * @param int $index open brace index + * + * @return null|int index of T_MATCH or `null` if not a `match` + */ + private function isOpenBraceOfMatch(Tokens $tokens, int $index): ?int + { + if (!\defined('T_MATCH') || !$tokens->isTokenKindFound(T_MATCH)) { // @TODO: drop condition when PHP 8.0+ is required + return null; + } + + $index = $tokens->getPrevMeaningfulToken($index); + + if (!$tokens[$index]->equals(')')) { + return null; + } + + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getPrevMeaningfulToken($index); + + return $tokens[$index]->isGivenKind(T_MATCH) ? $index : null; + } + + private function isUsedInCatchOrFinally(Tokens $tokens, int $returnVarIndex, int $functionOpenIndex, int $functionCloseIndex): bool + { + // Find try + $tryIndex = $tokens->getPrevTokenOfKind($returnVarIndex, [[T_TRY]]); + if (null === $tryIndex || $tryIndex <= $functionOpenIndex) { + return false; + } + $tryOpenIndex = $tokens->getNextTokenOfKind($tryIndex, ['{']); + $tryCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $tryOpenIndex); + + // Find catch or finally + $nextIndex = $tokens->getNextMeaningfulToken($tryCloseIndex); + if (null === $nextIndex) { + return false; + } + + // Find catches + while ($tokens[$nextIndex]->isGivenKind(T_CATCH)) { + $catchOpenIndex = $tokens->getNextTokenOfKind($nextIndex, ['{']); + $catchCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $catchOpenIndex); + + if ($catchCloseIndex >= $functionCloseIndex) { + return false; + } + $varIndex = $tokens->getNextTokenOfKind($catchOpenIndex, [$tokens[$returnVarIndex]]); + // Check if the variable is used in the finally block + if (null !== $varIndex && $varIndex < $catchCloseIndex) { + return true; + } + + $nextIndex = $tokens->getNextMeaningfulToken($catchCloseIndex); + if (null === $nextIndex) { + return false; + } + } + + if (!$tokens[$nextIndex]->isGivenKind(T_FINALLY)) { + return false; + } + + $finallyIndex = $nextIndex; + if ($finallyIndex >= $functionCloseIndex) { + return false; + } + $finallyOpenIndex = $tokens->getNextTokenOfKind($finallyIndex, ['{']); + $finallyCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $finallyOpenIndex); + $varIndex = $tokens->getNextTokenOfKind($finallyOpenIndex, [$tokens[$returnVarIndex]]); + // Check if the variable is used in the finally block + if (null !== $varIndex && $varIndex < $finallyCloseIndex) { + return true; + } + + return false; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php index 800c21c13a..38d1339330 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php @@ -26,9 +26,6 @@ */ final class SimplifiedNullReturnFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,13 +34,13 @@ public function getDefinition(): FixerDefinitionInterface new CodeSample("isTokenKindFound(T_RETURN); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -88,7 +79,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void */ private function clear(Tokens $tokens, int $index): void { - while (!$tokens[++$index]->equals(';')) { + while (!$tokens[++$index]->equalsAny([';', [T_CLOSE_TAG]])) { if ($this->shouldClearToken($tokens, $index)) { $tokens->clearAt($index); } @@ -105,13 +96,16 @@ private function needFixing(Tokens $tokens, int $index): bool } $content = ''; - while (!$tokens[$index]->equals(';')) { + while (!$tokens[$index]->equalsAny([';', [T_CLOSE_TAG]])) { $index = $tokens->getNextMeaningfulToken($index); $content .= $tokens[$index]->getContent(); } + $lastTokenContent = $tokens[$index]->getContent(); + $content = substr($content, 0, -\strlen($lastTokenContent)); + $content = ltrim($content, '('); - $content = rtrim($content, ');'); + $content = rtrim($content, ')'); return 'null' === strtolower($content); } @@ -134,7 +128,8 @@ private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, int $retur } while ($closingCurlyBraceIndex < $returnIndex); $possibleVoidIndex = $tokens->getPrevMeaningfulToken($openingCurlyBraceIndex); - $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind(T_STRING) && 'void' !== $tokens[$possibleVoidIndex]->getContent(); + $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind([T_STRING, CT::T_ARRAY_TYPEHINT]) + && 'void' !== $tokens[$possibleVoidIndex]->getContent(); $nullableTypeIndex = $tokens->getNextTokenOfKind($functionIndex, [[CT::T_NULLABLE_TYPE]]); $isNullableReturnType = null !== $nullableTypeIndex && $nullableTypeIndex < $openingCurlyBraceIndex; @@ -145,13 +140,32 @@ private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, int $retur /** * Should we clear the specific token? * - * If the token is a comment, or is whitespace that is immediately before a - * comment, then we'll leave it alone. + * We'll leave it alone if + * - token is a comment + * - token is whitespace that is immediately before a comment + * - token is whitespace that is immediately before the PHP close tag + * - token is whitespace that is immediately after a comment and before a semicolon */ private function shouldClearToken(Tokens $tokens, int $index): bool { $token = $tokens[$index]; - return !$token->isComment() && !($token->isWhitespace() && $tokens[$index + 1]->isComment()); + if ($token->isComment()) { + return false; + } + + if (!$token->isWhitespace()) { + return true; + } + + if ( + $tokens[$index + 1]->isComment() + || $tokens[$index + 1]->equals([T_CLOSE_TAG]) + || ($tokens[$index - 1]->isComment() && $tokens[$index + 1]->equals(';')) + ) { + return false; + } + + return true; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php index 919f2df338..3ea38cdf58 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/MultilineWhitespaceBeforeSemicolonsFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -23,16 +24,28 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Graham Campbell * @author Egidijus Girčys + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * strategy?: 'new_line_for_chained_calls'|'no_multi_line' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * strategy: 'new_line_for_chained_calls'|'no_multi_line' + * } */ final class MultilineWhitespaceBeforeSemicolonsFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** * @internal */ @@ -43,9 +56,6 @@ final class MultilineWhitespaceBeforeSemicolonsFixer extends AbstractFixer imple */ public const STRATEGY_NEW_LINE_FOR_CHAINED_CALLS = 'new_line_for_chained_calls'; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,7 +63,7 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( 'method1() - ->method2() - ->method(3); - ?> +$object->method1() + ->method2() + ->method(3); ', ['strategy' => self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS] ), @@ -83,17 +92,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(';'); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -107,80 +110,66 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void - { - if (self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS === $this->configuration['strategy']) { - $this->applyChainedCallsFix($tokens); - - return; - } - - if (self::STRATEGY_NO_MULTI_LINE === $this->configuration['strategy']) { - $this->applyNoMultiLineFix($tokens); - } - } - - private function applyNoMultiLineFix(Tokens $tokens): void { $lineEnding = $this->whitespacesConfig->getLineEnding(); - foreach ($tokens as $index => $token) { - if (!$token->equals(';')) { + for ($index = 0, $count = \count($tokens); $index < $count; ++$index) { + if (!$tokens[$index]->equals(';')) { continue; } $previousIndex = $index - 1; $previous = $tokens[$previousIndex]; - if (!$previous->isWhitespace() || !str_contains($previous->getContent(), "\n")) { - continue; - } - $content = $previous->getContent(); - if (str_starts_with($content, $lineEnding) && $tokens[$index - 2]->isComment()) { - $tokens->ensureWhitespaceAtIndex($previousIndex, 0, $lineEnding); - } else { - $tokens->clearAt($previousIndex); - } - } - } + $indent = $this->findWhitespaceBeforeFirstCall($index, $tokens); + if (self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS === $this->configuration['strategy'] && null !== $indent) { + if ($previous->isWhitespace() && $previous->getContent() === $lineEnding.$indent) { + continue; + } - private function applyChainedCallsFix(Tokens $tokens): void - { - for ($index = \count($tokens) - 1; $index >= 0; --$index) { - // continue if token is not a semicolon - if (!$tokens[$index]->equals(';')) { - continue; - } + // unset whitespace and semicolon + if ($previous->isWhitespace()) { + $tokens->clearAt($previousIndex); + } + $tokens->clearAt($index); - // get the indent of the chained call, null in case it's not a chained call - $indent = $this->findWhitespaceBeforeFirstCall($index - 1, $tokens); + // find the line ending token index after the semicolon + $index = $this->getNewLineIndex($index, $tokens); - if (null === $indent) { - continue; - } + // appended new line to the last method call + $newline = new Token([T_WHITESPACE, $lineEnding.$indent]); - // unset semicolon - $tokens->clearAt($index); + // insert the new line with indented semicolon + $tokens->insertAt($index++, [$newline, new Token(';')]); + } else { + if (!$previous->isWhitespace() || !str_contains($previous->getContent(), "\n")) { + continue; + } - // find the line ending token index after the semicolon - $index = $this->getNewLineIndex($index, $tokens); + $content = $previous->getContent(); + if (str_starts_with($content, $lineEnding) && $tokens[$index - 2]->isComment()) { + // if there is comment between closing parenthesis and semicolon - // line ending string of the last method call - $lineEnding = $this->whitespacesConfig->getLineEnding(); + // unset whitespace and semicolon + $tokens->clearAt($previousIndex); + $tokens->clearAt($index); - // appended new line to the last method call - $newline = new Token([T_WHITESPACE, $lineEnding.$indent]); + // find the significant token index before the semicolon + $significantTokenIndex = $this->getPreviousSignificantTokenIndex($index, $tokens); - // insert the new line with indented semicolon - $tokens->insertAt($index, [$newline, new Token(';')]); + // insert the semicolon + $tokens->insertAt($significantTokenIndex + 1, [new Token(';')]); + } else { + // if there is whitespace between closing bracket and semicolon, just remove it + $tokens->clearAt($previousIndex); + } + } } } /** - * Find the index for the new line. Return the given index when there's no new line. + * Find the index for the next new line. Return the given index when there's no new line. */ private function getNewLineIndex(int $index, Tokens $tokens): int { @@ -196,7 +185,28 @@ private function getNewLineIndex(int $index, Tokens $tokens): int } /** - * Checks if the semicolon closes a chained call and returns the whitespace of the first call at $index. + * Find the index for the previous significant token. Return the given index when there's no significant token. + */ + private function getPreviousSignificantTokenIndex(int $index, Tokens $tokens): int + { + $stopTokens = [ + T_LNUMBER, + T_DNUMBER, + T_STRING, + T_VARIABLE, + T_CONSTANT_ENCAPSED_STRING, + ]; + for ($index; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind($stopTokens) || $tokens[$index]->equals(')')) { + return $index; + } + } + + return $index; + } + + /** + * Checks if the semicolon closes a multiline call and returns the whitespace of the first call at $index. * i.e. it will return the whitespace marked with '____' in the example underneath. * * .. @@ -206,90 +216,26 @@ private function getNewLineIndex(int $index, Tokens $tokens): int */ private function findWhitespaceBeforeFirstCall(int $index, Tokens $tokens): ?string { - // semicolon followed by a closing bracket? - if (!$tokens[$index]->equals(')')) { - return null; - } - - // find opening bracket - $openingBrackets = 1; - for (--$index; $index > 0; --$index) { - if ($tokens[$index]->equals(')')) { - ++$openingBrackets; - - continue; - } - - if ($tokens[$index]->equals('(')) { - if (1 === $openingBrackets) { - break; - } - --$openingBrackets; - } - } - - // method name - if (!$tokens[--$index]->isGivenKind(T_STRING)) { - return null; - } - - // ->, ?-> or :: - if (!$tokens[--$index]->isObjectOperator() && !$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { - return null; - } + $isMultilineCall = false; + $prevIndex = $tokens->getPrevMeaningfulToken($index); - // white space - if (!$tokens[--$index]->isGivenKind(T_WHITESPACE)) { - return null; - } + while (!$tokens[$prevIndex]->equalsAny([';', ':', '{', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO], [T_ELSE]])) { + $index = $prevIndex; + $prevIndex = $tokens->getPrevMeaningfulToken($index); - $closingBrackets = 0; - for ($index; $index >= 0; --$index) { - if ($tokens[$index]->equals(')')) { - ++$closingBrackets; - } + $blockType = Tokens::detectBlockType($tokens[$index]); + if (null !== $blockType && !$blockType['isStart']) { + $prevIndex = $tokens->findBlockStart($blockType['type'], $index); - if ($tokens[$index]->equals('(')) { - --$closingBrackets; + continue; } - // must be the variable of the first call in the chain - if ($tokens[$index]->isGivenKind([T_VARIABLE, T_RETURN, T_STRING]) && 0 === $closingBrackets) { - if ($tokens[--$index]->isGivenKind(T_WHITESPACE) - || $tokens[$index]->isGivenKind(T_OPEN_TAG)) { - return $this->getIndentAt($tokens, $index); - } + if ($tokens[$index]->isObjectOperator() || $tokens[$index]->isGivenKind(T_DOUBLE_COLON)) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + $isMultilineCall |= $tokens->isPartialCodeMultiline($prevIndex, $index); } } - return null; - } - - private function getIndentAt(Tokens $tokens, int $index): ?string - { - $content = ''; - $lineEnding = $this->whitespacesConfig->getLineEnding(); - - // find line ending token - for ($index; $index > 0; --$index) { - if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { - break; - } - } - - if ($tokens[$index]->isWhitespace()) { - $content = $tokens[$index]->getContent(); - --$index; - } - - if ($tokens[$index]->isGivenKind(T_OPEN_TAG)) { - $content = $tokens[$index]->getContent().$content; - } - - if (1 === Preg::match('/\R{1}(\h*)$/', $content, $matches)) { - return $matches[1]; - } - - return null; + return $isMultilineCall ? WhitespacesAnalyzer::detectIndent($tokens, $index) : null; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php index bbd423e148..15c1e670df 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoEmptyStatementFixer.php @@ -26,9 +26,6 @@ */ final class NoEmptyStatementFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,7 +41,7 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BracesFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, MultilineWhitespaceBeforeSemicolonsFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoUselessElseFixer, NoUselessReturnFixer, NoWhitespaceInBlankLineFixer, ReturnAssignmentFixer, SpaceAfterSemicolonFixer, SwitchCaseSemicolonToColonFixer. + * Must run before BracesFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, MultilineWhitespaceBeforeSemicolonsFixer, NoExtraBlankLinesFixer, NoMultipleStatementsPerLineFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoUselessElseFixer, NoUselessReturnFixer, NoWhitespaceInBlankLineFixer, ReturnAssignmentFixer, SpaceAfterSemicolonFixer, SwitchCaseSemicolonToColonFixer. * Must run after NoUselessSprintfFixer. */ public function getPriority(): int @@ -52,17 +49,11 @@ public function getPriority(): int return 40; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(';'); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php index c3f0d13545..f06d9d7357 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/NoSinglelineWhitespaceBeforeSemicolonsFixer.php @@ -25,9 +25,6 @@ */ final class NoSinglelineWhitespaceBeforeSemicolonsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -39,24 +36,18 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run after CombineConsecutiveIssetsFixer, FunctionToConstantFixer, NoEmptyStatementFixer, NoUnneededImportAliasFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer. + * Must run after CombineConsecutiveIssetsFixer, FunctionToConstantFixer, LongToShorthandOperatorFixer, NoEmptyStatementFixer, NoUnneededImportAliasFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer. */ public function getPriority(): int { return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(';'); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php index c9a9131f73..2fe9ab07de 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php @@ -23,9 +23,6 @@ final class SemicolonAfterInstructionFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -44,17 +41,11 @@ public function getPriority(): int return 2; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_CLOSE_TAG); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; $index > 1; --$index) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php index 799ee5a83d..f8e4084ea9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -26,11 +27,21 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * remove_in_empty_for_expressions?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * remove_in_empty_for_expressions: bool + * } + */ final class SpaceAfterSemicolonFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -60,17 +71,11 @@ public function getPriority(): int return -1; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(';'); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -81,9 +86,6 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $insideForParenthesesUntil = null; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php index 76e26b4738..7e8172c24f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/DeclareStrictTypesFixer.php @@ -27,9 +27,6 @@ */ final class DeclareStrictTypesFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -54,38 +51,23 @@ public function getPriority(): int return 2; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return isset($tokens[0]) && $tokens[0]->isGivenKind(T_OPEN_TAG); + return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - // check if the declaration is already done - $searchIndex = $tokens->getNextMeaningfulToken(0); - if (null === $searchIndex) { - $this->insertSequence($tokens); // declaration not found, insert one + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0; - return; - } - - $sequenceLocation = $tokens->findSequence([[T_DECLARE, 'declare'], '(', [T_STRING, 'strict_types'], '=', [T_LNUMBER], ')'], $searchIndex, null, false); + $sequenceLocation = $tokens->findSequence([[T_DECLARE, 'declare'], '(', [T_STRING, 'strict_types'], '=', [T_LNUMBER], ')'], $openTagIndex, null, false); if (null === $sequenceLocation) { - $this->insertSequence($tokens); // declaration not found, insert one + $this->insertSequence($openTagIndex, $tokens); // declaration not found, insert one return; } @@ -114,7 +96,7 @@ private function fixStrictTypesCasingAndValue(Tokens $tokens, array $sequence): } } - private function insertSequence(Tokens $tokens): void + private function insertSequence(int $openTagIndex, Tokens $tokens): void { $sequence = [ new Token([T_DECLARE, 'declare']), @@ -125,28 +107,26 @@ private function insertSequence(Tokens $tokens): void new Token(')'), new Token(';'), ]; - $endIndex = \count($sequence); + $nextIndex = $openTagIndex + \count($sequence) + 1; - $tokens->insertAt(1, $sequence); + $tokens->insertAt($openTagIndex + 1, $sequence); - // start index of the sequence is always 1 here, 0 is always open tag - // transform "getContent(), "\n")) { - $tokens[0] = new Token([$tokens[0]->getId(), trim($tokens[0]->getContent()).' ']); + // transform "getContent(); + if (!str_contains($content, ' ') || str_contains($content, "\n")) { + $tokens[$openTagIndex] = new Token([$tokens[$openTagIndex]->getId(), trim($tokens[$openTagIndex]->getContent()).' ']); } - if ($endIndex === \count($tokens) - 1) { + if (\count($tokens) === $nextIndex) { return; // no more tokens after sequence, single_blank_line_at_eof might add a line } $lineEnding = $this->whitespacesConfig->getLineEnding(); - if (!$tokens[1 + $endIndex]->isWhitespace()) { - $tokens->insertAt(1 + $endIndex, new Token([T_WHITESPACE, $lineEnding])); - - return; + if ($tokens[$nextIndex]->isWhitespace()) { + $content = $tokens[$nextIndex]->getContent(); + $tokens[$nextIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]); + } else { + $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, $lineEnding])); } - - $content = $tokens[1 + $endIndex]->getContent(); - $tokens[1 + $endIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php index b03ff312b2..18222c5972 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictComparisonFixer.php @@ -39,32 +39,23 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before BinaryOperatorSpacesFixer. + * Must run before BinaryOperatorSpacesFixer, ModernizeStrposFixer. */ public function getPriority(): int { - return 0; + return 38; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_IS_EQUAL, T_IS_NOT_EQUAL]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { static $map = [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php index fd269421cd..e0f12f3330 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Strict/StrictParamFixer.php @@ -28,9 +28,6 @@ */ final class StrictParamFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -41,17 +38,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_STRING); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; @@ -67,9 +58,6 @@ public function getPriority(): int return 31; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $functionsAnalyzer = new FunctionsAnalyzer(); @@ -103,6 +91,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } + /** + * @param list $functionParams + */ private function fixFunction(Tokens $tokens, int $functionIndex, array $functionParams): void { $startBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); @@ -147,7 +138,7 @@ private function fixFunction(Tokens $tokens, int $functionIndex, array $function for ($i = $paramsQuantity; $i < $functionParamsQuantity; ++$i) { // function call do not have all params that are required to set useStrict flag, exit from method! - if (!$functionParams[$i]) { + if (null === $functionParams[$i]) { return; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php index d05e5139f9..81a53b1770 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/EscapeImplicitBackslashesFixer.php @@ -14,40 +14,60 @@ namespace PhpCsFixer\Fixer\StringNotation; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\Token; -use PhpCsFixer\Tokenizer\Tokens; /** * @author Filippo Tessarotto + * @author Michael Vorisek + * + * @deprecated Use `string_implicit_backslashes` with config: ['single_quoted' => 'ignore', 'double_quoted' => 'escape', 'heredoc' => 'escape'] (default) + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * double_quoted?: bool, + * heredoc_syntax?: bool, + * single_quoted?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * double_quoted: bool, + * heredoc_syntax: bool, + * single_quoted: bool + * } */ -final class EscapeImplicitBackslashesFixer extends AbstractFixer implements ConfigurableFixerInterface +final class EscapeImplicitBackslashesFixer extends AbstractProxyFixer implements ConfigurableFixerInterface, DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getSuccessorsNames(): array + { + return array_keys($this->proxyFixers); + } + public function getDefinition(): FixerDefinitionInterface { $codeSample = <<<'EOF' -isAnyTokenKindsFound([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]); - } - /** * {@inheritdoc} * * Must run before HeredocToNowdocFixer, SingleQuoteFixer. - * Must run after BacktickToShellExecFixer. + * Must run after BacktickToShellExecFixer, MultilineStringToHeredocFixer. */ public function getPriority(): int { - return 1; + return parent::getPriority(); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function configurePostNormalisation(): void { - static $singleQuotedRegex = '/(? $token) { - $content = $token->getContent(); - if ($token->equalsAny(['"', 'b"', 'B"'])) { - $doubleQuoteOpened = !$doubleQuoteOpened; - } - if (!$token->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]) || !str_contains($content, '\\')) { - continue; - } - - // Nowdoc syntax - if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && '\'' === substr(rtrim($tokens[$index - 1]->getContent()), -1)) { - continue; - } - - $firstTwoCharacters = strtolower(substr($content, 0, 2)); - $isSingleQuotedString = $token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('\'' === $content[0] || 'b\'' === $firstTwoCharacters); - $isDoubleQuotedString = - ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('"' === $content[0] || 'b"' === $firstTwoCharacters)) - || ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && $doubleQuoteOpened) - ; - $isHeredocSyntax = !$isSingleQuotedString && !$isDoubleQuotedString; - if ( - (false === $this->configuration['single_quoted'] && $isSingleQuotedString) - || (false === $this->configuration['double_quoted'] && $isDoubleQuotedString) - || (false === $this->configuration['heredoc_syntax'] && $isHeredocSyntax) - ) { - continue; - } - - $regex = $heredocSyntaxRegex; - if ($isSingleQuotedString) { - $regex = $singleQuotedRegex; - } elseif ($isDoubleQuotedString) { - $regex = $doubleQuotedRegex; - } - - $newContent = Preg::replace($regex, '\\\\\\\\$1', $content); - if ($newContent !== $content) { - $tokens[$index] = new Token([$token->getId(), $newContent]); - } - } + /** @var StringImplicitBackslashesFixer */ + $stringImplicitBackslashesFixer = $this->proxyFixers['string_implicit_backslashes']; + + $stringImplicitBackslashesFixer->configure([ + 'single_quoted' => true === $this->configuration['single_quoted'] ? 'escape' : 'ignore', + 'double_quoted' => true === $this->configuration['double_quoted'] ? 'escape' : 'ignore', + 'heredoc' => true === $this->configuration['heredoc_syntax'] ? 'escape' : 'ignore', + ]); + } + + protected function createProxyFixers(): array + { + $stringImplicitBackslashesFixer = new StringImplicitBackslashesFixer(); + $stringImplicitBackslashesFixer->configure([ + 'single_quoted' => 'ignore', + 'double_quoted' => 'escape', + 'heredoc' => 'escape', + ]); + + return [ + $stringImplicitBackslashesFixer, + ]; } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php index 2c74756b6d..1671940c87 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/ExplicitStringVariableFixer.php @@ -27,58 +27,50 @@ */ final class ExplicitStringVariableFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax.', [new CodeSample( <<<'EOT' -country !"; -$c = "I have $farm[0] chickens !"; + country !"; + $c = "I have $farm[0] chickens !"; -EOT + EOT )], 'The reasoning behind this rule is the following:' - ."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow' - ."\n".'- PHP manual marks `"$var"` syntax as implicit and `"${var}"` syntax as explicit: explicit code should always be preferred' - ."\n".'- Explicit syntax allows word concatenation inside strings, e.g. `"${var}IsAVar"`, implicit doesn\'t' - ."\n".'- Explicit syntax is easier to detect for IDE/editors and therefore has colors/highlight with higher contrast, which is easier to read' - ."\n".'Backtick operator is skipped because it is harder to handle; you can use `backtick_to_shell_exec` fixer to normalize backticks to strings' + ."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow.' + ."\n".'- PHP manual marks `"$var"` syntax as implicit and `"{$var}"` syntax as explicit: explicit code should always be preferred.' + ."\n".'- Explicit syntax allows word concatenation inside strings, e.g. `"{$var}IsAVar"`, implicit doesn\'t.' + ."\n".'- Explicit syntax is easier to detect for IDE/editors and therefore has colors/highlight with higher contrast, which is easier to read.' + ."\n".'Backtick operator is skipped because it is harder to handle; you can use `backtick_to_shell_exec` fixer to normalize backticks to strings.' ); } /** * {@inheritdoc} * - * Must run before SimpleToComplexStringVariableFixer. + * Must run before NoUselessConcatOperatorFixer. * Must run after BacktickToShellExecFixer. */ public function getPriority(): int { - return 0; + return 6; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_VARIABLE); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $backtickStarted = false; for ($index = \count($tokens) - 1; $index > 0; --$index) { $token = $tokens[$index]; + if ($token->equals('`')) { $backtickStarted = !$backtickStarted; @@ -90,6 +82,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $prevToken = $tokens[$index - 1]; + if (!$this->isStringPartToken($prevToken)) { continue; } @@ -105,6 +98,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $nextIndex = $index + 1; $squareBracketCount = 0; + while (!$this->isStringPartToken($tokens[$nextIndex])) { if ($tokens[$nextIndex]->isGivenKind(T_CURLY_OPEN)) { $nextIndex = $tokens->getNextTokenOfKind($nextIndex, [[CT::T_CURLY_CLOSE]]); @@ -130,12 +124,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void foreach ($variableTokens as $distinctVariableSet) { if (1 === \count($distinctVariableSet['tokens'])) { - $singleVariableIndex = key($distinctVariableSet['tokens']); + $singleVariableIndex = array_key_first($distinctVariableSet['tokens']); $singleVariableToken = current($distinctVariableSet['tokens']); $tokens->overrideRange($singleVariableIndex, $singleVariableIndex, [ - new Token([T_DOLLAR_OPEN_CURLY_BRACES, '${']), - new Token([T_STRING_VARNAME, substr($singleVariableToken->getContent(), 1)]), - new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']), + new Token([T_CURLY_OPEN, '{']), + new Token([T_VARIABLE, $singleVariableToken->getContent()]), + new Token([CT::T_CURLY_CLOSE, '}']), ]); } else { foreach ($distinctVariableSet['tokens'] as $variablePartIndex => $variablePartToken) { @@ -167,7 +161,6 @@ private function isStringPartToken(Token $token): bool return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) || $token->isGivenKind(T_START_HEREDOC) || '"' === $token->getContent() - || 'b"' === strtolower($token->getContent()) - ; + || 'b"' === strtolower($token->getContent()); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocClosingMarkerFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocClosingMarkerFixer.php new file mode 100644 index 0000000000..dda5025d1d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocClosingMarkerFixer.php @@ -0,0 +1,205 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michael Vorisek + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * closing_marker?: string, + * explicit_heredoc_style?: bool, + * reserved_closing_markers?: list + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * closing_marker: string, + * explicit_heredoc_style: bool, + * reserved_closing_markers: list + * } + */ +final class HeredocClosingMarkerFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** + * @var list + */ + public const RESERVED_CLOSING_MARKERS = [ + 'CSS', + 'DIFF', + 'HTML', + 'JS', + 'JSON', + 'MD', + 'PHP', + 'PYTHON', + 'RST', + 'TS', + 'SQL', + 'XML', + 'YAML', + ]; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Unify `heredoc` or `nowdoc` closing marker.', + [ + new CodeSample( + <<<'EOD' + 'EOF'] + ), + new CodeSample( + <<<'EOD_' + true] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_START_HEREDOC); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder( + 'closing_marker', + 'Preferred closing marker.' + )) + ->setAllowedTypes(['string']) + ->setDefault('EOD') + ->getOption(), + (new FixerOptionBuilder( + 'reserved_closing_markers', + 'Reserved closing markers to be kept unchanged.' + )) + ->setAllowedTypes(['string[]']) + ->setDefault(self::RESERVED_CLOSING_MARKERS) + ->getOption(), + (new FixerOptionBuilder( + 'explicit_heredoc_style', + 'Whether the closing marker should be wrapped in double quotes.' + )) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $reservedClosingMarkersMap = null; + + $startIndex = null; + foreach ($tokens as $index => $token) { + if ($token->isGivenKind(T_START_HEREDOC)) { + $startIndex = $index; + + continue; + } + + if (null !== $startIndex && $token->isGivenKind(T_END_HEREDOC)) { + $existingClosingMarker = trim($token->getContent()); + + if (null === $reservedClosingMarkersMap) { + $reservedClosingMarkersMap = []; + foreach ($this->configuration['reserved_closing_markers'] as $v) { + $reservedClosingMarkersMap[mb_strtoupper($v)] = $v; + } + } + + $existingClosingMarker = mb_strtoupper($existingClosingMarker); + do { + $newClosingMarker = $reservedClosingMarkersMap[$existingClosingMarker] ?? null; + if (!str_ends_with($existingClosingMarker, '_')) { + break; + } + $existingClosingMarker = substr($existingClosingMarker, 0, -1); + } while (null === $newClosingMarker); + + if (null === $newClosingMarker) { + $newClosingMarker = $this->configuration['closing_marker']; + } + + $content = $tokens->generatePartialCode($startIndex + 1, $index - 1); + while (Preg::match('~(^|[\r\n])\s*'.preg_quote($newClosingMarker, '~').'(?!\w)~', $content)) { + $newClosingMarker .= '_'; + } + + [$tokens[$startIndex], $tokens[$index]] = $this->convertClosingMarker($tokens[$startIndex], $token, $newClosingMarker); + + $startIndex = null; + + continue; + } + } + } + + /** + * @return array{Token, Token} + */ + private function convertClosingMarker(Token $startToken, Token $endToken, string $newClosingMarker): array + { + $isNowdoc = str_contains($startToken->getContent(), '\''); + + $markerQuote = $isNowdoc + ? '\'' + : (true === $this->configuration['explicit_heredoc_style'] ? '"' : ''); + + return [new Token([ + $startToken->getId(), + Preg::replace('/<<<\h*\K["\']?[^\s"\']+["\']?/', $markerQuote.$newClosingMarker.$markerQuote, $startToken->getContent()), + ]), new Token([ + $endToken->getId(), + Preg::replace('/\S+/', $newClosingMarker, $endToken->getContent()), + ])]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php index 185cc8bcaa..9c736c1420 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/HeredocToNowdocFixer.php @@ -27,9 +27,6 @@ */ final class HeredocToNowdocFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,11 +34,11 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( <<<'EOF' -isTokenKindFound(T_START_HEREDOC); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -90,12 +81,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $content = $tokens[$index + 1]->getContent(); // regex: odd number of backslashes, not followed by dollar - if (Preg::match('/(?convertToNowdoc($token); - $content = str_replace(['\\\\', '\\$'], ['\\', '$'], $content); + $content = str_replace(['\\\\', '\$'], ['\\', '$'], $content); $tokens[$index + 1] = new Token([ $tokens[$index + 1]->getId(), $content, diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/MultilineStringToHeredocFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/MultilineStringToHeredocFixer.php new file mode 100644 index 0000000000..1a6790e679 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/MultilineStringToHeredocFixer.php @@ -0,0 +1,166 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Michael Vorisek + */ +final class MultilineStringToHeredocFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Convert multiline string to `heredoc` or `nowdoc`.', + [ + new CodeSample( + <<<'EOD' + getName()}"; + EOD."\n" + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE]); + } + + /** + * {@inheritdoc} + * + * Must run before EscapeImplicitBackslashesFixer, HeredocIndentationFixer, StringImplicitBackslashesFixer. + */ + public function getPriority(): int + { + return 16; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $inHeredoc = false; + $complexStringStartIndex = null; + foreach ($tokens as $index => $token) { + if ($token->isGivenKind([T_START_HEREDOC, T_END_HEREDOC])) { + $inHeredoc = $token->isGivenKind(T_START_HEREDOC) || !$token->isGivenKind(T_END_HEREDOC); + + continue; + } + + if (null === $complexStringStartIndex) { + if ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $this->convertStringToHeredoc($tokens, $index, $index); + + // skip next 2 added tokens if replaced + if ($tokens[$index]->isGivenKind(T_START_HEREDOC)) { + $inHeredoc = true; + } + } elseif ($token->equalsAny(['"', 'b"', 'B"'])) { + $complexStringStartIndex = $index; + } + } elseif ($token->equals('"')) { + $this->convertStringToHeredoc($tokens, $complexStringStartIndex, $index); + + $complexStringStartIndex = null; + } + } + } + + private function convertStringToHeredoc(Tokens $tokens, int $stringStartIndex, int $stringEndIndex): void + { + $closingMarker = 'EOD'; + + if ($tokens[$stringStartIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + $content = $tokens[$stringStartIndex]->getContent(); + if ('b' === strtolower(substr($content, 0, 1))) { + $content = substr($content, 1); + } + $isSingleQuoted = str_starts_with($content, '\''); + $content = substr($content, 1, -1); + + if ($isSingleQuoted) { + $content = Preg::replace('~\\\([\\\\\'])~', '$1', $content); + } else { + $content = Preg::replace('~(\\\\\\\)|\\\(")~', '$1$2', $content); + } + + $constantStringToken = new Token([T_ENCAPSED_AND_WHITESPACE, $content."\n"]); + } else { + $content = $tokens->generatePartialCode($stringStartIndex + 1, $stringEndIndex - 1); + $isSingleQuoted = false; + $constantStringToken = null; + } + + if (!str_contains($content, "\n") && !str_contains($content, "\r")) { + return; + } + + while (Preg::match('~(^|[\r\n])\s*'.preg_quote($closingMarker, '~').'(?!\w)~', $content)) { + $closingMarker .= '_'; + } + + $quoting = $isSingleQuoted ? '\'' : ''; + $heredocStartToken = new Token([T_START_HEREDOC, '<<<'.$quoting.$closingMarker.$quoting."\n"]); + $heredocEndToken = new Token([T_END_HEREDOC, $closingMarker]); + + if (null !== $constantStringToken) { + $tokens->overrideRange($stringStartIndex, $stringEndIndex, [ + $heredocStartToken, + $constantStringToken, + $heredocEndToken, + ]); + } else { + for ($i = $stringStartIndex + 1; $i < $stringEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $tokens[$i] = new Token([ + $tokens[$i]->getId(), + Preg::replace('~(\\\\\\\)|\\\(")~', '$1$2', $tokens[$i]->getContent()), + ]); + } + } + + $tokens[$stringStartIndex] = $heredocStartToken; + $tokens[$stringEndIndex] = $heredocEndToken; + if ($tokens[$stringEndIndex - 1]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $tokens[$stringEndIndex - 1] = new Token([ + $tokens[$stringEndIndex - 1]->getId(), + $tokens[$stringEndIndex - 1]->getContent()."\n", + ]); + } else { + $tokens->insertAt($stringEndIndex, new Token([ + T_ENCAPSED_AND_WHITESPACE, + "\n", + ])); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php index c2b05c57fc..3c41ab5ba2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoBinaryStringFixer.php @@ -26,17 +26,17 @@ */ final class NoBinaryStringFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC]); + return $tokens->isAnyTokenKindsFound( + [ + T_CONSTANT_ENCAPSED_STRING, + T_START_HEREDOC, + 'b"', + ] + ); } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -50,18 +50,25 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} + * + * Must run before NoUselessConcatOperatorFixer, PhpUnitDedicateAssertInternalTypeFixer, RegularCallableCallFixer, SetTypeToCastFixer. */ + public function getPriority(): int + { + return 40; + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { - if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC])) { - continue; - } - - $content = $token->getContent(); + if ($token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC])) { + $content = $token->getContent(); - if ('b' === strtolower($content[0])) { - $tokens[$index] = new Token([$token->getId(), substr($content, 1)]); + if ('b' === strtolower($content[0])) { + $tokens[$index] = new Token([$token->getId(), substr($content, 1)]); + } + } elseif ($token->equals('b"')) { + $tokens[$index] = new Token('"'); } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php index 8ba97ff1b2..5e04607309 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php @@ -27,25 +27,16 @@ */ final class NoTrailingWhitespaceInStringFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -60,9 +51,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = $tokens->count() - 1, $last = true; $index >= 0; --$index, $last = false) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php index 53ccabae3b..9c6b4852b4 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SimpleToComplexStringVariableFixer.php @@ -27,9 +27,6 @@ */ final class SimpleToComplexStringVariableFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -37,21 +34,21 @@ public function getDefinition(): FixerDefinitionInterface [ new CodeSample( <<<'EOT' -isTokenKindFound(T_DOLLAR_OPEN_CURLY_BRACES); @@ -79,38 +73,24 @@ public function isCandidate(Tokens $tokens): bool protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 3; $index > 0; --$index) { - $token = $tokens[$index]; - - if (!$token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { + if (!$tokens[$index]->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) { continue; } - $varnameToken = $tokens[$index + 1]; if (!$varnameToken->isGivenKind(T_STRING_VARNAME)) { continue; } - $dollarCloseToken = $tokens[$index + 2]; + $dollarCloseToken = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_COMPLEX_STRING_VARIABLE, $index); - if (!$dollarCloseToken->isGivenKind(CT::T_DOLLAR_CLOSE_CURLY_BRACES)) { - continue; + $prevTokenContent = $tokens[$index - 1]->getContent(); + if (str_ends_with($prevTokenContent, '$') && !str_ends_with($prevTokenContent, '\$')) { + $tokens[$index - 1] = new Token([T_ENCAPSED_AND_WHITESPACE, substr($prevTokenContent, 0, -1).'\$']); } - - $tokenOfStringBeforeToken = $tokens[$index - 1]; - $stringContent = $tokenOfStringBeforeToken->getContent(); - - if (str_ends_with($stringContent, '$') && !str_ends_with($stringContent, '\\$')) { - $newContent = substr($stringContent, 0, -1).'\\$'; - $tokenOfStringBeforeToken = new Token([T_ENCAPSED_AND_WHITESPACE, $newContent]); - } - - $tokens->overrideRange($index - 1, $index + 2, [ - $tokenOfStringBeforeToken, - new Token([T_CURLY_OPEN, '{']), - new Token([T_VARIABLE, '$'.$varnameToken->getContent()]), - new Token([CT::T_CURLY_CLOSE, '}']), - ]); + $tokens[$index] = new Token([T_CURLY_OPEN, '{']); + $tokens[$index + 1] = new Token([T_VARIABLE, '$'.$varnameToken->getContent()]); + $tokens[$dollarCloseToken] = new Token([CT::T_CURLY_CLOSE, '}']); } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php index b269b418cd..6541699cb0 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/SingleQuoteFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -28,21 +29,30 @@ /** * @author Gregor Harlan + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * strings_containing_single_quote_chars?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * strings_containing_single_quote_chars: bool + * } */ final class SingleQuoteFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { $codeSample = <<<'EOF' -isTokenKindFound(T_CONSTANT_ENCAPSED_STRING); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -96,18 +101,15 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void '"' === $content[0] && (true === $this->configuration['strings_containing_single_quote_chars'] || !str_contains($content, "'")) // regex: odd number of backslashes, not followed by double quote or dollar - && !Preg::match('/(? + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\StringNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Filippo Tessarotto + * @author Michael Vorisek + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * double_quoted?: 'escape'|'ignore'|'unescape', + * heredoc?: 'escape'|'ignore'|'unescape', + * single_quoted?: 'escape'|'ignore'|'unescape' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * double_quoted: 'escape'|'ignore'|'unescape', + * heredoc: 'escape'|'ignore'|'unescape', + * single_quoted: 'escape'|'ignore'|'unescape' + * } + */ +final class StringImplicitBackslashesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + $codeSample = <<<'EOF' + 'escape'] + ), + new CodeSample( + $codeSample, + ['double_quoted' => 'unescape'] + ), + new CodeSample( + $codeSample, + ['heredoc' => 'unescape'] + ), + ], + 'In PHP double-quoted strings and heredocs some chars like `n`, `$` or `u` have special meanings if preceded by a backslash ' + .'(and some are special only if followed by other special chars), while a backslash preceding other chars are interpreted like a plain ' + .'backslash. The precise list of those special chars is hard to remember and to identify quickly: this fixer escapes backslashes ' + ."that do not start a special interpretation with the char after them.\n" + .'It is possible to fix also single-quoted strings: in this case there is no special chars apart from single-quote and backslash ' + .'itself, so the fixer simply ensure that all backslashes are escaped. Both single and double backslashes are allowed in single-quoted ' + .'strings, so the purpose in this context is mainly to have a uniformed way to have them written all over the codebase.' + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocToNowdocFixer, SingleQuoteFixer. + * Must run after BacktickToShellExecFixer, MultilineStringToHeredocFixer. + */ + public function getPriority(): int + { + return 15; + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $singleQuotedReservedRegex = '[\'\\\]'; + $doubleQuotedReservedRegex = '(?:[efnrtv$"\\\0-7]|x[0-9A-Fa-f]|u{|$)'; + $heredocSyntaxReservedRegex = '(?:[efnrtv$\\\0-7]|x[0-9A-Fa-f]|u{|$)'; + + $doubleQuoteOpened = false; + foreach ($tokens as $index => $token) { + if ($token->equalsAny(['"', 'b"', 'B"'])) { + $doubleQuoteOpened = !$doubleQuoteOpened; + } + + if (!$token->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING])) { + continue; + } + + $content = $token->getContent(); + if (!str_contains($content, '\\')) { + continue; + } + + // nowdoc syntax + if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && '\'' === substr(rtrim($tokens[$index - 1]->getContent()), -1)) { + continue; + } + + $firstTwoCharacters = strtolower(substr($content, 0, 2)); + $isSingleQuotedString = $token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('\'' === $content[0] || 'b\'' === $firstTwoCharacters); + $isDoubleQuotedString = + ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('"' === $content[0] || 'b"' === $firstTwoCharacters)) + || ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && $doubleQuoteOpened); + + if ($isSingleQuotedString + ? 'ignore' === $this->configuration['single_quoted'] + : ($isDoubleQuotedString + ? 'ignore' === $this->configuration['double_quoted'] + : 'ignore' === $this->configuration['heredoc']) + ) { + continue; + } + + $escapeBackslashes = $isSingleQuotedString + ? 'escape' === $this->configuration['single_quoted'] + : ($isDoubleQuotedString + ? 'escape' === $this->configuration['double_quoted'] + : 'escape' === $this->configuration['heredoc']); + + $reservedRegex = $isSingleQuotedString + ? $singleQuotedReservedRegex + : ($isDoubleQuotedString + ? $doubleQuotedReservedRegex + : $heredocSyntaxReservedRegex); + + if ($escapeBackslashes) { + $regex = '/(?getId(), $newContent]); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('single_quoted', 'Whether to escape backslashes in single-quoted strings.')) + ->setAllowedValues(['escape', 'unescape', 'ignore']) + ->setDefault('unescape') + ->getOption(), + (new FixerOptionBuilder('double_quoted', 'Whether to escape backslashes in double-quoted strings.')) + ->setAllowedValues(['escape', 'unescape', 'ignore']) + ->setDefault('escape') + ->getOption(), + (new FixerOptionBuilder('heredoc', 'Whether to escape backslashes in heredoc syntax.')) + ->setAllowedValues(['escape', 'unescape', 'ignore']) + ->setDefault('escape') + ->getOption(), + ]); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php index 35d5603103..361cc73c57 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLengthToEmptyFixer.php @@ -24,9 +24,6 @@ final class StringLengthToEmptyFixer extends AbstractFunctionReferenceFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -41,16 +38,13 @@ public function getDefinition(): FixerDefinitionInterface * {@inheritdoc} * * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer. - * Must run after NoSpacesInsideParenthesisFixer. + * Must run after NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer. */ public function getPriority(): int { return 1; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); @@ -255,9 +249,8 @@ private function isOperandOfInterest(Token $token): bool private function isOperatorOfInterest(Token $token): bool { return - ($token->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_IS_GREATER_OR_EQUAL])) - || $token->equals('<') || $token->equals('>') - ; + $token->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_IS_GREATER_OR_EQUAL]) + || $token->equals('<') || $token->equals('>'); } private function isOfHigherPrecedence(Token $token): bool @@ -292,7 +285,6 @@ private function keepParentheses(Tokens $tokens, int $openParenthesisIndex, int continue; } - /** @var null|array{isStart: bool, type: int} $blockType */ $blockType = Tokens::detectBlockType($token); if (null !== $blockType && $blockType['isStart']) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php index 7e50c736c0..aae692620b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/StringNotation/StringLineEndingFixer.php @@ -30,25 +30,16 @@ */ final class StringLineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]); } - /** - * {@inheritdoc} - */ public function isRisky(): bool { return true; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -63,9 +54,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $ending = $this->whitespacesConfig->getLineEnding(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php index a7504c2a91..80ef9ef909 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/ArrayIndentationFixer.php @@ -15,6 +15,7 @@ namespace PhpCsFixer\Fixer\Whitespace; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\Indentation; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; @@ -26,15 +27,8 @@ final class ArrayIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** @var int */ - private $newlineTokenIndexCache; + use Indentation; - /** @var int */ - private $newlineTokenPositionCache; - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,19 +39,16 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { - return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); + return $tokens->isAnyTokenKindsFound([T_ARRAY, T_LIST, CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]); } /** * {@inheritdoc} * * Must run before AlignMultilineCommentFixer, BinaryOperatorSpacesFixer. - * Must run after BracesFixer, MethodArgumentSpaceFixer, MethodChainingIndentationFixer. + * Must run after MethodArgumentSpaceFixer. */ public function getPriority(): int { @@ -66,8 +57,7 @@ public function getPriority(): int protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { - $this->returnWithUpdateCache(0, null); - + $lastIndent = ''; $scopes = []; $previousLineInitialIndent = ''; $previousLineNewIndent = ''; @@ -80,23 +70,25 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } if ( - $token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN) - || ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) + $token->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]) + || ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_ARRAY, T_LIST])) ) { - $endIndex = $tokens->findBlockEnd( - $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, - $index - ); + $blockType = Tokens::detectBlockType($token); + $endIndex = $tokens->findBlockEnd($blockType['type'], $index); $scopes[] = [ 'type' => 'array', 'end_index' => $endIndex, - 'initial_indent' => $this->getLineIndentation($tokens, $index), + 'initial_indent' => $lastIndent, ]; continue; } + if ($this->isNewLineToken($tokens, $index)) { + $lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index)); + } + if (null === $currentScope) { continue; } @@ -139,6 +131,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } $tokens[$index] = new Token([T_WHITESPACE, $content]); + $lastIndent = $this->extractIndent($content); continue; } @@ -180,7 +173,9 @@ private function findExpressionEndIndex(Tokens $tokens, int $index, int $parentS for ($searchEndIndex = $index + 1; $searchEndIndex < $parentScopeEndIndex; ++$searchEndIndex) { $searchEndToken = $tokens[$searchEndIndex]; - if ($searchEndToken->equalsAny(['(', '{']) || $searchEndToken->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) { + if ($searchEndToken->equalsAny(['(', '{']) + || $searchEndToken->isGivenKind([CT::T_ARRAY_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN]) + ) { $type = Tokens::detectBlockType($searchEndToken); $searchEndIndex = $tokens->findBlockEnd( $type['type'], @@ -199,74 +194,4 @@ private function findExpressionEndIndex(Tokens $tokens, int $index, int $parentS return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex); } - - private function getLineIndentation(Tokens $tokens, int $index): string - { - $newlineTokenIndex = $this->getPreviousNewlineTokenIndex($tokens, $index); - - if (null === $newlineTokenIndex) { - return ''; - } - - return $this->extractIndent($this->computeNewLineContent($tokens, $newlineTokenIndex)); - } - - private function extractIndent(string $content): string - { - if (Preg::match('/\R(\h*)[^\r\n]*$/D', $content, $matches)) { - return $matches[1]; - } - - return ''; - } - - private function getPreviousNewlineTokenIndex(Tokens $tokens, int $startIndex): ?int - { - $index = $startIndex; - while ($index > 0) { - $index = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE], [T_INLINE_HTML]]); - - if ($this->newlineTokenIndexCache > $index) { - return $this->returnWithUpdateCache($startIndex, $this->newlineTokenPositionCache); - } - - if (null === $index) { - break; - } - - if ($this->isNewLineToken($tokens, $index)) { - return $this->returnWithUpdateCache($startIndex, $index); - } - } - - return $this->returnWithUpdateCache($startIndex, null); - } - - private function isNewLineToken(Tokens $tokens, int $index): bool - { - if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML])) { - return false; - } - - return (bool) Preg::match('/\R/', $this->computeNewLineContent($tokens, $index)); - } - - private function computeNewLineContent(Tokens $tokens, int $index): string - { - $content = $tokens[$index]->getContent(); - - if (0 !== $index && $tokens[$index - 1]->equalsAny([[T_OPEN_TAG], [T_CLOSE_TAG]])) { - $content = Preg::replace('/\S/', '', $tokens[$index - 1]->getContent()).$content; - } - - return $content; - } - - private function returnWithUpdateCache(int $index, ?int $position): ?int - { - $this->newlineTokenIndexCache = $index; - $this->newlineTokenPositionCache = $position; - - return $position; - } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php index 917d8f4795..404354a10c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; @@ -31,16 +32,30 @@ /** * @author Dariusz Rumiński * @author Andreas Möller + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * statements?: list<'break'|'case'|'continue'|'declare'|'default'|'do'|'exit'|'for'|'foreach'|'goto'|'if'|'include'|'include_once'|'phpdoc'|'require'|'require_once'|'return'|'switch'|'throw'|'try'|'while'|'yield'|'yield_from'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * statements: list<'break'|'case'|'continue'|'declare'|'default'|'do'|'exit'|'for'|'foreach'|'goto'|'if'|'include'|'include_once'|'phpdoc'|'require'|'require_once'|'return'|'switch'|'throw'|'try'|'while'|'yield'|'yield_from'> + * } */ final class BlankLineBeforeStatementFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + /** + * @var array + */ private static array $tokenMap = [ 'break' => T_BREAK, 'case' => T_CASE, 'continue' => T_CONTINUE, 'declare' => T_DECLARE, 'default' => T_DEFAULT, - 'phpdoc' => T_DOC_COMMENT, 'do' => T_DO, 'exit' => T_EXIT, 'for' => T_FOR, @@ -49,6 +64,7 @@ final class BlankLineBeforeStatementFixer extends AbstractFixer implements Confi 'if' => T_IF, 'include' => T_INCLUDE, 'include_once' => T_INCLUDE_ONCE, + 'phpdoc' => T_DOC_COMMENT, 'require' => T_REQUIRE, 'require_once' => T_REQUIRE_ONCE, 'return' => T_RETURN, @@ -60,27 +76,11 @@ final class BlankLineBeforeStatementFixer extends AbstractFixer implements Confi 'yield_from' => T_YIELD_FROM, ]; - private array $fixTokenMap = []; - /** - * {@inheritdoc} + * @var list */ - public function configure(array $configuration): void - { - parent::configure($configuration); - - $this->fixTokenMap = []; - - foreach ($this->configuration['statements'] as $key) { - $this->fixTokenMap[$key] = self::$tokenMap[$key]; - } - - $this->fixTokenMap = array_values($this->fixTokenMap); - } + private array $fixTokenMap = []; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -221,10 +221,11 @@ function A() { ), new CodeSample( 'isAnyTokenKindsFound($this->fixTokenMap); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + $fixTokenMap = []; + + foreach ($this->configuration['statements'] as $key) { + $fixTokenMap[$key] = self::$tokenMap[$key]; + } + + $this->fixTokenMap = array_values($fixTokenMap); + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $analyzer = new TokensAnalyzer($tokens); @@ -271,24 +277,26 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - $prevNonWhitespace = $tokens->getPrevNonWhitespace($index); + if ($token->isGivenKind(T_CASE) && $analyzer->isEnumCase($index)) { + continue; + } + + $insertBlankLineIndex = $this->getInsertBlankLineIndex($tokens, $index); + $prevNonWhitespace = $tokens->getPrevNonWhitespace($insertBlankLineIndex); if ($this->shouldAddBlankLine($tokens, $prevNonWhitespace)) { - $this->insertBlankLine($tokens, $index); + $this->insertBlankLine($tokens, $insertBlankLineIndex); } $index = $prevNonWhitespace; } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('statements', 'List of statements which must be preceded by an empty line.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset(array_keys(self::$tokenMap))]) ->setDefault([ 'break', @@ -302,6 +310,33 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ]); } + private function getInsertBlankLineIndex(Tokens $tokens, int $index): int + { + while ($index > 0) { + if ($tokens[$index - 1]->isWhitespace() && substr_count($tokens[$index - 1]->getContent(), "\n") > 1) { + break; + } + + $prevIndex = $tokens->getPrevNonWhitespace($index); + + if (!$tokens[$prevIndex]->isComment()) { + break; + } + + if (!$tokens[$prevIndex - 1]->isWhitespace()) { + break; + } + + if (1 !== substr_count($tokens[$prevIndex - 1]->getContent(), "\n")) { + break; + } + + $index = $prevIndex; + } + + return $index; + } + private function shouldAddBlankLine(Tokens $tokens, int $prevNonWhitespace): bool { $prevNonWhitespaceToken = $tokens[$prevNonWhitespace]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBetweenImportGroupsFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBetweenImportGroupsFixer.php new file mode 100644 index 0000000000..7bd48ca64a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/BlankLineBetweenImportGroupsFixer.php @@ -0,0 +1,180 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Sander Verkuil + */ +final class BlankLineBetweenImportGroupsFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + private const IMPORT_TYPE_CLASS = 'class'; + + private const IMPORT_TYPE_CONST = 'const'; + + private const IMPORT_TYPE_FUNCTION = 'function'; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Putting blank lines between `use` statement groups.', + [ + new CodeSample( + 'isTokenKindFound(T_USE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); + + foreach (array_reverse($namespacesImports) as $uses) { + $this->walkOverUses($tokens, $uses); + } + } + + /** + * @param list $uses + */ + private function walkOverUses(Tokens $tokens, array $uses): void + { + $usesCount = \count($uses); + + if ($usesCount < 2) { + return; // nothing to fix + } + + $previousType = null; + + for ($i = $usesCount - 1; $i >= 0; --$i) { + $index = $uses[$i]; + $startIndex = $tokens->getNextMeaningfulToken($index + 1); + $endIndex = $tokens->getNextTokenOfKind($startIndex, [';', [T_CLOSE_TAG]]); + + if ($tokens[$startIndex]->isGivenKind(CT::T_CONST_IMPORT)) { + $type = self::IMPORT_TYPE_CONST; + } elseif ($tokens[$startIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { + $type = self::IMPORT_TYPE_FUNCTION; + } else { + $type = self::IMPORT_TYPE_CLASS; + } + + if (null !== $previousType && $type !== $previousType) { + $this->ensureLine($tokens, $endIndex + 1); + } + + $previousType = $type; + } + } + + private function ensureLine(Tokens $tokens, int $index): void + { + static $lineEnding; + + if (null === $lineEnding) { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + $lineEnding .= $lineEnding; + } + + $index = $this->getInsertIndex($tokens, $index); + $indent = WhitespacesAnalyzer::detectIndent($tokens, $index); + + $tokens->ensureWhitespaceAtIndex($index, 1, $lineEnding.$indent); + } + + private function getInsertIndex(Tokens $tokens, int $index): int + { + $tokensCount = \count($tokens); + + for (; $index < $tokensCount - 1; ++$index) { + if (!$tokens[$index]->isWhitespace() && !$tokens[$index]->isComment()) { + return $index - 1; + } + + $content = $tokens[$index]->getContent(); + + if (str_contains($content, "\n")) { + return $index; + } + } + + return $index; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypeDeclarationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypeDeclarationFixer.php new file mode 100644 index 0000000000..e1af647ef4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypeDeclarationFixer.php @@ -0,0 +1,72 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Jack Cherng + */ +final class CompactNullableTypeDeclarationFixer extends AbstractFixer +{ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Remove extra spaces in a nullable type declaration.', + [ + new CodeSample( + "isTokenKindFound(CT::T_NULLABLE_TYPE); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + static $typehintKinds = [ + CT::T_ARRAY_TYPEHINT, + T_CALLABLE, + T_NS_SEPARATOR, + T_STATIC, + T_STRING, + ]; + + for ($index = $tokens->count() - 1; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { + continue; + } + + // remove whitespaces only if there are only whitespaces + // between '?' and the variable type + if ( + $tokens[$index + 1]->isWhitespace() + && $tokens[$index + 2]->isGivenKind($typehintKinds) + ) { + $tokens->removeTrailingWhitespace($index); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php index 042d82847c..89c4ec6547 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/CompactNullableTypehintFixer.php @@ -14,67 +14,50 @@ namespace PhpCsFixer\Fixer\Whitespace; -use PhpCsFixer\AbstractFixer; -use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\CT; -use PhpCsFixer\Tokenizer\Tokens; /** * @author Jack Cherng + * + * @deprecated */ -final class CompactNullableTypehintFixer extends AbstractFixer +final class CompactNullableTypehintFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ + private CompactNullableTypeDeclarationFixer $compactNullableTypeDeclarationFixer; + + public function __construct() + { + $this->compactNullableTypeDeclarationFixer = new CompactNullableTypeDeclarationFixer(); + + parent::__construct(); + } + public function getDefinition(): FixerDefinitionInterface { + $fixerDefinition = $this->compactNullableTypeDeclarationFixer->getDefinition(); + return new FixerDefinition( 'Remove extra spaces in a nullable typehint.', - [ - new CodeSample( - "getCodeSamples(), + $fixerDefinition->getDescription(), + $fixerDefinition->getRiskyDescription(), ); } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isTokenKindFound(CT::T_NULLABLE_TYPE); + return [ + $this->compactNullableTypeDeclarationFixer->getName(), + ]; } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + protected function createProxyFixers(): array { - static $typehintKinds = [ - CT::T_ARRAY_TYPEHINT, - T_CALLABLE, - T_NS_SEPARATOR, - T_STRING, + return [ + $this->compactNullableTypeDeclarationFixer, ]; - - for ($index = $tokens->count() - 1; $index >= 0; --$index) { - if (!$tokens[$index]->isGivenKind(CT::T_NULLABLE_TYPE)) { - continue; - } - - // remove whitespaces only if there are only whitespaces - // between '?' and the variable type - if ( - $tokens[$index + 1]->isWhitespace() - && $tokens[$index + 2]->isGivenKind($typehintKinds) - ) { - $tokens->removeTrailingWhitespace($index); - } - } } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php index a5025180f8..67b9d2838a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/HeredocIndentationFixer.php @@ -16,14 +16,14 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\FixerDefinition\VersionSpecification; -use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; use PhpCsFixer\Tokenizer\Token; @@ -31,52 +31,51 @@ /** * @author Gregor Harlan + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * indentation?: 'same_as_start'|'start_plus_one' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * indentation: 'same_as_start'|'start_plus_one' + * } */ final class HeredocIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'Heredoc/nowdoc content must be properly indented. Requires PHP >= 7.3.', + 'Heredoc/nowdoc content must be properly indented.', [ - new VersionSpecificCodeSample( - <<<'SAMPLE' - 'same_as_start'] ), ] @@ -85,15 +84,19 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} + * + * Must run after BracesFixer, MultilineStringToHeredocFixer, StatementIndentationFixer. */ + public function getPriority(): int + { + return -26; + } + public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(T_START_HEREDOC); } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ @@ -137,7 +140,9 @@ private function fixIndentation(Tokens $tokens, int $start, int $end): void return; } - for ($index = $end - 1, $last = true; $index > $start; --$index, $last = false) { + $index = $end - 1; + + for ($last = true; $index > $start; --$index, $last = false) { if (!$tokens[$index]->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_WHITESPACE])) { continue; } @@ -148,7 +153,7 @@ private function fixIndentation(Tokens $tokens, int $start, int $end): void $content = Preg::replace('/(?<=\v)(?!'.$currentIndent.')\h+/', '', $content); } - $regexEnd = $last && !$currentIndent ? '(?!\v|$)' : '(?!\v)'; + $regexEnd = $last && '' === $currentIndent ? '(?!\v|$)' : '(?!\v)'; $content = Preg::replace('/(?<=\v)'.$currentIndent.$regexEnd.'/', $indent, $content); $tokens[$index] = new Token([$tokens[$index]->getId(), $content]); @@ -164,9 +169,9 @@ private function fixIndentation(Tokens $tokens, int $start, int $end): void $content = $tokens[$index]->getContent(); - if (!\in_array($content[0], ["\r", "\n"], true) && (!$currentIndent || str_starts_with($content, $currentIndent))) { + if (!\in_array($content[0], ["\r", "\n"], true) && ('' === $currentIndent || str_starts_with($content, $currentIndent))) { $content = $indent.substr($content, $currentIndentLength); - } elseif ($currentIndent) { + } elseif ('' !== $currentIndent) { $content = Preg::replace('/^(?!'.$currentIndent.')\h+/', '', $content); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php index 14a1d29e56..d88c74ba0d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/IndentationTypeFixer.php @@ -35,9 +35,6 @@ final class IndentationTypeFixer extends AbstractFixer implements WhitespacesAwa */ private $indent; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -59,17 +56,11 @@ public function getPriority(): int return 50; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound([T_COMMENT, T_DOC_COMMENT, T_WHITESPACE]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $this->indent = $this->whitespacesConfig->getIndent(); @@ -101,9 +92,7 @@ private function fixIndentInComment(Tokens $tokens, int $index): Token $indent = $this->indent; // change indent to expected one - $content = Preg::replaceCallback('/^(?: )+/m', function (array $matches) use ($indent): string { - return $this->getExpectedIndent($matches[0], $indent); - }, $content); + $content = Preg::replaceCallback('/^(?: )+/m', fn (array $matches): string => $this->getExpectedIndent($matches[0], $indent), $content); return new Token([$tokens[$index]->getId(), $content]); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php index 65e673abd2..adc2016fe8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/LineEndingFixer.php @@ -31,17 +31,11 @@ */ final class LineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -54,19 +48,6 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - * - * Must run before BracesFixer. - */ - public function getPriority(): int - { - return 40; - } - - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $ending = $this->whitespacesConfig->getLineEnding(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php index 66e70b4e81..dc7fff6eab 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/MethodChainingIndentationFixer.php @@ -20,7 +20,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -29,9 +28,6 @@ */ final class MethodChainingIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -43,25 +39,18 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run before ArrayIndentationFixer, MethodArgumentSpaceFixer. - * Must run after BracesFixer. + * Must run after NoSpaceAroundDoubleColonFixer. */ public function getPriority(): int { - return 34; + return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getObjectOperatorKinds()); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $lineEnding = $this->whitespacesConfig->getLineEnding(); @@ -71,25 +60,59 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } + $endParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', ',', [T_CLOSE_TAG]]); + + if (null === $endParenthesisIndex || !$tokens[$endParenthesisIndex]->equals('(')) { + continue; + } + if ($this->canBeMovedToNextLine($index, $tokens)) { $newline = new Token([T_WHITESPACE, $lineEnding]); + if ($tokens[$index - 1]->isWhitespace()) { $tokens[$index - 1] = $newline; } else { $tokens->insertAt($index, $newline); ++$index; + ++$endParenthesisIndex; } } $currentIndent = $this->getIndentAt($tokens, $index - 1); + if (null === $currentIndent) { continue; } $expectedIndent = $this->getExpectedIndentAt($tokens, $index); + if ($currentIndent !== $expectedIndent) { $tokens[$index - 1] = new Token([T_WHITESPACE, $lineEnding.$expectedIndent]); } + + $endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endParenthesisIndex); + + for ($searchIndex = $index + 1; $searchIndex < $endParenthesisIndex; ++$searchIndex) { + $searchToken = $tokens[$searchIndex]; + + if (!$searchToken->isWhitespace()) { + continue; + } + + $content = $searchToken->getContent(); + + if (!Preg::match('/\R/', $content)) { + continue; + } + + $content = Preg::replace( + '/(\R)'.$currentIndent.'(\h*)$/D', + '$1'.$expectedIndent.'$2', + $content + ); + + $tokens[$searchIndex] = new Token([$searchToken->getId(), $content]); + } } } @@ -136,7 +159,7 @@ private function canBeMovedToNextLine(int $index, Tokens $tokens): bool continue; } - if ($tokens[$i]->isWhitespace() && 1 === Preg::match('/\R/', $tokens[$i]->getContent())) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) { return $hasCommentBefore; } } @@ -149,7 +172,7 @@ private function canBeMovedToNextLine(int $index, Tokens $tokens): bool */ private function getIndentAt(Tokens $tokens, int $index): ?string { - if (1 === Preg::match('/\R{1}(\h*)$/', $this->getIndentContentAt($tokens, $index), $matches)) { + if (Preg::match('/\R{1}(\h*)$/', $this->getIndentContentAt($tokens, $index), $matches)) { return $matches[1]; } @@ -181,17 +204,18 @@ private function getIndentContentAt(Tokens $tokens, int $index): string */ private function currentLineRequiresExtraIndentLevel(Tokens $tokens, int $start, int $end): bool { - if ($tokens[$start + 1]->isObjectOperator()) { - return false; - } + $firstMeaningful = $tokens->getNextMeaningfulToken($start); + + if ($tokens[$firstMeaningful]->isObjectOperator()) { + $thirdMeaningful = $tokens->getNextMeaningfulToken($tokens->getNextMeaningfulToken($firstMeaningful)); - if ($tokens[$end]->isGivenKind(CT::T_BRACE_CLASS_INSTANTIATION_CLOSE)) { - return true; + return + $tokens[$thirdMeaningful]->equals('(') + && $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $thirdMeaningful) > $end; } return !$tokens[$end]->equals(')') - || $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end) >= $start - ; + || $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $end) >= $start; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php index 9c484e0108..dbb7be3996 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; @@ -25,6 +26,7 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -33,13 +35,26 @@ /** * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * tokens?: list<'attribute'|'break'|'case'|'continue'|'curly_brace_block'|'default'|'extra'|'parenthesis_brace_block'|'return'|'square_brace_block'|'switch'|'throw'|'use'|'use_trait'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * tokens: list<'attribute'|'break'|'case'|'continue'|'curly_brace_block'|'default'|'extra'|'parenthesis_brace_block'|'return'|'square_brace_block'|'switch'|'throw'|'use'|'use_trait'> + * } */ final class NoExtraBlankLinesFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + /** - * @var string[] + * @var list */ private static array $availableTokens = [ + 'attribute', 'break', 'case', 'continue', @@ -56,80 +71,19 @@ final class NoExtraBlankLinesFixer extends AbstractFixer implements Configurable ]; /** - * @var array key is token id, value is name of callback - */ - private $tokenKindCallbackMap; - - /** - * @var array token prototype, value is name of callback + * @var array key is token id */ - private $tokenEqualsMap; + private array $tokenKindCallbackMap; /** - * @var Tokens + * @var array key is token's content */ - private $tokens; - - /** - * @var TokensAnalyzer - */ - private $tokensAnalyzer; - - /** - * {@inheritdoc} - */ - public function configure(array $configuration): void - { - if (isset($configuration['tokens']) && \in_array('use_trait', $configuration['tokens'], true)) { - Utils::triggerDeprecation(new \RuntimeException('Option "tokens: use_trait" used in `no_extra_blank_lines` rule is deprecated, use the rule `class_attributes_separation` with `elements: trait_import` instead.')); - } - - parent::configure($configuration); - - static $reprToTokenMap = [ - 'break' => T_BREAK, - 'case' => T_CASE, - 'continue' => T_CONTINUE, - 'curly_brace_block' => '{', - 'default' => T_DEFAULT, - 'extra' => T_WHITESPACE, - 'parenthesis_brace_block' => '(', - 'return' => T_RETURN, - 'square_brace_block' => CT::T_ARRAY_SQUARE_BRACE_OPEN, - 'switch' => T_SWITCH, - 'throw' => T_THROW, - 'use' => T_USE, - 'use_trait' => CT::T_USE_TRAIT, - ]; - - static $tokenKindCallbackMap = [ - T_BREAK => 'fixAfterToken', - T_CASE => 'fixAfterToken', - T_CONTINUE => 'fixAfterToken', - T_DEFAULT => 'fixAfterToken', - T_RETURN => 'fixAfterToken', - T_SWITCH => 'fixAfterToken', - T_THROW => 'fixAfterThrowToken', - T_USE => 'removeBetweenUse', - T_WHITESPACE => 'removeMultipleBlankLines', - CT::T_USE_TRAIT => 'removeBetweenUse', - CT::T_ARRAY_SQUARE_BRACE_OPEN => 'fixStructureOpenCloseIfMultiLine', // typeless '[' tokens should not be fixed (too rare) - ]; + private array $tokenEqualsMap; - static $tokenEqualsMap = [ - '{' => 'fixStructureOpenCloseIfMultiLine', // i.e. not: CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN - '(' => 'fixStructureOpenCloseIfMultiLine', // i.e. not: CT::T_BRACE_CLASS_INSTANTIATION_OPEN - ]; - - $tokensAssoc = array_flip(array_intersect_key($reprToTokenMap, array_flip($this->configuration['tokens']))); + private Tokens $tokens; - $this->tokenKindCallbackMap = array_intersect_key($tokenKindCallbackMap, $tokensAssoc); - $this->tokenEqualsMap = array_intersect_key($tokenEqualsMap, $tokensAssoc); - } + private TokensAnalyzer $tokensAnalyzer; - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -271,47 +225,96 @@ class Bar * {@inheritdoc} * * Must run before BlankLineBeforeStatementFixer. - * Must run after ClassAttributesSeparationFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, EmptyLoopConditionFixer, FunctionToConstantFixer, ModernizeStrposFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUnusedImportsFixer, NoUselessElseFixer, NoUselessReturnFixer, NoUselessSprintfFixer, StringLengthToEmptyFixer. + * Must run after ClassAttributesSeparationFixer, CombineConsecutiveUnsetsFixer, EmptyLoopBodyFixer, EmptyLoopConditionFixer, FunctionToConstantFixer, LongToShorthandOperatorFixer, ModernizeStrposFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUnusedImportsFixer, NoUselessElseFixer, NoUselessReturnFixer, NoUselessSprintfFixer, PhpdocReadonlyClassCommentToKeywordFixer, StringLengthToEmptyFixer, YieldFromArrayToYieldsFixer. */ public function getPriority(): int { return -20; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } /** - * {@inheritdoc} + * @param _AutogeneratedInputConfiguration $configuration */ + protected function configurePreNormalisation(array $configuration): void + { + if (isset($configuration['tokens']) && \in_array('use_trait', $configuration['tokens'], true)) { + Utils::triggerDeprecation(new \RuntimeException('Option "tokens: use_trait" used in `no_extra_blank_lines` rule is deprecated, use the rule `class_attributes_separation` with `elements: trait_import` instead.')); + } + } + + protected function configurePostNormalisation(): void + { + $tokensConfiguration = $this->configuration['tokens']; + + $this->tokenEqualsMap = []; + + if (\in_array('curly_brace_block', $tokensConfiguration, true)) { + $this->tokenEqualsMap['{'] = [$this, 'fixStructureOpenCloseIfMultiLine']; // i.e. not: CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN + } + + if (\in_array('parenthesis_brace_block', $tokensConfiguration, true)) { + $this->tokenEqualsMap['('] = [$this, 'fixStructureOpenCloseIfMultiLine']; // i.e. not: CT::T_BRACE_CLASS_INSTANTIATION_OPEN + } + + // Each item requires explicit array-like callable, otherwise PHPStan will complain about unused private methods. + $configMap = [ + 'attribute' => [CT::T_ATTRIBUTE_CLOSE, [$this, 'fixAfterToken']], + 'break' => [T_BREAK, [$this, 'fixAfterToken']], + 'case' => [T_CASE, [$this, 'fixAfterCaseToken']], + 'continue' => [T_CONTINUE, [$this, 'fixAfterToken']], + 'default' => [T_DEFAULT, [$this, 'fixAfterToken']], + 'extra' => [T_WHITESPACE, [$this, 'removeMultipleBlankLines']], + 'return' => [T_RETURN, [$this, 'fixAfterToken']], + 'square_brace_block' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, [$this, 'fixStructureOpenCloseIfMultiLine']], + 'switch' => [T_SWITCH, [$this, 'fixAfterToken']], + 'throw' => [T_THROW, [$this, 'fixAfterThrowToken']], + 'use' => [T_USE, [$this, 'removeBetweenUse']], + 'use_trait' => [CT::T_USE_TRAIT, [$this, 'removeBetweenUse']], + ]; + + $this->tokenKindCallbackMap = []; + + foreach ($tokensConfiguration as $config) { + if (isset($configMap[$config])) { + $this->tokenKindCallbackMap[$configMap[$config][0]] = $configMap[$config][1]; + } + } + } + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $this->tokens = $tokens; $this->tokensAnalyzer = new TokensAnalyzer($this->tokens); + for ($index = $tokens->getSize() - 1; $index > 0; --$index) { $this->fixByToken($tokens[$index], $index); } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ (new FixerOptionBuilder('tokens', 'List of tokens to fix.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset(self::$availableTokens)]) ->setDefault(['extra']) ->getOption(), ]); } + /** + * @uses fixAfterToken() + * @uses fixAfterCaseToken() + * @uses fixAfterThrowToken() + * @uses fixStructureOpenCloseIfMultiLine() + * @uses removeBetweenUse() + * @uses removeMultipleBlankLines() + */ private function fixByToken(Token $token, int $index): void { foreach ($this->tokenKindCallbackMap as $kind => $callback) { @@ -319,7 +322,7 @@ private function fixByToken(Token $token, int $index): void continue; } - $this->{$callback}($index); + \call_user_func_array($this->tokenKindCallbackMap[$token->getId()], [$index]); return; } @@ -329,7 +332,7 @@ private function fixByToken(Token $token, int $index): void continue; } - $this->{$callback}($index); + \call_user_func_array($this->tokenEqualsMap[$token->getContent()], [$index]); return; } @@ -338,11 +341,13 @@ private function fixByToken(Token $token, int $index): void private function removeBetweenUse(int $index): void { $next = $this->tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); + if (null === $next || $this->tokens[$next]->isGivenKind(T_CLOSE_TAG)) { return; } $nextUseCandidate = $this->tokens->getNextMeaningfulToken($next); + if (null === $nextUseCandidate || !$this->tokens[$nextUseCandidate]->isGivenKind($this->tokens[$index]->getId()) || !$this->containsLinebreak($index, $nextUseCandidate)) { return; } @@ -352,7 +357,7 @@ private function removeBetweenUse(int $index): void private function removeMultipleBlankLines(int $index): void { - $expected = $this->tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && 1 === Preg::match('/\R$/', $this->tokens[$index - 1]->getContent()) ? 1 : 2; + $expected = $this->tokens[$index - 1]->isGivenKind(T_OPEN_TAG) && Preg::match('/\R$/', $this->tokens[$index - 1]->getContent()) ? 1 : 2; $parts = Preg::split('/(.*\R)/', $this->tokens[$index]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $count = \count($parts); @@ -381,11 +386,32 @@ private function fixAfterToken(int $index): void $this->removeEmptyLinesAfterLineWithTokenAt($index); } + private function fixAfterCaseToken(int $index): void + { + if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required + $enumSwitchIndex = $this->tokens->getPrevTokenOfKind($index, [[T_SWITCH], [T_ENUM]]); + + if (!$this->tokens[$enumSwitchIndex]->isGivenKind(T_SWITCH)) { + return; + } + } + + $this->removeEmptyLinesAfterLineWithTokenAt($index); + } + private function fixAfterThrowToken(int $index): void { - if ($this->tokens[$this->tokens->getPrevMeaningfulToken($index)]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) { - $this->fixAfterToken($index); + $prevIndex = $this->tokens->getPrevMeaningfulToken($index); + + if (!$this->tokens[$prevIndex]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) { + return; } + + if ($this->tokens[$prevIndex]->equals(':') && !SwitchAnalyzer::belongsToSwitch($this->tokens, $prevIndex)) { + return; + } + + $this->fixAfterToken($index); } /** @@ -412,8 +438,24 @@ private function fixStructureOpenCloseIfMultiLine(int $index): void private function removeEmptyLinesAfterLineWithTokenAt(int $index): void { // find the line break + $parenthesesDepth = 0; $tokenCount = \count($this->tokens); for ($end = $index; $end < $tokenCount; ++$end) { + if ($this->tokens[$end]->equals('(')) { + ++$parenthesesDepth; + + continue; + } + + if ($this->tokens[$end]->equals(')')) { + --$parenthesesDepth; + if ($parenthesesDepth < 0) { + return; + } + + continue; + } + if ( $this->tokens[$end]->equals('}') || str_contains($this->tokens[$end]->getContent(), "\n") @@ -430,16 +472,12 @@ private function removeEmptyLinesAfterLineWithTokenAt(int $index): void for ($i = $end; $i < $tokenCount && $this->tokens[$i]->isWhitespace(); ++$i) { $content = $this->tokens[$i]->getContent(); + if (substr_count($content, "\n") < 1) { continue; } - $pos = strrpos($content, "\n"); - if ($pos + 2 <= \strlen($content)) { // preserve indenting where possible - $newContent = $ending.substr($content, $pos + 1); - } else { - $newContent = $ending; - } + $newContent = Preg::replace('/^.*\R(\h*)$/s', $ending.'$1', $content); $this->tokens[$i] = new Token([T_WHITESPACE, $newContent]); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php index a9f69617ec..1d8156cab9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; @@ -28,12 +29,21 @@ /** * @author Javier Spagnoletti + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * positions?: list<'inside'|'outside'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * positions: list<'inside'|'outside'> + * } */ final class NoSpacesAroundOffsetFixer extends AbstractFixer implements ConfigurableFixerInterface { - /** - * {@inheritdoc} - */ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -46,17 +56,11 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { foreach ($tokens as $index => $token) { @@ -93,16 +97,13 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void } } - /** - * {@inheritdoc} - */ protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { $values = ['inside', 'outside']; return new FixerConfigurationResolver([ (new FixerOptionBuilder('positions', 'Whether spacing should be fixed inside and/or outside the offset braces.')) - ->setAllowedTypes(['array']) + ->setAllowedTypes(['string[]']) ->setAllowedValues([new AllowedValueSubset($values)]) ->setDefault($values) ->getOption(), diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php index fe6b56a348..d850751247 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php @@ -14,7 +14,8 @@ namespace PhpCsFixer\Fixer\Whitespace; -use PhpCsFixer\AbstractFixer; +use PhpCsFixer\AbstractProxyFixer; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -25,12 +26,11 @@ * * @author Marc Aubé * @author Dariusz Rumiński + * + * @deprecated in favor of SpacesInsideParenthesisFixer */ -final class NoSpacesInsideParenthesisFixer extends AbstractFixer +final class NoSpacesInsideParenthesisFixer extends AbstractProxyFixer implements DeprecatedFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -51,61 +51,25 @@ function foo( \$bar, \$baz ) * {@inheritdoc} * * Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer, StringLengthToEmptyFixer. - * Must run after CombineConsecutiveIssetsFixer, CombineNestedDirnameFixer, LambdaNotUsedImportFixer, ModernizeStrposFixer, NoTrailingCommaInSinglelineFunctionCallFixer, NoUselessSprintfFixer, PowToExponentiationFixer. + * Must run after CombineConsecutiveIssetsFixer, CombineNestedDirnameFixer, IncrementStyleFixer, LambdaNotUsedImportFixer, ModernizeStrposFixer, NoUselessSprintfFixer, PowToExponentiationFixer. */ public function getPriority(): int { - return 2; + return 3; } - /** - * {@inheritdoc} - */ - public function isCandidate(Tokens $tokens): bool + public function getSuccessorsNames(): array { - return $tokens->isTokenKindFound('('); + return array_keys($this->proxyFixers); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + public function isCandidate(Tokens $tokens): bool { - foreach ($tokens as $index => $token) { - if (!$token->equals('(')) { - continue; - } - - $prevIndex = $tokens->getPrevMeaningfulToken($index); - - // ignore parenthesis for T_ARRAY - if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ARRAY)) { - continue; - } - - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); - - // remove space after opening `(` - if (!$tokens[$tokens->getNextNonWhitespace($index)]->isComment()) { - $this->removeSpaceAroundToken($tokens, $index + 1); - } - - // remove space before closing `)` if it is not `list($a, $b, )` case - if (!$tokens[$tokens->getPrevMeaningfulToken($endIndex)]->equals(',')) { - $this->removeSpaceAroundToken($tokens, $endIndex - 1); - } - } + return $tokens->isTokenKindFound('('); } - /** - * Remove spaces from token at a given index. - */ - private function removeSpaceAroundToken(Tokens $tokens, int $index): void + protected function createProxyFixers(): array { - $token = $tokens[$index]; - - if ($token->isWhitespace() && !str_contains($token->getContent(), "\n")) { - $tokens->clearAt($index); - } + return [new SpacesInsideParenthesesFixer()]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php index 5e7d6a812d..e05a9fdd0d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php @@ -32,9 +32,6 @@ */ final class NoTrailingWhitespaceFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,17 +50,11 @@ public function getPriority(): int return 0; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { for ($index = \count($tokens) - 1; $index >= 0; --$index) { @@ -72,8 +63,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void $token->isGivenKind(T_OPEN_TAG) && $tokens->offsetExists($index + 1) && $tokens[$index + 1]->isWhitespace() - && 1 === Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches) - && 1 === Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches) + && Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches) + && Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches) ) { $tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]); $tokens->ensureWhitespaceAtIndex($index + 1, 0, $whitespaceMatches[2]); @@ -85,12 +76,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void continue; } - $lines = Preg::split('/(\\R+)/', $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); + $lines = Preg::split('/(\R+)/', $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); $linesSize = \count($lines); // fix only multiline whitespaces or singleline whitespaces at the end of file if ($linesSize > 1 || !isset($tokens[$index + 1])) { - if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || 1 !== Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) { + if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || !Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) { $lines[0] = rtrim($lines[0], " \t"); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php index 7f39bc6b08..352ce7a85d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php @@ -20,7 +20,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** @@ -28,9 +27,6 @@ */ final class NoWhitespaceInBlankLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -42,24 +38,18 @@ public function getDefinition(): FixerDefinitionInterface /** * {@inheritdoc} * - * Must run after AssignNullCoalescingToCoalesceEqualFixer, CombineConsecutiveIssetsFixer, CombineConsecutiveUnsetsFixer, FunctionToConstantFixer, NoEmptyCommentFixer, NoEmptyPhpdocFixer, NoEmptyStatementFixer, NoUselessElseFixer, NoUselessReturnFixer. + * Must run after AssignNullCoalescingToCoalesceEqualFixer, CombineConsecutiveIssetsFixer, CombineConsecutiveUnsetsFixer, FunctionToConstantFixer, NoEmptyCommentFixer, NoEmptyStatementFixer, NoUselessElseFixer, NoUselessReturnFixer, YieldFromArrayToYieldsFixer. */ public function getPriority(): int { - return -19; + return -99; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { // skip first as it cannot be a white space token diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php index af7fa873cd..6fbea69dce 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SingleBlankLineAtEofFixer.php @@ -31,9 +31,6 @@ */ final class SingleBlankLineAtEofFixer extends AbstractFixer implements WhitespacesAwareFixerInterface { - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -45,26 +42,17 @@ public function getDefinition(): FixerDefinitionInterface ); } - /** - * {@inheritdoc} - */ public function getPriority(): int { // must run last to be sure the file is properly formatted before it runs - return -50; + return -100; } - /** - * {@inheritdoc} - */ public function isCandidate(Tokens $tokens): bool { return true; } - /** - * {@inheritdoc} - */ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $count = $tokens->count(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SpacesInsideParenthesesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SpacesInsideParenthesesFixer.php new file mode 100644 index 0000000000..f5078c2b01 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/SpacesInsideParenthesesFixer.php @@ -0,0 +1,223 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Fixer for rules defined in PSR2 ¶4.3, ¶4.6, ¶5. + * + * @author Marc Aubé + * @author Dariusz Rumiński + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * space?: 'none'|'single' + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * space: 'none'|'single' + * } + */ +final class SpacesInsideParenthesesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Parentheses must be declared using the configured whitespace.', + [ + new CodeSample(" 'none'] + ), + new CodeSample( + " 'single'] + ), + new CodeSample( + " 'single'] + ), + ], + 'By default there are not any additional spaces inside parentheses, however with `space=single` configuration option whitespace inside parentheses will be unified to single space.' + ); + } + + /** + * {@inheritdoc} + * + * Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer, StringLengthToEmptyFixer. + * Must run after CombineConsecutiveIssetsFixer, CombineNestedDirnameFixer, IncrementStyleFixer, LambdaNotUsedImportFixer, ModernizeStrposFixer, NoUselessSprintfFixer, PowToExponentiationFixer. + */ + public function getPriority(): int + { + return 3; + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound(['(', CT::T_BRACE_CLASS_INSTANTIATION_OPEN]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + if ('none' === $this->configuration['space']) { + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + // ignore parenthesis for T_ARRAY + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ARRAY)) { + continue; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + $endIndex = $tokens->findBlockEnd($blockType['type'], $index); + + // remove space after opening `(` + if (!$tokens[$tokens->getNextNonWhitespace($index)]->isComment()) { + $this->removeSpaceAroundToken($tokens, $index + 1); + } + + // remove space before closing `)` if it is not `list($a, $b, )` case + if (!$tokens[$tokens->getPrevMeaningfulToken($endIndex)]->equals(',')) { + $this->removeSpaceAroundToken($tokens, $endIndex - 1); + } + } + } + + if ('single' === $this->configuration['space']) { + foreach ($tokens as $index => $token) { + if (!$token->equalsAny(['(', [CT::T_BRACE_CLASS_INSTANTIATION_OPEN]])) { + continue; + } + + $blockType = Tokens::detectBlockType($tokens[$index]); + $endParenthesisIndex = $tokens->findBlockEnd($blockType['type'], $index); + + // if not other content than spaces in block remove spaces + $blockContent = $this->getBlockContent($index, $endParenthesisIndex, $tokens); + if (1 === \count($blockContent) && \in_array(' ', $blockContent, true)) { + $this->removeSpaceAroundToken($tokens, $index + 1); + + continue; + } + + // don't process if the next token is `)` + $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index); + if (')' === $tokens[$nextMeaningfulTokenIndex]->getContent()) { + continue; + } + + $afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex); + $afterParenthesisToken = $tokens[$afterParenthesisIndex]; + + if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) { + $useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']); + $useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex); + + // add single-line edge whitespaces inside use parentheses + $this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex); + } + + // add single-line edge whitespaces inside parameters list parentheses + $this->fixParenthesisInnerEdge($tokens, $index, $endParenthesisIndex); + } + } + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('space', 'Whether to have `single` or `none` space inside parentheses.')) + ->setAllowedValues(['none', 'single']) + ->setDefault('none') + ->getOption(), + ]); + } + + /** + * Remove spaces from token at a given index. + */ + private function removeSpaceAroundToken(Tokens $tokens, int $index): void + { + $token = $tokens[$index]; + + if ($token->isWhitespace() && !str_contains($token->getContent(), "\n")) { + $tokens->clearAt($index); + } + } + + private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void + { + // fix white space before ')' + if ($tokens[$end - 1]->isWhitespace()) { + $content = $tokens[$end - 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n") && !$tokens[$tokens->getPrevNonWhitespace($end - 1)]->isComment()) { + $tokens[$end - 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($end, new Token([T_WHITESPACE, ' '])); + } + + // fix white space after '(' + if ($tokens[$start + 1]->isWhitespace()) { + $content = $tokens[$start + 1]->getContent(); + if (' ' !== $content && !str_contains($content, "\n") && !$tokens[$tokens->getNextNonWhitespace($start + 1)]->isComment()) { + $tokens[$start + 1] = new Token([T_WHITESPACE, ' ']); + } + } else { + $tokens->insertAt($start + 1, new Token([T_WHITESPACE, ' '])); + } + } + + /** + * @return list + */ + private function getBlockContent(int $startIndex, int $endIndex, Tokens $tokens): array + { + // + 1 for ( + $contents = []; + for ($i = ($startIndex + 1); $i < $endIndex; ++$i) { + $contents[] = $tokens[$i]->getContent(); + } + + return $contents; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php new file mode 100644 index 0000000000..db33fa5316 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/StatementIndentationFixer.php @@ -0,0 +1,834 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\Fixer\Indentation; +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\AlternativeSyntaxAnalyzer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * stick_comment_to_next_continuous_control_statement?: bool + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * stick_comment_to_next_continuous_control_statement: bool + * } + */ +final class StatementIndentationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + use Indentation; + + private AlternativeSyntaxAnalyzer $alternativeSyntaxAnalyzer; + + private bool $bracesFixerCompatibility; + + public function __construct(bool $bracesFixerCompatibility = false) + { + parent::__construct(); + + $this->bracesFixerCompatibility = $bracesFixerCompatibility; + } + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Each statement must be indented.', + [ + new CodeSample( + ' false] + ), + new CodeSample( + ' true] + ), + ] + ); + } + + /** + * {@inheritdoc} + * + * Must run before HeredocIndentationFixer. + * Must run after BracesPositionFixer, ClassAttributesSeparationFixer, CurlyBracesPositionFixer, FullyQualifiedStrictTypesFixer, GlobalNamespaceImportFixer, MethodArgumentSpaceFixer, NoUselessElseFixer, YieldFromArrayToYieldsFixer. + */ + public function getPriority(): int + { + return -3; + } + + public function isCandidate(Tokens $tokens): bool + { + return true; + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('stick_comment_to_next_continuous_control_statement', 'Last comment of code block counts as comment for next block.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $this->alternativeSyntaxAnalyzer = new AlternativeSyntaxAnalyzer(); + + $blockSignatureFirstTokens = [ + T_USE, + T_IF, + T_ELSE, + T_ELSEIF, + T_FOR, + T_FOREACH, + T_WHILE, + T_DO, + T_SWITCH, + T_CASE, + T_DEFAULT, + T_TRY, + T_CLASS, + T_INTERFACE, + T_TRAIT, + T_EXTENDS, + T_IMPLEMENTS, + T_CONST, + ]; + $controlStructurePossibiblyWithoutBracesTokens = [ + T_IF, + T_ELSE, + T_ELSEIF, + T_FOR, + T_FOREACH, + T_WHILE, + T_DO, + ]; + if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required + $blockSignatureFirstTokens[] = T_MATCH; + } + + $blockFirstTokens = ['{', [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], [CT::T_USE_TRAIT], [CT::T_GROUP_IMPORT_BRACE_OPEN]]; + if (\defined('T_ATTRIBUTE')) { // @TODO: drop condition when PHP 8.0+ is required + $blockFirstTokens[] = [T_ATTRIBUTE]; + } + + $endIndex = \count($tokens) - 1; + if ($tokens[$endIndex]->isWhitespace()) { + --$endIndex; + } + + $lastIndent = $this->getLineIndentationWithBracesCompatibility( + $tokens, + 0, + $this->extractIndent($this->computeNewLineContent($tokens, 0)), + ); + + /** + * @var list $scopes + */ + $scopes = [ + [ + 'type' => 'block', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => true, + 'initial_indent' => $lastIndent, + 'is_indented_block' => false, + ], + ]; + + $previousLineInitialIndent = ''; + $previousLineNewIndent = ''; + $noBracesBlockStarts = []; + $alternativeBlockStarts = []; + $caseBlockStarts = []; + + foreach ($tokens as $index => $token) { + $currentScope = \count($scopes) - 1; + + if (isset($noBracesBlockStarts[$index])) { + $scopes[] = [ + 'type' => 'block', + 'skip' => false, + 'end_index' => $this->findStatementEndIndex($tokens, $index, \count($tokens) - 1), + 'end_index_inclusive' => true, + 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), + 'is_indented_block' => true, + ]; + ++$currentScope; + } + + if ( + $token->equalsAny($blockFirstTokens) + || ($token->equals('(') && !$tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) + || isset($alternativeBlockStarts[$index]) + || isset($caseBlockStarts[$index]) + ) { + $endIndexInclusive = true; + + if ($token->isGivenKind([T_EXTENDS, T_IMPLEMENTS])) { + $endIndex = $tokens->getNextTokenOfKind($index, ['{']); + } elseif ($token->isGivenKind(CT::T_USE_TRAIT)) { + $endIndex = $tokens->getNextTokenOfKind($index, [';']); + } elseif ($token->equals(':')) { + if (isset($caseBlockStarts[$index])) { + [$endIndex, $endIndexInclusive] = $this->findCaseBlockEnd($tokens, $index); + } elseif ($this->alternativeSyntaxAnalyzer->belongsToAlternativeSyntax($tokens, $index)) { + $endIndex = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $alternativeBlockStarts[$index]); + } + } elseif ($token->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)) { + $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]]); + } elseif ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $endIndex = $tokens->getNextTokenOfKind($index, [[CT::T_GROUP_IMPORT_BRACE_CLOSE]]); + } elseif ($token->equals('{')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + } elseif ($token->equals('(')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + } else { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + } + + if ('block_signature' === $scopes[$currentScope]['type']) { + $initialIndent = $scopes[$currentScope]['initial_indent']; + } else { + $initialIndent = $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent); + } + + $skip = false; + if ($this->bracesFixerCompatibility) { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (null !== $prevIndex) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + if (null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([T_FUNCTION, T_FN])) { + $skip = true; + } + } + + $scopes[] = [ + 'type' => 'block', + 'skip' => $skip, + 'end_index' => $endIndex, + 'end_index_inclusive' => $endIndexInclusive, + 'initial_indent' => $initialIndent, + 'is_indented_block' => true, + ]; + ++$currentScope; + + while ($index >= $scopes[$currentScope]['end_index']) { + array_pop($scopes); + + --$currentScope; + } + + continue; + } + + if ( + $token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN) + || ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_ARRAY)) + ) { + $blockType = $token->equals('(') ? Tokens::BLOCK_TYPE_PARENTHESIS_BRACE : Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE; + + $scopes[] = [ + 'type' => 'statement', + 'skip' => true, + 'end_index' => $tokens->findBlockEnd($blockType, $index), + 'end_index_inclusive' => true, + 'initial_indent' => $previousLineInitialIndent, + 'new_indent' => $previousLineNewIndent, + 'is_indented_block' => false, + ]; + + continue; + } + + $isPropertyStart = $this->isPropertyStart($tokens, $index); + if ($isPropertyStart || $token->isGivenKind($blockSignatureFirstTokens)) { + $lastWhitespaceIndex = null; + $closingParenthesisIndex = null; + + for ($endIndex = $index + 1, $max = \count($tokens); $endIndex < $max; ++$endIndex) { + $endToken = $tokens[$endIndex]; + + if ($endToken->equals('(')) { + $closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); + $endIndex = $closingParenthesisIndex; + + continue; + } + + if ($endToken->equalsAny(['{', ';', [T_DOUBLE_ARROW], [T_IMPLEMENTS]])) { + break; + } + + if ($endToken->equals(':')) { + if ($token->isGivenKind([T_CASE, T_DEFAULT])) { + $caseBlockStarts[$endIndex] = $index; + } else { + $alternativeBlockStarts[$endIndex] = $index; + } + + break; + } + + if (!$token->isGivenKind($controlStructurePossibiblyWithoutBracesTokens)) { + continue; + } + + if ($endToken->isWhitespace()) { + $lastWhitespaceIndex = $endIndex; + + continue; + } + + if (!$endToken->isComment()) { + $noBraceBlockStartIndex = $lastWhitespaceIndex ?? $endIndex; + $noBracesBlockStarts[$noBraceBlockStartIndex] = true; + + $endIndex = $closingParenthesisIndex ?? $index; + + break; + } + } + + $scopes[] = [ + 'type' => 'block_signature', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => true, + 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), + 'is_indented_block' => $isPropertyStart || $token->isGivenKind([T_EXTENDS, T_IMPLEMENTS, T_CONST]), + ]; + + continue; + } + + if ($token->isGivenKind(T_FUNCTION)) { + $endIndex = $index + 1; + + for ($max = \count($tokens); $endIndex < $max; ++$endIndex) { + if ($tokens[$endIndex]->equals('(')) { + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex); + + continue; + } + + if ($tokens[$endIndex]->equalsAny(['{', ';'])) { + break; + } + } + + $scopes[] = [ + 'type' => 'block_signature', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => true, + 'initial_indent' => $this->getLineIndentationWithBracesCompatibility($tokens, $index, $lastIndent), + 'is_indented_block' => false, + ]; + + continue; + } + + if ( + $token->isWhitespace() + || ($index > 0 && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) + ) { + $previousOpenTagContent = $tokens[$index - 1]->isGivenKind(T_OPEN_TAG) + ? Preg::replace('/\S/', '', $tokens[$index - 1]->getContent()) + : ''; + + $content = $previousOpenTagContent.($token->isWhitespace() ? $token->getContent() : ''); + + if (!Preg::match('/\R/', $content)) { + continue; + } + + $nextToken = $tokens[$index + 1] ?? null; + + if ( + $this->bracesFixerCompatibility + && null !== $nextToken + && $nextToken->isComment() + && !$this->isCommentWithFixableIndentation($tokens, $index + 1) + ) { + continue; + } + + if ('block' === $scopes[$currentScope]['type'] || 'block_signature' === $scopes[$currentScope]['type']) { + $indent = false; + + if ($scopes[$currentScope]['is_indented_block']) { + $firstNonWhitespaceTokenIndex = null; + $nextNewlineIndex = null; + for ($searchIndex = $index + 1, $max = \count($tokens); $searchIndex < $max; ++$searchIndex) { + $searchToken = $tokens[$searchIndex]; + + if (!$searchToken->isWhitespace()) { + if (null === $firstNonWhitespaceTokenIndex) { + $firstNonWhitespaceTokenIndex = $searchIndex; + } + + continue; + } + + if (Preg::match('/\R/', $searchToken->getContent())) { + $nextNewlineIndex = $searchIndex; + + break; + } + } + + $endIndex = $scopes[$currentScope]['end_index']; + + if (!$scopes[$currentScope]['end_index_inclusive']) { + ++$endIndex; + } + + if ( + (null !== $firstNonWhitespaceTokenIndex && $firstNonWhitespaceTokenIndex < $endIndex) + || (null !== $nextNewlineIndex && $nextNewlineIndex < $endIndex) + ) { + if ( + // do we touch whitespace directly before comment... + $tokens[$firstNonWhitespaceTokenIndex]->isGivenKind(T_COMMENT) + // ...and afterwards, there is only comment or `}` + && $tokens[$tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('}') + ) { + if ( + // ... and the comment was only content in docblock + $tokens[$tokens->getPrevMeaningfulToken($firstNonWhitespaceTokenIndex)]->equals('{') + ) { + $indent = true; + } else { + // or it was dedicated comment for next control loop + // ^^ we need to check if there is a control group afterwards, and in that case don't make extra indent level + $nextIndex = $tokens->getNextMeaningfulToken($firstNonWhitespaceTokenIndex); + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + if (null !== $nextNextIndex && $tokens[$nextNextIndex]->isGivenKind([T_ELSE, T_ELSEIF])) { + $indent = true !== $this->configuration['stick_comment_to_next_continuous_control_statement']; + } else { + $indent = true; + } + } + } else { + $indent = true; + } + } + } + + $previousLineInitialIndent = $this->extractIndent($content); + + if ($scopes[$currentScope]['skip']) { + $whitespaces = $previousLineInitialIndent; + } else { + $whitespaces = $scopes[$currentScope]['initial_indent'].($indent ? $this->whitespacesConfig->getIndent() : ''); + } + + $content = Preg::replace( + '/(\R+)\h*$/', + '$1'.$whitespaces, + $content + ); + + $previousLineNewIndent = $this->extractIndent($content); + } else { + $content = Preg::replace( + '/(\R)'.$scopes[$currentScope]['initial_indent'].'(\h*)$/D', + '$1'.$scopes[$currentScope]['new_indent'].'$2', + $content + ); + } + + $lastIndent = $this->extractIndent($content); + + if ('' !== $previousOpenTagContent) { + $content = Preg::replace("/^{$previousOpenTagContent}/", '', $content); + } + + if ('' !== $content) { + $tokens->ensureWhitespaceAtIndex($index, 0, $content); + } elseif ($token->isWhitespace()) { + $tokens->clearAt($index); + } + + if (null !== $nextToken && $nextToken->isComment()) { + $tokens[$index + 1] = new Token([ + $nextToken->getId(), + Preg::replace( + '/(\R)'.preg_quote($previousLineInitialIndent, '/').'(\h*\S+.*)/', + '$1'.$previousLineNewIndent.'$2', + $nextToken->getContent() + ), + ]); + } + + if ($token->isWhitespace()) { + continue; + } + } + + if ($this->isNewLineToken($tokens, $index)) { + $lastIndent = $this->extractIndent($this->computeNewLineContent($tokens, $index)); + } + + while ($index >= $scopes[$currentScope]['end_index']) { + array_pop($scopes); + + if ([] === $scopes) { + return; + } + + --$currentScope; + } + + if ($token->isComment() || $token->equalsAny([';', ',', '}', [T_OPEN_TAG], [T_CLOSE_TAG], [CT::T_ATTRIBUTE_CLOSE]])) { + continue; + } + + if ('statement' !== $scopes[$currentScope]['type'] && 'block_signature' !== $scopes[$currentScope]['type']) { + $endIndex = $this->findStatementEndIndex($tokens, $index, $scopes[$currentScope]['end_index']); + + if ($endIndex === $index) { + continue; + } + + $scopes[] = [ + 'type' => 'statement', + 'skip' => false, + 'end_index' => $endIndex, + 'end_index_inclusive' => false, + 'initial_indent' => $previousLineInitialIndent, + 'new_indent' => $previousLineNewIndent, + 'is_indented_block' => true, + ]; + } + } + } + + private function findStatementEndIndex(Tokens $tokens, int $index, int $parentScopeEndIndex): int + { + $endIndex = null; + + $ifLevel = 0; + $doWhileLevel = 0; + for ($searchEndIndex = $index; $searchEndIndex <= $parentScopeEndIndex; ++$searchEndIndex) { + $searchEndToken = $tokens[$searchEndIndex]; + + if ( + $searchEndToken->isGivenKind(T_IF) + && !$tokens[$tokens->getPrevMeaningfulToken($searchEndIndex)]->isGivenKind(T_ELSE) + ) { + ++$ifLevel; + + continue; + } + + if ($searchEndToken->isGivenKind(T_DO)) { + ++$doWhileLevel; + + continue; + } + + if ($searchEndToken->equalsAny(['(', '{', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) { + if ($searchEndToken->equals('(')) { + $blockType = Tokens::BLOCK_TYPE_PARENTHESIS_BRACE; + } elseif ($searchEndToken->equals('{')) { + $blockType = Tokens::BLOCK_TYPE_CURLY_BRACE; + } else { + $blockType = Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE; + } + + $searchEndIndex = $tokens->findBlockEnd($blockType, $searchEndIndex); + $searchEndToken = $tokens[$searchEndIndex]; + } + + if (!$searchEndToken->equalsAny([';', ',', '}', [T_CLOSE_TAG]])) { + continue; + } + + $controlStructureContinuationIndex = $tokens->getNextMeaningfulToken($searchEndIndex); + + if ( + $ifLevel > 0 + && null !== $controlStructureContinuationIndex + && $tokens[$controlStructureContinuationIndex]->isGivenKind([T_ELSE, T_ELSEIF]) + ) { + if ( + $tokens[$controlStructureContinuationIndex]->isGivenKind(T_ELSE) + && !$tokens[$tokens->getNextMeaningfulToken($controlStructureContinuationIndex)]->isGivenKind(T_IF) + ) { + --$ifLevel; + } + + $searchEndIndex = $controlStructureContinuationIndex; + + continue; + } + + if ( + $doWhileLevel > 0 + && null !== $controlStructureContinuationIndex + && $tokens[$controlStructureContinuationIndex]->isGivenKind([T_WHILE]) + ) { + --$doWhileLevel; + $searchEndIndex = $controlStructureContinuationIndex; + + continue; + } + + $endIndex = $tokens->getPrevNonWhitespace($searchEndIndex); + + break; + } + + return $endIndex ?? $tokens->getPrevMeaningfulToken($parentScopeEndIndex); + } + + /** + * @return array{int, bool} + */ + private function findCaseBlockEnd(Tokens $tokens, int $index): array + { + for ($max = \count($tokens); $index < $max; ++$index) { + if ($tokens[$index]->isGivenKind(T_SWITCH)) { + $braceIndex = $tokens->getNextMeaningfulToken( + $tokens->findBlockEnd( + Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, + $tokens->getNextMeaningfulToken($index) + ) + ); + + if ($tokens[$braceIndex]->equals(':')) { + $index = $this->alternativeSyntaxAnalyzer->findAlternativeSyntaxBlockEnd($tokens, $index); + } else { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $braceIndex); + } + + continue; + } + + if ($tokens[$index]->equals('{')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); + + continue; + } + + if ($tokens[$index]->equalsAny([[T_CASE], [T_DEFAULT]])) { + return [$index, true]; + } + + if ($tokens[$index]->equalsAny(['}', [T_ENDSWITCH]])) { + return [$tokens->getPrevNonWhitespace($index), false]; + } + } + + throw new \LogicException('End of case block not found.'); + } + + private function getLineIndentationWithBracesCompatibility(Tokens $tokens, int $index, string $regularIndent): string + { + if ( + $this->bracesFixerCompatibility + && $tokens[$index]->isGivenKind(T_OPEN_TAG) + && Preg::match('/\R/', $tokens[$index]->getContent()) + && isset($tokens[$index + 1]) + && $tokens[$index + 1]->isWhitespace() + && Preg::match('/\h+$/D', $tokens[$index + 1]->getContent()) + ) { + return Preg::replace('/.*?(\h+)$/sD', '$1', $tokens[$index + 1]->getContent()); + } + + return $regularIndent; + } + + /** + * Returns whether the token at given index is the last token in a property + * declaration before the type or the name of that property. + */ + private function isPropertyStart(Tokens $tokens, int $index): bool + { + $propertyKeywords = [T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC]; + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $propertyKeywords[] = T_READONLY; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ( + null === $nextIndex + || $tokens[$nextIndex]->isGivenKind($propertyKeywords) + || $tokens[$nextIndex]->isGivenKind([T_CONST, T_FUNCTION]) + ) { + return false; + } + + while ($tokens[$index]->isGivenKind($propertyKeywords)) { + if ($tokens[$index]->isGivenKind([T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE])) { + return true; + } + + $index = $tokens->getPrevMeaningfulToken($index); + } + + return false; + } + + /** + * Returns whether the token at given index is a comment whose indentation + * can be fixed. + * + * Indentation of a comment is not changed when the comment is part of a + * multi-line message whose lines are all single-line comments and at least + * one line has meaningful content. + */ + private function isCommentWithFixableIndentation(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isComment()) { + return false; + } + + if (str_starts_with($tokens[$index]->getContent(), '/*')) { + return true; + } + + $indent = preg_quote($this->whitespacesConfig->getIndent(), '~'); + + if (Preg::match("~^(//|#)({$indent}.*)?$~", $tokens[$index]->getContent())) { + return false; + } + + $firstCommentIndex = $index; + while (true) { + $firstCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $firstCommentIndex, false); + if (null === $firstCommentCandidateIndex) { + break; + } + + $firstCommentIndex = $firstCommentCandidateIndex; + } + + $lastCommentIndex = $index; + while (true) { + $lastCommentCandidateIndex = $this->getSiblingContinuousSingleLineComment($tokens, $lastCommentIndex, true); + if (null === $lastCommentCandidateIndex) { + break; + } + + $lastCommentIndex = $lastCommentCandidateIndex; + } + + if ($firstCommentIndex === $lastCommentIndex) { + return true; + } + + for ($i = $firstCommentIndex + 1; $i < $lastCommentIndex; ++$i) { + if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isComment()) { + return false; + } + } + + return true; + } + + private function getSiblingContinuousSingleLineComment(Tokens $tokens, int $index, bool $after): ?int + { + $siblingIndex = $index; + do { + if ($after) { + $siblingIndex = $tokens->getNextTokenOfKind($siblingIndex, [[T_COMMENT]]); + } else { + $siblingIndex = $tokens->getPrevTokenOfKind($siblingIndex, [[T_COMMENT]]); + } + + if (null === $siblingIndex) { + return null; + } + } while (str_starts_with($tokens[$siblingIndex]->getContent(), '/*')); + + $newLines = 0; + for ($i = min($siblingIndex, $index) + 1, $max = max($siblingIndex, $index); $i < $max; ++$i) { + if ($tokens[$i]->isWhitespace() && Preg::match('/\R/', $tokens[$i]->getContent())) { + if (1 === $newLines || Preg::match('/\R.*\R/', $tokens[$i]->getContent())) { + return null; + } + + ++$newLines; + } + } + + return $siblingIndex; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.php new file mode 100644 index 0000000000..b07514e12d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypeDeclarationSpacesFixer.php @@ -0,0 +1,212 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Fixer\Whitespace; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; + +/** + * @author Dariusz Rumiński + * @author John Paul E. Balandan, CPA + * + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * elements?: list<'function'|'property'> + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * elements: list<'function'|'property'> + * } + */ +final class TypeDeclarationSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; + + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Ensure single space between a variable and its type declaration in function arguments and properties.', + [ + new CodeSample( + ' (string) $c; + } +} +', + ['elements' => ['function']] + ), + new CodeSample( + ' ['property']] + ), + ] + ); + } + + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isAnyTokenKindsFound([...Token::getClassyTokenKinds(), T_FN, T_FUNCTION]); + } + + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('elements', 'Structural elements where the spacing after the type declaration should be fixed.')) + ->setAllowedTypes(['string[]']) + ->setAllowedValues([new AllowedValueSubset(['function', 'property'])]) + ->setDefault(['function', 'property']) + ->getOption(), + ]); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens): void + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach (array_reverse($this->getElements($tokens), true) as $index => $type) { + if ('property' === $type && \in_array('property', $this->configuration['elements'], true)) { + $this->ensureSingleSpaceAtPropertyTypehint($tokens, $index); + + continue; + } + + if ('method' === $type && \in_array('function', $this->configuration['elements'], true)) { + $this->ensureSingleSpaceAtFunctionArgumentTypehint($functionsAnalyzer, $tokens, $index); + + // implicit continue; + } + } + } + + /** + * @return array + * + * @phpstan-return array + */ + private function getElements(Tokens $tokens): array + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + $elements = array_map( + static fn (array $element): string => $element['type'], + array_filter( + $tokensAnalyzer->getClassyElements(), + static fn (array $element): bool => \in_array($element['type'], ['method', 'property'], true) + ) + ); + + foreach ($tokens as $index => $token) { + if ( + $token->isGivenKind(T_FN) + || ($token->isGivenKind(T_FUNCTION) && !isset($elements[$index])) + ) { + $elements[$index] = 'method'; + } + } + + return $elements; + } + + private function ensureSingleSpaceAtFunctionArgumentTypehint(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index): void + { + foreach (array_reverse($functionsAnalyzer->getFunctionArguments($tokens, $index)) as $argumentInfo) { + $argumentType = $argumentInfo->getTypeAnalysis(); + + if (null === $argumentType) { + continue; + } + + $tokens->ensureWhitespaceAtIndex($argumentType->getEndIndex() + 1, 0, ' '); + } + } + + private function ensureSingleSpaceAtPropertyTypehint(Tokens $tokens, int $index): void + { + $propertyIndex = $index; + $propertyModifiers = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR]; + + if (\defined('T_READONLY')) { + $propertyModifiers[] = T_READONLY; // @TODO drop condition when PHP 8.1 is supported + } + + do { + $index = $tokens->getPrevMeaningfulToken($index); + } while (!$tokens[$index]->isGivenKind($propertyModifiers)); + + $propertyType = $this->collectTypeAnalysis($tokens, $index, $propertyIndex); + + if (null === $propertyType) { + return; + } + + $tokens->ensureWhitespaceAtIndex($propertyType->getEndIndex() + 1, 0, ' '); + } + + private function collectTypeAnalysis(Tokens $tokens, int $startIndex, int $endIndex): ?TypeAnalysis + { + $type = ''; + $typeStartIndex = $tokens->getNextMeaningfulToken($startIndex); + $typeEndIndex = $typeStartIndex; + + for ($i = $typeStartIndex; $i < $endIndex; ++$i) { + if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) { + continue; + } + + $type .= $tokens[$i]->getContent(); + $typeEndIndex = $i; + } + + return '' !== $type ? new TypeAnalysis($type, $typeStartIndex, $typeEndIndex) : null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php index fb59a649ee..538c221fe3 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Fixer/Whitespace/TypesSpacesFixer.php @@ -16,6 +16,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\Fixer\ConfigurableFixerTrait; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; @@ -29,24 +30,27 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; +/** + * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> + * + * @phpstan-type _AutogeneratedInputConfiguration array{ + * space?: 'none'|'single', + * space_multiple_catch?: 'none'|'single'|null + * } + * @phpstan-type _AutogeneratedComputedConfiguration array{ + * space: 'none'|'single', + * space_multiple_catch: 'none'|'single'|null + * } + */ final class TypesSpacesFixer extends AbstractFixer implements ConfigurableFixerInterface { - public function configure(array $configuration): void - { - parent::configure($configuration); + /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */ + use ConfigurableFixerTrait; - if (!isset($this->configuration['space_multiple_catch'])) { - $this->configuration['space_multiple_catch'] = $this->configuration['space']; - } - } - - /** - * {@inheritdoc} - */ public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( - 'A single space or none should be around union type operator.', + 'A single space or none should be around union type and intersection type operators.', [ new CodeSample( "isAnyTokenKindsFound([CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]); } - /** - * {@inheritdoc} - */ + protected function configurePostNormalisation(): void + { + if (!isset($this->configuration['space_multiple_catch'])) { + $this->configuration = [ + 'space' => $this->configuration['space'], + 'space_multiple_catch' => $this->configuration['space'], + ]; + } + } + protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('space', 'spacing to apply around union type operator.')) + (new FixerOptionBuilder('space', 'Spacing to apply around union type and intersection type operators.')) ->setAllowedValues(['none', 'single']) ->setDefault('none') ->getOption(), - (new FixerOptionBuilder('space_multiple_catch', 'spacing to apply around type operator when catching exceptions of multiple types, use `null` to follow the value configured for `space`.')) + (new FixerOptionBuilder('space_multiple_catch', 'Spacing to apply around type operator when catching exceptions of multiple types, use `null` to follow the value configured for `space`.')) ->setAllowedValues(['none', 'single', null]) ->setDefault(null) ->getOption(), @@ -139,7 +157,7 @@ private function ensureSingleSpace(Tokens $tokens, int $index, int $offset): int return 1; } - if (' ' !== $tokens[$index]->getContent() && 1 !== Preg::match('/\R/', $tokens[$index]->getContent())) { + if (' ' !== $tokens[$index]->getContent() && !Preg::match('/\R/', $tokens[$index]->getContent())) { $tokens[$index] = new Token([T_WHITESPACE, ' ']); } @@ -148,7 +166,7 @@ private function ensureSingleSpace(Tokens $tokens, int $index, int $offset): int private function ensureNoSpace(Tokens $tokens, int $index): void { - if ($tokens[$index]->isWhitespace() && 1 !== Preg::match('/\R/', $tokens[$index]->getContent())) { + if ($tokens[$index]->isWhitespace() && !Preg::match('/\R/', $tokens[$index]->getContent())) { $tokens->clearAt($index); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php index 8170e9ef03..65a7a3b3cf 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOption.php @@ -36,57 +36,36 @@ public function getAlias(): string return $this->alias; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->fixerOption->getName(); } - /** - * {@inheritdoc} - */ public function getDescription(): string { return $this->fixerOption->getDescription(); } - /** - * {@inheritdoc} - */ public function hasDefault(): bool { return $this->fixerOption->hasDefault(); } - /** - * {@inheritdoc} - */ public function getDefault() { return $this->fixerOption->getDefault(); } - /** - * {@inheritdoc} - */ public function getAllowedTypes(): ?array { return $this->fixerOption->getAllowedTypes(); } - /** - * {@inheritdoc} - */ public function getAllowedValues(): ?array { return $this->fixerOption->getAllowedValues(); } - /** - * {@inheritdoc} - */ public function getNormalizer(): ?\Closure { return $this->fixerOption->getNormalizer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php index 9ba11c8478..13bb3faae7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AliasedFixerOptionBuilder.php @@ -42,7 +42,7 @@ public function setDefault($default): self } /** - * @param string[] $allowedTypes + * @param list $allowedTypes */ public function setAllowedTypes(array $allowedTypes): self { @@ -51,6 +51,9 @@ public function setAllowedTypes(array $allowedTypes): self return $this; } + /** + * @param list $allowedValues + */ public function setAllowedValues(array $allowedValues): self { $this->optionBuilder->setAllowedValues($allowedValues); diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php index 3317605a7b..fad56516e8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/AllowedValueSubset.php @@ -20,13 +20,17 @@ final class AllowedValueSubset { /** - * @var array + * @var list */ - private $allowedValues; + private array $allowedValues; + /** + * @param list $allowedValues + */ public function __construct(array $allowedValues) { $this->allowedValues = $allowedValues; + sort($this->allowedValues, SORT_FLAG_CASE | SORT_STRING); } /** @@ -49,7 +53,10 @@ public function __invoke($values): bool return true; } - public function getAllowedValues(): ?array + /** + * @return list + */ + public function getAllowedValues(): array { return $this->allowedValues; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php index 607aba397c..7224a673db 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/DeprecatedFixerOption.php @@ -26,57 +26,36 @@ public function __construct(FixerOptionInterface $option, string $deprecationMes $this->deprecationMessage = $deprecationMessage; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->option->getName(); } - /** - * {@inheritdoc} - */ public function getDescription(): string { return $this->option->getDescription(); } - /** - * {@inheritdoc} - */ public function hasDefault(): bool { return $this->option->hasDefault(); } - /** - * {@inheritdoc} - */ public function getDefault() { return $this->option->getDefault(); } - /** - * {@inheritdoc} - */ public function getAllowedTypes(): ?array { return $this->option->getAllowedTypes(); } - /** - * {@inheritdoc} - */ public function getAllowedValues(): ?array { return $this->option->getAllowedValues(); } - /** - * {@inheritdoc} - */ public function getNormalizer(): ?\Closure { return $this->option->getNormalizer(); diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php index 5372347338..4615b69314 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolver.php @@ -14,6 +14,7 @@ namespace PhpCsFixer\FixerConfiguration; +use PhpCsFixer\Preg; use PhpCsFixer\Utils; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -21,12 +22,12 @@ final class FixerConfigurationResolver implements FixerConfigurationResolverInterface { /** - * @var FixerOptionInterface[] + * @var list */ private array $options = []; /** - * @var string[] + * @var list */ private array $registeredNames = []; @@ -35,7 +36,9 @@ final class FixerConfigurationResolver implements FixerConfigurationResolverInte */ public function __construct(iterable $options) { - foreach ($options as $option) { + $fixerOptionSorter = new FixerOptionSorter(); + + foreach ($fixerOptionSorter->sort($options) as $option) { $this->addOption($option); } @@ -44,17 +47,11 @@ public function __construct(iterable $options) } } - /** - * {@inheritdoc} - */ public function getOptions(): array { return $this->options; } - /** - * {@inheritdoc} - */ public function resolve(array $configuration): array { $resolver = new OptionsResolver(); @@ -67,10 +64,10 @@ public function resolve(array $configuration): array if (\array_key_exists($alias, $configuration)) { if (\array_key_exists($name, $configuration)) { - throw new InvalidOptionsException(sprintf('Aliased option "%s"/"%s" is passed multiple times.', $name, $alias)); + throw new InvalidOptionsException(\sprintf('Aliased option "%s"/"%s" is passed multiple times.', $name, $alias)); } - Utils::triggerDeprecation(new \RuntimeException(sprintf( + Utils::triggerDeprecation(new \RuntimeException(\sprintf( 'Option "%s" is deprecated, use "%s" instead.', $alias, $name @@ -91,9 +88,7 @@ public function resolve(array $configuration): array if (null !== $allowedValues) { foreach ($allowedValues as &$allowedValue) { if (\is_object($allowedValue) && \is_callable($allowedValue)) { - $allowedValue = static function (/* mixed */ $values) use ($allowedValue) { - return $allowedValue($values); - }; + $allowedValue = static fn (/* mixed */ $values) => $allowedValue($values); } } @@ -102,7 +97,28 @@ public function resolve(array $configuration): array $allowedTypes = $option->getAllowedTypes(); if (null !== $allowedTypes) { - $resolver->setAllowedTypes($name, $allowedTypes); + // Symfony OptionsResolver doesn't support `array` natively, let's simplify the type + $allowedTypesNormalised = array_map( + static function (string $type): string { + $matches = []; + if (true === Preg::match('/array<\w+,\s*(\??[\w\'|]+)>/', $type, $matches)) { + if ('?' === $matches[1][0]) { + return 'array'; + } + + if ("'" === $matches[1][0]) { + return 'string[]'; + } + + return $matches[1].'[]'; + } + + return $type; + }, + $allowedTypes, + ); + + $resolver->setAllowedTypes($name, $allowedTypesNormalised); } $normalizer = $option->getNormalizer(); @@ -122,7 +138,7 @@ private function addOption(FixerOptionInterface $option): void $name = $option->getName(); if (\in_array($name, $this->registeredNames, true)) { - throw new \LogicException(sprintf('The "%s" option is defined multiple times.', $name)); + throw new \LogicException(\sprintf('The "%s" option is defined multiple times.', $name)); } $this->options[] = $option; diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php index d0932ace80..284cdbd3dd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerConfigurationResolverInterface.php @@ -17,7 +17,7 @@ interface FixerConfigurationResolverInterface { /** - * @return FixerOptionInterface[] + * @return list */ public function getOptions(): array; diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php index de75e8098c..2866bdc1b8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOption.php @@ -16,15 +16,11 @@ final class FixerOption implements FixerOptionInterface { - /** - * @var string - */ - private $name; + private string $name; - /** - * @var string - */ - private $description; + private string $description; + + private bool $isRequired; /** * @var mixed @@ -32,17 +28,12 @@ final class FixerOption implements FixerOptionInterface private $default; /** - * @var bool - */ - private $isRequired; - - /** - * @var null|string[] + * @var null|list */ private $allowedTypes; /** - * @var null|array + * @var null|list */ private $allowedValues; @@ -52,8 +43,9 @@ final class FixerOption implements FixerOptionInterface private $normalizer; /** - * @param mixed $default - * @param null|string[] $allowedTypes + * @param mixed $default + * @param null|list $allowedTypes + * @param null|list $allowedValues */ public function __construct( string $name, @@ -88,33 +80,21 @@ public function __construct( } } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ public function getDescription(): string { return $this->description; } - /** - * {@inheritdoc} - */ public function hasDefault(): bool { return !$this->isRequired; } - /** - * {@inheritdoc} - */ public function getDefault() { if (!$this->hasDefault()) { @@ -124,25 +104,16 @@ public function getDefault() return $this->default; } - /** - * {@inheritdoc} - */ public function getAllowedTypes(): ?array { return $this->allowedTypes; } - /** - * {@inheritdoc} - */ public function getAllowedValues(): ?array { return $this->allowedValues; } - /** - * {@inheritdoc} - */ public function getNormalizer(): ?\Closure { return $this->normalizer; diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php index 2dd6e588e5..307518dca8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionBuilder.php @@ -28,12 +28,12 @@ final class FixerOptionBuilder private bool $isRequired = true; /** - * @var null|string[] + * @var null|list */ private $allowedTypes; /** - * @var null|array + * @var null|list */ private $allowedValues; @@ -67,7 +67,7 @@ public function setDefault($default): self } /** - * @param string[] $allowedTypes + * @param list $allowedTypes * * @return $this */ @@ -79,6 +79,8 @@ public function setAllowedTypes(array $allowedTypes): self } /** + * @param list $allowedValues + * * @return $this */ public function setAllowedValues(array $allowedValues): self diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php index f7bfaebd02..d760e4df90 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionInterface.php @@ -23,17 +23,20 @@ public function getDescription(): string; public function hasDefault(): bool; /** - * @throws \LogicException when no default value is defined - * * @return mixed + * + * @throws \LogicException when no default value is defined */ public function getDefault(); /** - * @return null|string[] + * @return null|list */ public function getAllowedTypes(): ?array; + /** + * @return null|list + */ public function getAllowedValues(): ?array; public function getNormalizer(): ?\Closure; diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionSorter.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionSorter.php new file mode 100644 index 0000000000..bb5268748a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/FixerOptionSorter.php @@ -0,0 +1,37 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\FixerConfiguration; + +/** + * @internal + */ +final class FixerOptionSorter +{ + /** + * @param iterable $options + * + * @return list + */ + public function sort(iterable $options): array + { + if (!\is_array($options)) { + $options = iterator_to_array($options, false); + } + + usort($options, static fn (FixerOptionInterface $a, FixerOptionInterface $b): int => $a->getName() <=> $b->getName()); + + return $options; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php index e1957ac611..4c12d5da6a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerConfiguration/InvalidOptionsForEnvException.php @@ -21,6 +21,4 @@ * * @internal */ -final class InvalidOptionsForEnvException extends InvalidOptionsException -{ -} +final class InvalidOptionsForEnvException extends InvalidOptionsException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php index a0f7c9f4c5..b0dd2fedaf 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSample.php @@ -22,10 +22,13 @@ final class CodeSample implements CodeSampleInterface private string $code; /** - * @var null|array + * @var null|array */ - private $configuration; + private ?array $configuration; + /** + * @param null|array $configuration + */ public function __construct(string $code, ?array $configuration = null) { $this->code = $code; diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php index 7d4971f4c9..9bce5eb8b4 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/CodeSampleInterface.php @@ -21,5 +21,8 @@ interface CodeSampleInterface { public function getCode(): string; + /** + * @return null|array + */ public function getConfiguration(): ?array; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php index 10698c10b8..effd0e5297 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FileSpecificCodeSample.php @@ -25,6 +25,9 @@ final class FileSpecificCodeSample implements FileSpecificCodeSampleInterface private \SplFileInfo $splFileInfo; + /** + * @param null|array $configuration + */ public function __construct( string $code, \SplFileInfo $splFileInfo, @@ -34,25 +37,16 @@ public function __construct( $this->splFileInfo = $splFileInfo; } - /** - * {@inheritdoc} - */ public function getCode(): string { return $this->codeSample->getCode(); } - /** - * {@inheritdoc} - */ public function getConfiguration(): ?array { return $this->codeSample->getConfiguration(); } - /** - * {@inheritdoc} - */ public function getSplFileInfo(): \SplFileInfo { return $this->splFileInfo; diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php index 8c16a7211a..f4ae915970 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinition.php @@ -22,23 +22,23 @@ final class FixerDefinition implements FixerDefinitionInterface private string $summary; /** - * @var CodeSampleInterface[] + * @var list */ private array $codeSamples; /** - * @var null|string + * Description of Fixer and benefit of using it. */ - private $description; + private ?string $description; /** - * @var null|string + * Description why Fixer is risky. */ - private $riskyDescription; + private ?string $riskyDescription; /** - * @param CodeSampleInterface[] $codeSamples array of samples, where single sample is [code, configuration] - * @param null|string $riskyDescription null for non-risky fixer + * @param list $codeSamples array of samples, where single sample is [code, configuration] + * @param null|string $riskyDescription null for non-risky fixer */ public function __construct( string $summary, diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php index 85c22824b3..5bc2f934a2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/FixerDefinitionInterface.php @@ -31,7 +31,7 @@ public function getRiskyDescription(): ?string; /** * Array of samples, where single sample is [code, configuration]. * - * @return CodeSampleInterface[] + * @return list */ public function getCodeSamples(): array; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php index e74eebd610..f4b981112c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecificCodeSample.php @@ -23,6 +23,9 @@ final class VersionSpecificCodeSample implements VersionSpecificCodeSampleInterf private VersionSpecificationInterface $versionSpecification; + /** + * @param null|array $configuration + */ public function __construct( string $code, VersionSpecificationInterface $versionSpecification, @@ -32,25 +35,16 @@ public function __construct( $this->versionSpecification = $versionSpecification; } - /** - * {@inheritdoc} - */ public function getCode(): string { return $this->codeSample->getCode(); } - /** - * {@inheritdoc} - */ public function getConfiguration(): ?array { return $this->codeSample->getConfiguration(); } - /** - * {@inheritdoc} - */ public function isSuitableFor(int $version): bool { return $this->versionSpecification->isSatisfiedBy($version); diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php index d28cae6913..2910fe7285 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerDefinition/VersionSpecification.php @@ -20,16 +20,19 @@ final class VersionSpecification implements VersionSpecificationInterface { /** - * @var null|int + * @var null|int<1, max> */ - private $minimum; + private ?int $minimum; /** - * @var null|int + * @var null|int<1, max> */ - private $maximum; + private ?int $maximum; /** + * @param null|int<1, max> $minimum + * @param null|int<1, max> $maximum + * * @throws \InvalidArgumentException */ public function __construct(?int $minimum = null, ?int $maximum = null) @@ -56,9 +59,6 @@ public function __construct(?int $minimum = null, ?int $maximum = null) $this->maximum = $maximum; } - /** - * {@inheritdoc} - */ public function isSatisfiedBy(int $version): bool { if (null !== $this->minimum && $version < $this->minimum) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php b/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php index 3d356334f9..8b4223964c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerFactory.php @@ -40,12 +40,12 @@ final class FixerFactory private FixerNameValidator $nameValidator; /** - * @var FixerInterface[] + * @var list */ private array $fixers = []; /** - * @var FixerInterface[] Associative array of fixers with names as keys + * @var array */ private array $fixersByName = []; @@ -66,7 +66,7 @@ public function setWhitespacesConfig(WhitespacesFixerConfig $config): self } /** - * @return FixerInterface[] + * @return list */ public function getFixers(): array { @@ -83,25 +83,35 @@ public function registerBuiltInFixers(): self static $builtInFixers = null; if (null === $builtInFixers) { + /** @var list> */ $builtInFixers = []; + $finder = SymfonyFinder::create()->files() + ->in(__DIR__.'/Fixer') + ->exclude(['Internal']) + ->name('*Fixer.php') + ->depth(1) + ; + /** @var SplFileInfo $file */ - foreach (SymfonyFinder::create()->files()->in(__DIR__.'/Fixer')->name('*Fixer.php')->depth(1) as $file) { + foreach ($finder as $file) { $relativeNamespace = $file->getRelativePath(); - $fixerClass = 'PhpCsFixer\\Fixer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); + $fixerClass = 'PhpCsFixer\Fixer\\'.('' !== $relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); $builtInFixers[] = $fixerClass; } } foreach ($builtInFixers as $class) { - $this->registerFixer(new $class(), false); + /** @var FixerInterface */ + $fixer = new $class(); + $this->registerFixer($fixer, false); } return $this; } /** - * @param FixerInterface[] $fixers + * @param iterable $fixers * * @return $this */ @@ -122,11 +132,11 @@ public function registerFixer(FixerInterface $fixer, bool $isCustom): self $name = $fixer->getName(); if (isset($this->fixersByName[$name])) { - throw new \UnexpectedValueException(sprintf('Fixer named "%s" is already registered.', $name)); + throw new \UnexpectedValueException(\sprintf('Fixer named "%s" is already registered.', $name)); } if (!$this->nameValidator->isValid($name, $isCustom)) { - throw new \UnexpectedValueException(sprintf('Fixer named "%s" has invalid name.', $name)); + throw new \UnexpectedValueException(\sprintf('Fixer named "%s" has invalid name.', $name)); } $this->fixers[] = $fixer; @@ -149,7 +159,7 @@ public function useRuleSet(RuleSetInterface $ruleSet): self $fixerNames = array_keys($ruleSet->getRules()); foreach ($fixerNames as $name) { if (!\array_key_exists($name, $this->fixersByName)) { - throw new \UnexpectedValueException(sprintf('Rule "%s" does not exist.', $name)); + throw new \UnexpectedValueException(\sprintf('Rule "%s" does not exist.', $name)); } $fixer = $this->fixersByName[$name]; @@ -195,11 +205,15 @@ public function hasRule(string $name): bool } /** - * @return null|string[] + * @return list */ - private function getFixersConflicts(FixerInterface $fixer): ?array + private function getFixersConflicts(FixerInterface $fixer): array { static $conflictMap = [ + 'blank_lines_before_namespace' => [ + 'no_blank_lines_before_namespace', + 'single_blank_line_before_namespace', + ], 'no_blank_lines_before_namespace' => ['single_blank_line_before_namespace'], 'single_import_per_statement' => ['group_import'], ]; @@ -210,7 +224,7 @@ private function getFixersConflicts(FixerInterface $fixer): ?array } /** - * @param array $fixerConflicts + * @param array> $fixerConflicts */ private function generateConflictMessage(array $fixerConflicts): string { @@ -221,13 +235,11 @@ private function generateConflictMessage(array $fixerConflicts): string // filter mutual conflicts $report[$fixer] = array_filter( $fixers, - static function (string $candidate) use ($report, $fixer): bool { - return !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true); - } + static fn (string $candidate): bool => !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true) ); if (\count($report[$fixer]) > 0) { - $message .= sprintf("\n- \"%s\" with \"%s\"", $fixer, implode('", "', $report[$fixer])); + $message .= \sprintf("\n- \"%s\" with %s", $fixer, Utils::naturalLanguageJoin($report[$fixer])); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php b/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php index 27ebed5dc5..c9e9cb9fb9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerFileProcessedEvent.php @@ -30,7 +30,6 @@ final class FixerFileProcessedEvent extends Event */ public const NAME = 'fixer.file_processed'; - public const STATUS_UNKNOWN = 0; public const STATUS_INVALID = 1; public const STATUS_SKIPPED = 2; public const STATUS_NO_CHANGES = 3; @@ -40,13 +39,28 @@ final class FixerFileProcessedEvent extends Event private int $status; - public function __construct(int $status) + private ?string $fileRelativePath; + private ?string $fileHash; + + public function __construct(int $status, ?string $fileRelativePath = null, ?string $fileHash = null) { $this->status = $status; + $this->fileRelativePath = $fileRelativePath; + $this->fileHash = $fileHash; } public function getStatus(): int { return $this->status; } + + public function getFileRelativePath(): ?string + { + return $this->fileRelativePath; + } + + public function getFileHash(): ?string + { + return $this->fileHash; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php b/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php index ce2697d3e8..8f19cb06cd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/FixerNameValidator.php @@ -24,9 +24,9 @@ final class FixerNameValidator public function isValid(string $name, bool $isCustom): bool { if (!$isCustom) { - return 1 === Preg::match('/^[a-z][a-z0-9_]*$/', $name); + return Preg::match('/^[a-z][a-z0-9_]*$/', $name); } - return 1 === Preg::match('/^[A-Z][a-zA-Z0-9]*\/[a-z][a-z0-9_]*$/', $name); + return Preg::match('/^[A-Z][a-zA-Z0-9]*\/[a-z][a-z0-9_]*$/', $name); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php b/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php index 0666d6df5c..ed82aaa2ab 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Indicator/PhpUnitTestCaseIndicator.php @@ -25,7 +25,7 @@ final class PhpUnitTestCaseIndicator public function isPhpUnitClass(Tokens $tokens, int $index): bool { if (!$tokens[$index]->isGivenKind(T_CLASS)) { - throw new \LogicException(sprintf('No "T_CLASS" at given index %d, got "%s".', $index, $tokens[$index]->getName())); + throw new \LogicException(\sprintf('No "T_CLASS" at given index %d, got "%s".', $index, $tokens[$index]->getName())); } $index = $tokens->getNextMeaningfulToken($index); @@ -40,7 +40,7 @@ public function isPhpUnitClass(Tokens $tokens, int $index): bool return false; } - if (0 !== Preg::match('/(?:Test|TestCase)$/', $tokens[$index]->getContent())) { + if (Preg::match('/(?:Test|TestCase)$/', $tokens[$index]->getContent())) { return true; } @@ -53,7 +53,7 @@ public function isPhpUnitClass(Tokens $tokens, int $index): bool continue; // not part of extends nor part of implements; so continue } - if (0 !== Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { + if (Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { return true; } } @@ -62,9 +62,14 @@ public function isPhpUnitClass(Tokens $tokens, int $index): bool } /** - * @return \Generator array of [int start, int end] indices from sooner to later classes + * Returns an indices of PHPUnit classes in reverse appearance order. + * Order is important - it's reverted, so if we inject tokens into collection, + * we do it for bottom of file first, and then to the top of the file, so we + * mitigate risk of not visiting whole collections (final indices). + * + * @return iterable array of [int start, int end] indices from later to earlier classes */ - public function findPhpUnitClasses(Tokens $tokens): \Generator + public function findPhpUnitClasses(Tokens $tokens): iterable { for ($index = $tokens->count() - 1; $index > 0; --$index) { if (!$tokens[$index]->isGivenKind(T_CLASS) || !$this->isPhpUnitClass($tokens, $index)) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php index 39ff6daa9e..b859060067 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/CachingLinter.php @@ -24,7 +24,7 @@ final class CachingLinter implements LinterInterface private LinterInterface $sublinter; /** - * @var array + * @var array */ private array $cache = []; @@ -33,20 +33,14 @@ public function __construct(LinterInterface $linter) $this->sublinter = $linter; } - /** - * {@inheritdoc} - */ public function isAsync(): bool { return $this->sublinter->isAsync(); } - /** - * {@inheritdoc} - */ public function lintFile(string $path): LintingResultInterface { - $checksum = crc32(file_get_contents($path)); + $checksum = md5(file_get_contents($path)); if (!isset($this->cache[$checksum])) { $this->cache[$checksum] = $this->sublinter->lintFile($path); @@ -55,12 +49,9 @@ public function lintFile(string $path): LintingResultInterface return $this->cache[$checksum]; } - /** - * {@inheritdoc} - */ public function lintSource(string $source): LintingResultInterface { - $checksum = crc32($source); + $checksum = md5($source); if (!isset($this->cache[$checksum])) { $this->cache[$checksum] = $this->sublinter->lintSource($source); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php index 4139e1e182..b8619b4473 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/Linter.php @@ -30,25 +30,16 @@ public function __construct() $this->subLinter = new TokenizerLinter(); } - /** - * {@inheritdoc} - */ public function isAsync(): bool { return $this->subLinter->isAsync(); } - /** - * {@inheritdoc} - */ public function lintFile(string $path): LintingResultInterface { return $this->subLinter->lintFile($path); } - /** - * {@inheritdoc} - */ public function lintSource(string $source): LintingResultInterface { return $this->subLinter->lintSource($source); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php index eb24dda8ff..1874bac1ed 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/LintingException.php @@ -18,8 +18,7 @@ * @author Dariusz Rumiński * * @final + * * @TODO 4.0 make class "final" */ -class LintingException extends \RuntimeException -{ -} +class LintingException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php index 2ec206aa18..67fbd9391a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLinter.php @@ -29,15 +29,9 @@ */ final class ProcessLinter implements LinterInterface { - /** - * @var FileRemoval - */ - private $fileRemoval; + private FileRemoval $fileRemoval; - /** - * @var ProcessLinterProcessBuilder - */ - private $processBuilder; + private ProcessLinterProcessBuilder $processBuilder; /** * Temporary file for code linting. @@ -90,7 +84,7 @@ public function __destruct() */ public function __sleep(): array { - throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + throw new \BadMethodCallException('Cannot serialize '.self::class); } /** @@ -101,28 +95,19 @@ public function __sleep(): array */ public function __wakeup(): void { - throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + throw new \BadMethodCallException('Cannot unserialize '.self::class); } - /** - * {@inheritdoc} - */ public function isAsync(): bool { return true; } - /** - * {@inheritdoc} - */ public function lintFile(string $path): LintingResultInterface { return new ProcessLintingResult($this->createProcessForFile($path), $path); } - /** - * {@inheritdoc} - */ public function lintSource(string $source): LintingResultInterface { return new ProcessLintingResult($this->createProcessForSource($source), $this->temporaryFile); @@ -158,7 +143,7 @@ private function createProcessForSource(string $source): Process } if (false === @file_put_contents($this->temporaryFile, $source)) { - throw new IOException(sprintf('Failed to write file "%s".', $this->temporaryFile), 0, null, $this->temporaryFile); + throw new IOException(\sprintf('Failed to write file "%s".', $this->temporaryFile), 0, null, $this->temporaryFile); } return $this->createProcessForFile($this->temporaryFile); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php index 808f7959b9..bbdd78c97b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/ProcessLintingResult.php @@ -23,20 +23,11 @@ */ final class ProcessLintingResult implements LintingResultInterface { - /** - * @var bool - */ - private $isSuccessful; + private Process $process; - /** - * @var Process - */ - private $process; + private ?string $path; - /** - * @var null|string - */ - private $path; + private ?bool $isSuccessful = null; public function __construct(Process $process, ?string $path = null) { @@ -44,9 +35,6 @@ public function __construct(Process $process, ?string $path = null) $this->path = $path; } - /** - * {@inheritdoc} - */ public function check(): void { if (!$this->isSuccessful()) { @@ -57,32 +45,33 @@ public function check(): void private function getProcessErrorMessage(): string { - $output = strtok(ltrim($this->process->getErrorOutput() ?: $this->process->getOutput()), "\n"); + $errorOutput = $this->process->getErrorOutput(); + $output = strtok(ltrim('' !== $errorOutput ? $errorOutput : $this->process->getOutput()), "\n"); if (false === $output) { return 'Fatal error: Unable to lint file.'; } if (null !== $this->path) { - $needle = sprintf('in %s ', $this->path); + $needle = \sprintf('in %s ', $this->path); $pos = strrpos($output, $needle); if (false !== $pos) { - $output = sprintf('%s%s', substr($output, 0, $pos), substr($output, $pos + \strlen($needle))); + $output = \sprintf('%s%s', substr($output, 0, $pos), substr($output, $pos + \strlen($needle))); } } $prefix = substr($output, 0, 18); if ('PHP Parse error: ' === $prefix) { - return sprintf('Parse error: %s.', substr($output, 18)); + return \sprintf('Parse error: %s.', substr($output, 18)); } if ('PHP Fatal error: ' === $prefix) { - return sprintf('Fatal error: %s.', substr($output, 18)); + return \sprintf('Fatal error: %s.', substr($output, 18)); } - return sprintf('%s.', $output); + return \sprintf('%s.', $output); } private function isSuccessful(): bool diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php index 8b70421c82..b0920d302e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLinter.php @@ -27,25 +27,16 @@ */ final class TokenizerLinter implements LinterInterface { - /** - * {@inheritdoc} - */ public function isAsync(): bool { return false; } - /** - * {@inheritdoc} - */ public function lintFile(string $path): LintingResultInterface { return $this->lintSource(FileReader::createSingleton()->read($path)); } - /** - * {@inheritdoc} - */ public function lintSource(string $source): LintingResultInterface { try { @@ -58,9 +49,7 @@ public function lintSource(string $source): LintingResultInterface Tokens::fromCode($source); return new TokenizerLintingResult(); - } catch (\ParseError $e) { - return new TokenizerLintingResult($e); - } catch (\CompileError $e) { + } catch (\CompileError|\ParseError $e) { return new TokenizerLintingResult($e); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php index ea49e491e8..01fb04a568 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/TokenizerLintingResult.php @@ -21,24 +21,18 @@ */ final class TokenizerLintingResult implements LintingResultInterface { - /** - * @var null|\Error - */ - private $error; + private ?\Error $error; public function __construct(?\Error $error = null) { $this->error = $error; } - /** - * {@inheritdoc} - */ public function check(): void { if (null !== $this->error) { throw new LintingException( - sprintf('%s: %s on line %d.', $this->getMessagePrefix(), $this->error->getMessage(), $this->error->getLine()), + \sprintf('%s: %s on line %d.', $this->getMessagePrefix(), $this->error->getMessage(), $this->error->getLine()), $this->error->getCode(), $this->error ); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php b/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php index 32e2765213..df67b5f0bc 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Linter/UnavailableLinterException.php @@ -20,8 +20,7 @@ * @author Dariusz Rumiński * * @final + * * @TODO 4.0 make class "final" */ -class UnavailableLinterException extends \RuntimeException -{ -} +class UnavailableLinterException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/ParallelAwareConfigInterface.php b/vendor/friendsofphp/php-cs-fixer/src/ParallelAwareConfigInterface.php new file mode 100644 index 0000000000..6716cf3ce1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/ParallelAwareConfigInterface.php @@ -0,0 +1,29 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer; + +use PhpCsFixer\Runner\Parallel\ParallelConfig; + +/** + * @author Greg Korba + * + * @TODO 4.0 Include parallel runner config in main ConfigInterface + */ +interface ParallelAwareConfigInterface extends ConfigInterface +{ + public function getParallelConfig(): ParallelConfig; + + public function setParallelConfig(ParallelConfig $config): ConfigInterface; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php b/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php index fb352420f5..b7cb356c18 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php +++ b/vendor/friendsofphp/php-cs-fixer/src/PharChecker.php @@ -19,9 +19,6 @@ */ final class PharChecker implements PharCheckerInterface { - /** - * {@inheritdoc} - */ public function checkFileValidity(string $filename): ?string { try { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Preg.php b/vendor/friendsofphp/php-cs-fixer/src/Preg.php index 6216e01e07..a88db938f2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Preg.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Preg.php @@ -25,27 +25,63 @@ final class Preg { /** - * @param null|string[] $matches + * @param array $matches + * @param int-mask $flags + * + * @param-out ($flags is PREG_OFFSET_CAPTURE + * ? array + * : ($flags is PREG_UNMATCHED_AS_NULL + * ? array + * : ($flags is int-mask&768 + * ? array + * : array + * ) + * ) + * ) $matches * * @throws PregException */ - public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int + public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { $result = @preg_match(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset); if (false !== $result && PREG_NO_ERROR === preg_last_error()) { - return $result; + return 1 === $result; } $result = @preg_match(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset); if (false !== $result && PREG_NO_ERROR === preg_last_error()) { - return $result; + return 1 === $result; } - throw self::newPregException(preg_last_error(), __METHOD__, (array) $pattern); + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern); } /** - * @param null|string[] $matches + * @param array $matches + * @param int-mask $flags + * + * @param-out ($flags is PREG_PATTERN_ORDER + * ? array> + * : ($flags is PREG_SET_ORDER + * ? list> + * : ($flags is int-mask&(256|257) + * ? array> + * : ($flags is int-mask&258 + * ? list> + * : ($flags is int-mask&(512|513) + * ? array> + * : ($flags is int-mask&514 + * ? list> + * : ($flags is int-mask&770 + * ? list> + * : ($flags is 0 ? array> : array) + * ) + * ) + * ) + * ) + * ) + * ) + * ) $matches * * @throws PregException */ @@ -61,11 +97,13 @@ public static function matchAll(string $pattern, string $subject, ?array &$match return $result; } - throw self::newPregException(preg_last_error(), __METHOD__, (array) $pattern); + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern); } /** - * @param string|string[] $subject + * @param array|string $subject + * + * @param-out int $count * * @throws PregException */ @@ -81,10 +119,12 @@ public static function replace(string $pattern, string $replacement, $subject, i return $result; } - throw self::newPregException(preg_last_error(), __METHOD__, (array) $pattern); + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern); } /** + * @param-out int $count + * * @throws PregException */ public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = -1, ?int &$count = null): string @@ -99,13 +139,13 @@ public static function replaceCallback(string $pattern, callable $callback, stri return $result; } - throw self::newPregException(preg_last_error(), __METHOD__, (array) $pattern); + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern); } /** - * @throws PregException + * @return list * - * @return string[] + * @throws PregException */ public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array { @@ -119,34 +159,16 @@ public static function split(string $pattern, string $subject, int $limit = -1, return $result; } - throw self::newPregException(preg_last_error(), __METHOD__, (array) $pattern); + throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern); } - /** - * @param string|string[] $pattern - * - * @return string|string[] - */ - private static function addUtf8Modifier($pattern) + private static function addUtf8Modifier(string $pattern): string { - if (\is_array($pattern)) { - return array_map(__METHOD__, $pattern); - } - return $pattern.'u'; } - /** - * @param string|string[] $pattern - * - * @return string|string[] - */ - private static function removeUtf8Modifier($pattern) + private static function removeUtf8Modifier(string $pattern): string { - if (\is_array($pattern)) { - return array_map(__METHOD__, $pattern); - } - if ('' === $pattern) { return ''; } @@ -159,42 +181,35 @@ private static function removeUtf8Modifier($pattern) } /** - * Create PregException. - * - * Create the generic PregException message and if possible due to finding - * an invalid pattern, tell more about such kind of error in the message. - * - * @param string[] $patterns + * Create the generic PregException message and tell more about such kind of error in the message. */ - private static function newPregException(int $error, string $method, array $patterns): PregException + private static function newPregException(int $error, string $errorMsg, string $method, string $pattern): PregException { - foreach ($patterns as $pattern) { - $last = error_get_last(); - $result = @preg_match($pattern, ''); - - if (false !== $result) { - continue; - } - - $code = preg_last_error(); - $next = error_get_last(); - - if ($last !== $next) { - $message = sprintf( - '(code: %d) %s', - $code, - preg_replace('~preg_[a-z_]+[()]{2}: ~', '', $next['message']) - ); - } else { - $message = sprintf('(code: %d)', $code); - } - - return new PregException( - sprintf('%s(): Invalid PCRE pattern "%s": %s (version: %s)', $method, $pattern, $message, PCRE_VERSION), - $code - ); + $result = null; + $errorMessage = null; + + try { + $result = ExecutorWithoutErrorHandler::execute(static fn () => preg_match($pattern, '')); + } catch (ExecutorWithoutErrorHandlerException $e) { + $result = false; + $errorMessage = $e->getMessage(); + } + + if (false !== $result) { + return new PregException(\sprintf('Unknown error occurred when calling %s: %s.', $method, $errorMsg), $error); } - return new PregException(sprintf('Error occurred when calling %s.', $method), $error); + $code = preg_last_error(); + + $message = \sprintf( + '(code: %d) %s', + $code, + preg_replace('~preg_[a-z_]+[()]{2}: ~', '', $errorMessage) + ); + + return new PregException( + \sprintf('%s(): Invalid PCRE pattern "%s": %s (version: %s)', $method, $pattern, $message, PCRE_VERSION), + $code + ); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/PregException.php b/vendor/friendsofphp/php-cs-fixer/src/PregException.php index c91b86ce41..57519e8aa5 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/PregException.php +++ b/vendor/friendsofphp/php-cs-fixer/src/PregException.php @@ -21,6 +21,4 @@ * * @internal */ -final class PregException extends \RuntimeException -{ -} +final class PregException extends \RuntimeException {} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php index 9c36c3c79b..397c6873b1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractMigrationSetDescription.php @@ -25,14 +25,14 @@ public function getDescription(): string { $name = $this->getName(); - if (0 !== Preg::match('#^@PHPUnit([\d]{2})Migration.*$#', $name, $matches)) { - return sprintf('Rules to improve tests code for PHPUnit %d.%d compatibility.', $matches[1][0], $matches[1][1]); + if (Preg::match('#^@PHPUnit(\d+)(\d)Migration.*$#', $name, $matches)) { + return \sprintf('Rules to improve tests code for PHPUnit %d.%d compatibility.', $matches[1], $matches[2]); } - if (0 !== Preg::match('#^@PHP([\d]{2})Migration.*$#', $name, $matches)) { - return sprintf('Rules to improve code for PHP %d.%d compatibility.', $matches[1][0], $matches[1][1]); + if (Preg::match('#^@PHP([\d]{2})Migration.*$#', $name, $matches)) { + return \sprintf('Rules to improve code for PHP %d.%d compatibility.', $matches[1][0], $matches[1][1]); } - throw new \RuntimeException(sprintf('Cannot generate description for "%s" "%s".', static::class, $name)); + throw new \RuntimeException(\sprintf('Cannot generate description for "%s" "%s".', static::class, $name)); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php index d822ffccaf..db565b4104 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/AbstractRuleSetDescription.php @@ -19,9 +19,7 @@ */ abstract class AbstractRuleSetDescription implements RuleSetDescriptionInterface { - public function __construct() - { - } + public function __construct() {} public function getName(): string { diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/DeprecatedRuleSetDescriptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/DeprecatedRuleSetDescriptionInterface.php new file mode 100644 index 0000000000..ae52441d55 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/DeprecatedRuleSetDescriptionInterface.php @@ -0,0 +1,28 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet; + +/** + * @author Greg Korba + */ +interface DeprecatedRuleSetDescriptionInterface extends RuleSetDescriptionInterface +{ + /** + * Returns names of rule sets to use instead, if any. + * + * @return list + */ + public function getSuccessorsNames(): array; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php index e8efe9a693..0894f254e6 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSet.php @@ -15,6 +15,7 @@ namespace PhpCsFixer\RuleSet; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\Utils; /** * Set of rules to be used by fixer. @@ -28,12 +29,12 @@ final class RuleSet implements RuleSetInterface /** * Group of rules generated from input set. * - * The key is name of rule, value is bool if the rule/set should be used. + * The key is name of rule, value is configuration array or true. * The key must not point to any set. * - * @var array + * @var array|true> */ - private $rules; + private array $rules; public function __construct(array $set = []) { @@ -43,7 +44,7 @@ public function __construct(array $set = []) } if (\is_int($name)) { - throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value)); + throw new \InvalidArgumentException(\sprintf('Missing value for "%s" rule/set.', $value)); } if (!\is_bool($value) && !\is_array($value)) { @@ -60,21 +61,15 @@ public function __construct(array $set = []) $this->resolveSet($set); } - /** - * {@inheritdoc} - */ public function hasRule(string $rule): bool { return \array_key_exists($rule, $this->rules); } - /** - * {@inheritdoc} - */ public function getRuleConfiguration(string $rule): ?array { if (!$this->hasRule($rule)) { - throw new \InvalidArgumentException(sprintf('Rule "%s" is not in the set.', $rule)); + throw new \InvalidArgumentException(\sprintf('Rule "%s" is not in the set.', $rule)); } if (true === $this->rules[$rule]) { @@ -84,9 +79,6 @@ public function getRuleConfiguration(string $rule): ?array return $this->rules[$rule]; } - /** - * {@inheritdoc} - */ public function getRules(): array { return $this->rules; @@ -94,6 +86,8 @@ public function getRules(): array /** * Resolve input set into group of rules. + * + * @param array|bool> $rules */ private function resolveSet(array $rules): void { @@ -103,7 +97,7 @@ private function resolveSet(array $rules): void foreach ($rules as $name => $value) { if (str_starts_with($name, '@')) { if (!\is_bool($value)) { - throw new \UnexpectedValueException(sprintf('Nested rule set "%s" configuration must be a boolean.', $name)); + throw new \UnexpectedValueException(\sprintf('Nested rule set "%s" configuration must be a boolean.', $name)); } $set = $this->resolveSubset($name, $value); @@ -114,7 +108,10 @@ private function resolveSet(array $rules): void } // filter out all resolvedRules that are off - $resolvedRules = array_filter($resolvedRules); + $resolvedRules = array_filter( + $resolvedRules, + static fn ($value): bool => false !== $value + ); $this->rules = $resolvedRules; } @@ -124,10 +121,22 @@ private function resolveSet(array $rules): void * * If set value is false then disable all fixers in set, * if not then get value from set item. + * + * @return array|bool> */ private function resolveSubset(string $setName, bool $setValue): array { - $rules = RuleSets::getSetDefinition($setName)->getRules(); + $ruleSet = RuleSets::getSetDefinition($setName); + + if ($ruleSet instanceof DeprecatedRuleSetDescriptionInterface) { + $messageEnd = [] === $ruleSet->getSuccessorsNames() + ? 'No replacement available' + : \sprintf('Use %s instead', Utils::naturalLanguageJoin($ruleSet->getSuccessorsNames())); + + Utils::triggerDeprecation(new \RuntimeException("Rule set \"{$setName}\" is deprecated. {$messageEnd}.")); + } + + $rules = $ruleSet->getRules(); foreach ($rules as $name => $value) { if (str_starts_with($name, '@')) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php index 996ba195e3..156aed3a07 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetDescriptionInterface.php @@ -25,6 +25,8 @@ public function getName(): string; /** * Get all rules from rules set. + * + * @return array|bool> */ public function getRules(): array; diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php index 6149e0eaa7..024248258b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSetInterface.php @@ -23,24 +23,27 @@ */ interface RuleSetInterface { + /** + * @param array|bool> $set + */ public function __construct(array $set = []); /** * Get configuration for given rule. * - * @param string $rule name of rule + * @return null|array */ public function getRuleConfiguration(string $rule): ?array; /** * Get all rules from rules set. + * + * @return array|true> */ public function getRules(): array; /** * Check given rule is in rules set. - * - * @param string $rule name of rule */ public function hasRule(string $rule): bool; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php index 2b0969fd05..855a816f8b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/RuleSets.php @@ -24,7 +24,7 @@ final class RuleSets { /** - * @var array + * @var array */ private static $setDefinitions; @@ -43,14 +43,14 @@ public static function getSetDefinitions(): array self::$setDefinitions[$set->getName()] = $set; } - ksort(self::$setDefinitions); + uksort(self::$setDefinitions, static fn (string $x, string $y): int => strnatcmp($x, $y)); } return self::$setDefinitions; } /** - * @return string[] + * @return list */ public static function getSetDefinitionNames(): array { @@ -62,7 +62,7 @@ public static function getSetDefinition(string $name): RuleSetDescriptionInterfa $definitions = self::getSetDefinitions(); if (!isset($definitions[$name])) { - throw new \InvalidArgumentException(sprintf('Set "%s" does not exist.', $name)); + throw new \InvalidArgumentException(\sprintf('Set "%s" does not exist.', $name)); } return $definitions[$name]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0RiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0RiskySet.php new file mode 100644 index 0000000000..3e446037f0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0RiskySet.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v1.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/1.0.0/spec.md + */ +final class PERCS1x0RiskySet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS1.0:risky'; + } + + public function getRules(): array + { + return [ + '@PSR12:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 1.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0Set.php new file mode 100644 index 0000000000..3ad0a6fcc9 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS1x0Set.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v1.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/1.0.0/spec.md + */ +final class PERCS1x0Set extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS1.0'; + } + + public function getRules(): array + { + return [ + '@PSR12' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 1.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0RiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0RiskySet.php new file mode 100644 index 0000000000..4a75a93d0c --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0RiskySet.php @@ -0,0 +1,44 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v2.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/2.0.0/spec.md + */ +final class PERCS2x0RiskySet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS2.0:risky'; + } + + public function getRules(): array + { + return [ + '@PER-CS1.0:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 2.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0Set.php new file mode 100644 index 0000000000..f336050f30 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCS2x0Set.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + * + * PER Coding Style v2.0. + * + * @see https://github.com/php-fig/per-coding-style/blob/2.0.0/spec.md + */ +final class PERCS2x0Set extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS2.0'; + } + + public function getRules(): array + { + $rules = [ + '@PER-CS1.0' => true, + 'array_indentation' => true, + 'cast_spaces' => true, + 'concat_space' => ['spacing' => 'one'], + 'function_declaration' => [ + 'closure_fn_spacing' => 'none', + ], + 'method_argument_space' => true, + 'single_line_empty_body' => true, + 'trailing_comma_in_multiline' => [ + 'after_heredoc' => true, + 'elements' => ['arguments', 'arrays'], + ], + ]; + + if (\PHP_VERSION_ID >= 8_00_00) { + $rules['trailing_comma_in_multiline']['elements'][] = 'match'; + $rules['trailing_comma_in_multiline']['elements'][] = 'parameters'; + } + + return $rules; + } + + public function getDescription(): string + { + return 'Rules that follow `PER Coding Style 2.0 `_.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSRiskySet.php new file mode 100644 index 0000000000..6e6f7908c1 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSRiskySet.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PERCSRiskySet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS:risky'; + } + + public function getRules(): array + { + return [ + '@PER-CS2.0:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the latest revision of PER-CS risky rules. Use it if you always want to be in sync with newest PER-CS standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSSet.php new file mode 100644 index 0000000000..0fe5934990 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERCSSet.php @@ -0,0 +1,40 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; + +/** + * @internal + */ +final class PERCSSet extends AbstractRuleSetDescription +{ + public function getName(): string + { + return '@PER-CS'; + } + + public function getRules(): array + { + return [ + '@PER-CS2.0' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the latest revision of PER-CS rules. Use it if you always want to be in sync with newest PER-CS standard.'; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERRiskySet.php new file mode 100644 index 0000000000..9f156c288d --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERRiskySet.php @@ -0,0 +1,52 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; +use PhpCsFixer\RuleSet\DeprecatedRuleSetDescriptionInterface; + +/** + * @internal + * + * @deprecated use `@PER-CS:risky` instead + * + * @TODO 4.0 remove me + * + * Last updated to PER Coding Style v2.0. + */ +final class PERRiskySet extends AbstractRuleSetDescription implements DeprecatedRuleSetDescriptionInterface +{ + public function getName(): string + { + return '@PER:risky'; + } + + public function getRules(): array + { + return [ + '@PER-CS:risky' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the newest PER-CS risky rules. It is recommended you use ``@PER-CS2.0:risky`` instead if you want to stick with stable ruleset.'; + } + + public function getSuccessorsNames(): array + { + return ['@PER-CS:risky']; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERSet.php new file mode 100644 index 0000000000..81de37deda --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PERSet.php @@ -0,0 +1,47 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractRuleSetDescription; +use PhpCsFixer\RuleSet\DeprecatedRuleSetDescriptionInterface; + +/** + * @internal + * + * @deprecated use `@PER-CS` instead + * + * @TODO 4.0 remove me + * + * Last updated to PER Coding Style v2.0. + */ +final class PERSet extends AbstractRuleSetDescription implements DeprecatedRuleSetDescriptionInterface +{ + public function getRules(): array + { + return [ + '@PER-CS' => true, + ]; + } + + public function getDescription(): string + { + return 'Alias for the newest PER-CS rules. It is recommended you use ``@PER-CS2.0`` instead if you want to stick with stable ruleset.'; + } + + public function getSuccessorsNames(): array + { + return ['@PER-CS']; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP82MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP82MigrationSet.php new file mode 100644 index 0000000000..6a9da7dbaf --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP82MigrationSet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP82MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP81Migration' => true, + 'simple_to_complex_string_variable' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP83MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP83MigrationSet.php new file mode 100644 index 0000000000..a2bdfbd936 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP83MigrationSet.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP83MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP82Migration' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP84MigrationSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP84MigrationSet.php new file mode 100644 index 0000000000..480391bda7 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHP84MigrationSet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHP84MigrationSet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHP83Migration' => true, + 'nullable_type_declaration_for_default_null_value' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit100MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit100MigrationRiskySet.php new file mode 100644 index 0000000000..f2092648f5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit100MigrationRiskySet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit100MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit91Migration:risky' => true, + 'php_unit_data_provider_static' => ['force' => true], + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit91MigrationRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit91MigrationRiskySet.php new file mode 100644 index 0000000000..5f0736d94f --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PHPUnit91MigrationRiskySet.php @@ -0,0 +1,31 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\RuleSet\Sets; + +use PhpCsFixer\RuleSet\AbstractMigrationSetDescription; + +/** + * @internal + */ +final class PHPUnit91MigrationRiskySet extends AbstractMigrationSetDescription +{ + public function getRules(): array + { + return [ + '@PHPUnit84Migration:risky' => true, + 'php_unit_assert_new_names' => true, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php index dd04f4c1e3..47719b1073 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR12Set.php @@ -25,19 +25,24 @@ public function getRules(): array { return [ '@PSR2' => true, + 'binary_operator_spaces' => [ + 'default' => 'at_least_single_space', + ], 'blank_line_after_opening_tag' => true, - 'braces' => [ - 'allow_single_line_anonymous_class_with_empty_body' => true, + 'blank_line_between_import_groups' => true, + 'blank_lines_before_namespace' => true, + 'braces_position' => [ + 'allow_single_line_empty_anonymous_classes' => true, ], 'class_definition' => [ 'inline_constructor_arguments' => false, // handled by method_argument_space fixer 'space_before_parenthesis' => true, // defined in PSR12 ¶8. Anonymous Classes ], - 'compact_nullable_typehint' => true, + 'compact_nullable_type_declaration' => true, 'declare_equal_normalize' => true, 'lowercase_cast' => true, 'lowercase_static_reference' => true, - 'new_with_braces' => true, + 'new_with_parentheses' => true, 'no_blank_lines_after_class_opening' => true, 'no_leading_import_slash' => true, 'no_whitespace_in_blank_line' => true, @@ -56,9 +61,12 @@ public function getRules(): array ], 'return_type_declaration' => true, 'short_scalar_cast' => true, - 'single_blank_line_before_namespace' => true, + 'single_import_per_statement' => ['group_to_single_imports' => false], 'single_trait_insert_per_statement' => true, 'ternary_operator_spaces' => true, + 'unary_operator_spaces' => [ + 'only_dec_inc' => true, + ], 'visibility_required' => true, ]; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php index b9489eeafa..19d023d79c 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PSR2Set.php @@ -26,22 +26,25 @@ public function getRules(): array return [ '@PSR1' => true, 'blank_line_after_namespace' => true, - 'braces' => true, + 'braces_position' => true, 'class_definition' => true, 'constant_case' => true, + 'control_structure_braces' => true, + 'control_structure_continuation_position' => true, 'elseif' => true, 'function_declaration' => true, 'indentation_type' => true, 'line_ending' => true, 'lowercase_keywords' => true, 'method_argument_space' => [ + 'attribute_placement' => 'ignore', 'on_multiline' => 'ensure_fully_multiline', ], 'no_break_comment' => true, 'no_closing_tag' => true, + 'no_multiple_statements_per_line' => true, 'no_space_around_double_colon' => true, 'no_spaces_after_function_name' => true, - 'no_spaces_inside_parenthesis' => true, 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'single_blank_line_at_eof' => true, @@ -52,6 +55,8 @@ public function getRules(): array ], 'single_import_per_statement' => true, 'single_line_after_imports' => true, + 'spaces_inside_parentheses' => true, + 'statement_indentation' => true, 'switch_case_semicolon_to_colon' => true, 'switch_case_space' => true, 'visibility_required' => ['elements' => ['method', 'property']], diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php index d537201459..9048d242f9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerRiskySet.php @@ -24,9 +24,12 @@ final class PhpCsFixerRiskySet extends AbstractRuleSetDescription public function getRules(): array { return [ + '@PER-CS:risky' => true, '@Symfony:risky' => true, 'comment_to_phpdoc' => true, 'final_internal_class' => true, + 'get_class_to_class_keyword' => false, + 'modernize_strpos' => false, // @TODO: consider switching to `true`, like in @Symfony 'native_constant_invocation' => [ 'fix_built_in' => false, @@ -44,12 +47,16 @@ public function getRules(): array '@all', ], ], - 'no_unreachable_default_argument_value' => true, 'no_unset_on_property' => true, + 'php_unit_data_provider_name' => true, + 'php_unit_data_provider_return_type' => true, + 'php_unit_data_provider_static' => ['force' => true], 'php_unit_strict' => true, - 'php_unit_test_case_static_method_calls' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'static_lambda' => true, 'strict_comparison' => true, 'strict_param' => true, + 'yield_from_array_to_yields' => true, ]; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php index bd2a3a8e7a..8770cbada8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/PhpCsFixerSet.php @@ -24,9 +24,8 @@ final class PhpCsFixerSet extends AbstractRuleSetDescription public function getRules(): array { return [ + '@PER-CS' => true, '@Symfony' => true, - 'align_multiline_comment' => true, - 'array_indentation' => true, 'blank_line_before_statement' => [ 'statements' => [ 'break', @@ -46,14 +45,17 @@ public function getRules(): array 'throw', 'try', 'yield', + 'yield_from', ], ], 'combine_consecutive_issets' => true, 'combine_consecutive_unsets' => true, 'empty_loop_body' => true, - 'escape_implicit_backslashes' => true, 'explicit_indirect_variable' => true, 'explicit_string_variable' => true, + 'fully_qualified_strict_types' => [ + 'import_symbols' => true, + ], 'heredoc_to_nowdoc' => true, 'method_argument_space' => [ 'on_multiline' => 'ensure_fully_multiline', @@ -65,6 +67,7 @@ public function getRules(): array ], 'no_extra_blank_lines' => [ 'tokens' => [ + 'attribute', 'break', 'case', 'continue', @@ -79,26 +82,45 @@ public function getRules(): array 'use', ], ], - 'no_null_property_initialization' => true, 'no_superfluous_elseif' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + 'remove_inheritdoc' => true, + ], + 'no_unneeded_control_parentheses' => [ + 'statements' => [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'negative_instanceof', + 'others', + 'return', + 'switch_case', + 'yield', + 'yield_from', + ], + ], 'no_useless_else' => true, 'no_useless_return' => true, - 'operator_linebreak' => [ - 'only_booleans' => true, - ], + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], 'ordered_class_elements' => true, + 'ordered_types' => true, 'php_unit_internal_class' => true, 'php_unit_test_class_requires_covers' => true, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_no_empty_return' => true, - 'phpdoc_order' => true, 'phpdoc_order_by_value' => true, 'phpdoc_types_order' => true, 'phpdoc_var_annotation_correct_order' => true, + 'protected_to_private' => true, 'return_assignment' => true, - 'simple_to_complex_string_variable' => true, + 'self_static_accessor' => true, 'single_line_comment_style' => true, + 'single_line_empty_body' => true, 'single_line_throw' => false, + 'string_implicit_backslashes' => true, + 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], ]; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php index e07ae3424c..f60d9cac3d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonyRiskySet.php @@ -36,11 +36,14 @@ public function getRules(): array 'b_mode' => false, ], 'function_to_constant' => true, + 'get_class_to_class_keyword' => true, 'implode_call' => true, 'is_null' => true, 'logical_operators' => true, + 'long_to_shorthand_operator' => true, + 'modernize_strpos' => true, 'modernize_types_casting' => true, - 'native_constant_invocation' => true, + 'native_constant_invocation' => ['strict' => false], 'native_function_invocation' => [ 'include' => [ '@compiler_optimized', @@ -52,7 +55,6 @@ public function getRules(): array 'no_homoglyph_names' => true, 'no_php4_constructor' => true, 'no_unneeded_final_method' => true, - 'no_unreachable_default_argument_value' => false, 'no_useless_sprintf' => true, 'non_printable_character' => true, 'ordered_traits' => true, diff --git a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php index ed031b9507..1b4f38cdd9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php +++ b/vendor/friendsofphp/php-cs-fixer/src/RuleSet/Sets/SymfonySet.php @@ -14,6 +14,7 @@ namespace PhpCsFixer\RuleSet\Sets; +use PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer; use PhpCsFixer\RuleSet\AbstractRuleSetDescription; /** @@ -24,7 +25,8 @@ final class SymfonySet extends AbstractRuleSetDescription public function getRules(): array { return [ - '@PSR12' => true, + '@PER-CS2.0' => true, + 'align_multiline_comment' => true, 'array_syntax' => true, 'backtick_to_shell_exec' => true, 'binary_operator_spaces' => true, @@ -33,11 +35,10 @@ public function getRules(): array 'return', ], ], - 'braces' => [ - 'allow_single_line_anonymous_class_with_empty_body' => true, - 'allow_single_line_closure' => true, + 'braces_position' => [ + 'allow_single_line_anonymous_functions' => true, + 'allow_single_line_empty_anonymous_classes' => true, ], - 'cast_spaces' => true, 'class_attributes_separation' => [ 'elements' => [ 'method' => 'one', @@ -48,17 +49,23 @@ public function getRules(): array ], 'class_reference_name_casing' => true, 'clean_namespace' => true, - 'concat_space' => true, + 'concat_space' => true, // overrides @PER-CS2.0 + 'declare_parentheses' => true, 'echo_tag_syntax' => true, 'empty_loop_body' => ['style' => 'braces'], 'empty_loop_condition' => true, 'fully_qualified_strict_types' => true, - 'function_typehint_space' => true, + 'function_declaration' => true, // overrides @PER-CS2.0 'general_phpdoc_tag_rename' => [ 'replacements' => [ 'inheritDocs' => 'inheritDoc', ], ], + 'global_namespace_import' => [ + 'import_classes' => false, + 'import_constants' => false, + 'import_functions' => false, + ], 'include' => true, 'increment_style' => true, 'integer_literal_case' => true, @@ -66,11 +73,11 @@ public function getRules(): array 'linebreak_after_opening_tag' => true, 'magic_constant_casing' => true, 'magic_method_casing' => true, - 'method_argument_space' => [ + 'method_argument_space' => [ // overrides @PER-CS2.0 'on_multiline' => 'ignore', ], 'native_function_casing' => true, - 'native_function_type_declaration_casing' => true, + 'native_type_declaration_casing' => true, 'no_alias_language_construct_call' => true, 'no_alternative_syntax' => true, 'no_binary_string' => true, @@ -80,6 +87,7 @@ public function getRules(): array 'no_empty_statement' => true, 'no_extra_blank_lines' => [ 'tokens' => [ + 'attribute', 'case', 'continue', 'curly_brace_block', @@ -95,38 +103,56 @@ public function getRules(): array 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => true, 'no_multiline_whitespace_around_double_arrow' => true, + 'no_null_property_initialization' => true, 'no_short_bool_cast' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_around_offset' => true, 'no_superfluous_phpdoc_tags' => [ - 'allow_mixed' => true, - 'allow_unused_params' => true, + 'allow_hidden_params' => true, + 'remove_inheritdoc' => true, + ], + 'no_trailing_comma_in_singleline' => true, + 'no_unneeded_braces' => [ + 'namespaces' => true, ], - 'no_trailing_comma_in_list_call' => true, - 'no_trailing_comma_in_singleline_array' => true, - 'no_trailing_comma_in_singleline_function_call' => true, 'no_unneeded_control_parentheses' => [ 'statements' => [ 'break', 'clone', 'continue', 'echo_print', + 'others', 'return', 'switch_case', 'yield', 'yield_from', ], ], - 'no_unneeded_curly_braces' => [ - 'namespaces' => true, - ], 'no_unneeded_import_alias' => true, 'no_unset_cast' => true, 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_nullsafe_operator' => true, 'no_whitespace_before_comma_in_array' => true, 'normalize_index_brace' => true, + 'nullable_type_declaration' => true, + 'nullable_type_declaration_for_default_null_value' => true, 'object_operator_without_whitespace' => true, - 'ordered_imports' => true, + 'operator_linebreak' => [ + 'only_booleans' => true, + ], + 'ordered_imports' => [ + 'imports_order' => [ + 'class', + 'function', + 'const', + ], + 'sort_algorithm' => 'alpha', + ], + 'ordered_types' => [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'none', + ], 'php_unit_fqcn_annotation' => true, 'php_unit_method_casing' => true, 'phpdoc_align' => true, @@ -137,9 +163,21 @@ public function getRules(): array 'phpdoc_no_alias_tag' => true, 'phpdoc_no_package' => true, 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => [ + 'order' => [ + 'param', + 'return', + 'throws', + ], + ], 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, - 'phpdoc_separation' => true, + 'phpdoc_separation' => [ + 'groups' => [ + ['Annotation', 'NamedArgumentConstructor', 'Target'], + ...PhpdocSeparationFixer::OPTION_GROUPS_DEFAULT, + ], + ], 'phpdoc_single_line_var_spacing' => true, 'phpdoc_summary' => true, 'phpdoc_tag_type' => [ @@ -156,26 +194,32 @@ public function getRules(): array 'sort_algorithm' => 'none', ], 'phpdoc_var_without_name' => true, - 'protected_to_private' => true, 'semicolon_after_instruction' => true, + 'simple_to_complex_string_variable' => true, 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, 'single_line_comment_spacing' => true, 'single_line_comment_style' => [ 'comment_types' => [ 'hash', ], ], + 'single_line_empty_body' => false, // overrides @PER-CS2.0 'single_line_throw' => true, 'single_quote' => true, - 'single_space_after_construct' => true, + 'single_space_around_construct' => true, 'space_after_semicolon' => [ 'remove_in_empty_for_expressions' => true, ], 'standardize_increment' => true, 'standardize_not_equals' => true, + 'statement_indentation' => [ + 'stick_comment_to_next_continuous_control_statement' => true, + ], 'switch_continue_to_break' => true, 'trailing_comma_in_multiline' => true, 'trim_array_spaces' => true, + 'type_declaration_spaces' => true, 'types_spaces' => true, 'unary_operator_spaces' => true, 'whitespace_after_comma_in_array' => true, diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingFileIterator.php similarity index 77% rename from vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php rename to vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingFileIterator.php index d077f5e4bf..c46cf3d33d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingIterator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileCachingLintingFileIterator.php @@ -21,21 +21,18 @@ * @author Dariusz Rumiński * * @internal + * + * @extends \CachingIterator> */ -final class FileCachingLintingIterator extends \CachingIterator +final class FileCachingLintingFileIterator extends \CachingIterator implements LintingResultAwareFileIteratorInterface { - /** - * @var LintingResultInterface - */ - private $currentResult; - private LinterInterface $linter; + private ?LintingResultInterface $currentResult = null; + private ?LintingResultInterface $nextResult = null; /** - * @var LintingResultInterface + * @param \Iterator $iterator */ - private $nextResult; - public function __construct(\Iterator $iterator, LinterInterface $linter) { parent::__construct($iterator); @@ -43,7 +40,7 @@ public function __construct(\Iterator $iterator, LinterInterface $linter) $this->linter = $linter; } - public function currentLintingResult(): LintingResultInterface + public function currentLintingResult(): ?LintingResultInterface { return $this->currentResult; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php index 37486cb190..975270585b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/FileFilterIterator.php @@ -24,21 +24,23 @@ * @author Dariusz Rumiński * * @internal + * + * @extends \FilterIterator> */ final class FileFilterIterator extends \FilterIterator { - /** - * @var null|EventDispatcherInterface - */ - private $eventDispatcher; + private ?EventDispatcherInterface $eventDispatcher; private CacheManagerInterface $cacheManager; /** - * @var array + * @var array */ private array $visitedElements = []; + /** + * @param \Traversable<\SplFileInfo> $iterator + */ public function __construct( \Traversable $iterator, ?EventDispatcherInterface $eventDispatcher, @@ -59,7 +61,7 @@ public function accept(): bool $file = $this->current(); if (!$file instanceof \SplFileInfo) { throw new \RuntimeException( - sprintf( + \sprintf( 'Expected instance of "\SplFileInfo", got "%s".', get_debug_type($file) ) diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/LintingFileIterator.php similarity index 81% rename from vendor/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php rename to vendor/friendsofphp/php-cs-fixer/src/Runner/LintingFileIterator.php index 050d8c5a50..9b93ed1d6f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Runner/FileLintingIterator.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/LintingFileIterator.php @@ -21,16 +21,21 @@ * @author Dariusz Rumiński * * @internal + * + * @extends \IteratorIterator> */ -final class FileLintingIterator extends \IteratorIterator +final class LintingFileIterator extends \IteratorIterator implements LintingResultAwareFileIteratorInterface { /** - * @var LintingResultInterface + * @var null|LintingResultInterface */ private $currentResult; private LinterInterface $linter; + /** + * @param \Iterator $iterator + */ public function __construct(\Iterator $iterator, LinterInterface $linter) { parent::__construct($iterator); diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/LintingResultAwareFileIteratorInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/LintingResultAwareFileIteratorInterface.php new file mode 100644 index 0000000000..1c05bf29d6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/LintingResultAwareFileIteratorInterface.php @@ -0,0 +1,29 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner; + +use PhpCsFixer\Linter\LintingResultInterface; + +/** + * @author Greg Korba + * + * @internal + * + * @extends \Iterator + */ +interface LintingResultAwareFileIteratorInterface extends \Iterator +{ + public function currentLintingResult(): ?LintingResultInterface; +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelAction.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelAction.php new file mode 100644 index 0000000000..42585ee28e --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelAction.php @@ -0,0 +1,35 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +/** + * @author Greg Korba + * + * @internal + */ +final class ParallelAction +{ + // Actions executed by the runner (main process) + public const RUNNER_REQUEST_ANALYSIS = 'requestAnalysis'; + public const RUNNER_THANK_YOU = 'thankYou'; + + // Actions executed by the worker + public const WORKER_ERROR_REPORT = 'errorReport'; + public const WORKER_GET_FILE_CHUNK = 'getFileChunk'; + public const WORKER_HELLO = 'hello'; + public const WORKER_RESULT = 'result'; + + private function __construct() {} +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelConfig.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelConfig.php new file mode 100644 index 0000000000..7ccf9fdf62 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelConfig.php @@ -0,0 +1,65 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +/** + * @author Greg Korba + */ +final class ParallelConfig +{ + /** @internal */ + public const DEFAULT_FILES_PER_PROCESS = 10; + + /** @internal */ + public const DEFAULT_PROCESS_TIMEOUT = 120; + + private int $filesPerProcess; + private int $maxProcesses; + private int $processTimeout; + + /** + * @param positive-int $maxProcesses + * @param positive-int $filesPerProcess + * @param positive-int $processTimeout + */ + public function __construct( + int $maxProcesses = 2, + int $filesPerProcess = self::DEFAULT_FILES_PER_PROCESS, + int $processTimeout = self::DEFAULT_PROCESS_TIMEOUT + ) { + if ($maxProcesses <= 0 || $filesPerProcess <= 0 || $processTimeout <= 0) { + throw new \InvalidArgumentException('Invalid parallelisation configuration: only positive integers are allowed'); + } + + $this->maxProcesses = $maxProcesses; + $this->filesPerProcess = $filesPerProcess; + $this->processTimeout = $processTimeout; + } + + public function getFilesPerProcess(): int + { + return $this->filesPerProcess; + } + + public function getMaxProcesses(): int + { + return $this->maxProcesses; + } + + public function getProcessTimeout(): int + { + return $this->processTimeout; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelConfigFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelConfigFactory.php new file mode 100644 index 0000000000..162cf43746 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelConfigFactory.php @@ -0,0 +1,56 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +use Fidry\CpuCoreCounter\CpuCoreCounter; +use Fidry\CpuCoreCounter\Finder\DummyCpuCoreFinder; +use Fidry\CpuCoreCounter\Finder\FinderRegistry; + +/** + * @author Dariusz Rumiński + */ +final class ParallelConfigFactory +{ + private static ?CpuCoreCounter $cpuDetector = null; + + private function __construct() {} + + public static function sequential(): ParallelConfig + { + return new ParallelConfig(1); + } + + /** + * @param null|positive-int $filesPerProcess + * @param null|positive-int $processTimeout + */ + public static function detect( + ?int $filesPerProcess = null, + ?int $processTimeout = null + ): ParallelConfig { + if (null === self::$cpuDetector) { + self::$cpuDetector = new CpuCoreCounter([ + ...FinderRegistry::getDefaultLogicalFinders(), + new DummyCpuCoreFinder(1), + ]); + } + + return new ParallelConfig( + self::$cpuDetector->getCount(), + $filesPerProcess ?? ParallelConfig::DEFAULT_FILES_PER_PROCESS, + $processTimeout ?? ParallelConfig::DEFAULT_PROCESS_TIMEOUT + ); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelisationException.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelisationException.php new file mode 100644 index 0000000000..8c24e6a4a5 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ParallelisationException.php @@ -0,0 +1,30 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +/** + * Common exception for all the errors related to parallelisation. + * + * @author Greg Korba + * + * @internal + */ +final class ParallelisationException extends \RuntimeException +{ + public static function forUnknownIdentifier(ProcessIdentifier $identifier): self + { + return new self('Unknown process identifier: '.$identifier->toString()); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/Process.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/Process.php new file mode 100644 index 0000000000..0d5ce58803 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/Process.php @@ -0,0 +1,189 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +use React\ChildProcess\Process as ReactProcess; +use React\EventLoop\LoopInterface; +use React\EventLoop\TimerInterface; +use React\Stream\ReadableStreamInterface; +use React\Stream\WritableStreamInterface; + +/** + * Represents single process that is handled within parallel run. + * Inspired by: + * - https://github.com/phpstan/phpstan-src/blob/9ce425bca5337039fb52c0acf96a20a2b8ace490/src/Parallel/Process.php + * - https://github.com/phpstan/phpstan-src/blob/1477e752b4b5893f323b6d2c43591e68b3d85003/src/Process/ProcessHelper.php. + * + * @author Greg Korba + * + * @internal + */ +final class Process +{ + // Properties required for process instantiation + private string $command; + private LoopInterface $loop; + private int $timeoutSeconds; + + // Properties required for process execution + private ?ReactProcess $process = null; + private ?WritableStreamInterface $in = null; + + /** @var resource */ + private $stdErr; + + /** @var resource */ + private $stdOut; + + /** @var callable(array): void */ + private $onData; + + /** @var callable(\Throwable): void */ + private $onError; + + private ?TimerInterface $timer = null; + + public function __construct(string $command, LoopInterface $loop, int $timeoutSeconds) + { + $this->command = $command; + $this->loop = $loop; + $this->timeoutSeconds = $timeoutSeconds; + } + + /** + * @param callable(array $json): void $onData callback to be called when data is received from the parallelisation operator + * @param callable(\Throwable $exception): void $onError callback to be called when an exception occurs + * @param callable(?int $exitCode, string $output): void $onExit callback to be called when the process exits + */ + public function start(callable $onData, callable $onError, callable $onExit): void + { + $stdOut = tmpfile(); + if (false === $stdOut) { + throw new ParallelisationException('Failed creating temp file for stdOut.'); + } + $this->stdOut = $stdOut; + + $stdErr = tmpfile(); + if (false === $stdErr) { + throw new ParallelisationException('Failed creating temp file for stdErr.'); + } + $this->stdErr = $stdErr; + + $this->onData = $onData; + $this->onError = $onError; + + $this->process = new ReactProcess($this->command, null, null, [ + 1 => $this->stdOut, + 2 => $this->stdErr, + ]); + $this->process->start($this->loop); + $this->process->on('exit', function ($exitCode) use ($onExit): void { + $this->cancelTimer(); + + $output = ''; + rewind($this->stdOut); + $stdOut = stream_get_contents($this->stdOut); + if (\is_string($stdOut)) { + $output .= $stdOut; + } + + rewind($this->stdErr); + $stdErr = stream_get_contents($this->stdErr); + if (\is_string($stdErr)) { + $output .= $stdErr; + } + + $onExit($exitCode, $output); + + fclose($this->stdOut); + fclose($this->stdErr); + }); + } + + /** + * Handles requests from parallelisation operator to its worker (spawned process). + * + * @param array $data + */ + public function request(array $data): void + { + $this->cancelTimer(); // Configured process timeout actually means "chunk timeout" (each request resets timer) + + if (null === $this->in) { + throw new ParallelisationException( + 'Process not connected with parallelisation operator, ensure `bindConnection()` was called' + ); + } + + $this->in->write($data); + $this->timer = $this->loop->addTimer($this->timeoutSeconds, function (): void { + ($this->onError)( + new \Exception( + \sprintf( + 'Child process timed out after %d seconds. Try making it longer using `ParallelConfig`.', + $this->timeoutSeconds + ) + ) + ); + }); + } + + public function quit(): void + { + $this->cancelTimer(); + if (null === $this->process || !$this->process->isRunning()) { + return; + } + + foreach ($this->process->pipes as $pipe) { + $pipe->close(); + } + + if (null === $this->in) { + return; + } + + $this->in->end(); + } + + public function bindConnection(ReadableStreamInterface $out, WritableStreamInterface $in): void + { + $this->in = $in; + + $in->on('error', function (\Throwable $error): void { + ($this->onError)($error); + }); + + $out->on('data', function (array $json): void { + $this->cancelTimer(); + + // Pass everything to the parallelisation operator, it should decide how to handle the data + ($this->onData)($json); + }); + $out->on('error', function (\Throwable $error): void { + ($this->onError)($error); + }); + } + + private function cancelTimer(): void + { + if (null === $this->timer) { + return; + } + + $this->loop->cancelTimer($this->timer); + $this->timer = null; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessFactory.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessFactory.php new file mode 100644 index 0000000000..af37e05890 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessFactory.php @@ -0,0 +1,109 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +use PhpCsFixer\Runner\RunnerConfig; +use React\EventLoop\LoopInterface; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * @author Greg Korba + * + * @internal + */ +final class ProcessFactory +{ + private InputInterface $input; + + public function __construct(InputInterface $input) + { + $this->input = $input; + } + + public function create( + LoopInterface $loop, + RunnerConfig $runnerConfig, + ProcessIdentifier $identifier, + int $serverPort + ): Process { + $commandArgs = $this->getCommandArgs($serverPort, $identifier, $runnerConfig); + + return new Process( + implode(' ', $commandArgs), + $loop, + $runnerConfig->getParallelConfig()->getProcessTimeout() + ); + } + + /** + * @private + * + * @return list + */ + public function getCommandArgs(int $serverPort, ProcessIdentifier $identifier, RunnerConfig $runnerConfig): array + { + $phpBinary = (new PhpExecutableFinder())->find(false); + + if (false === $phpBinary) { + throw new ParallelisationException('Cannot find PHP executable.'); + } + + $mainScript = realpath(__DIR__.'/../../../php-cs-fixer'); + if (false === $mainScript + && isset($_SERVER['argv'][0]) + && str_contains($_SERVER['argv'][0], 'php-cs-fixer') + ) { + $mainScript = $_SERVER['argv'][0]; + } + + if (!is_file($mainScript)) { + throw new ParallelisationException('Cannot determine Fixer executable.'); + } + + $commandArgs = [ + $phpBinary, + escapeshellarg($mainScript), + 'worker', + '--port', + (string) $serverPort, + '--identifier', + escapeshellarg($identifier->toString()), + ]; + + if ($runnerConfig->isDryRun()) { + $commandArgs[] = '--dry-run'; + } + + if (filter_var($this->input->getOption('diff'), FILTER_VALIDATE_BOOLEAN)) { + $commandArgs[] = '--diff'; + } + + if (filter_var($this->input->getOption('stop-on-violation'), FILTER_VALIDATE_BOOLEAN)) { + $commandArgs[] = '--stop-on-violation'; + } + + foreach (['allow-risky', 'config', 'rules', 'using-cache', 'cache-file'] as $option) { + $optionValue = $this->input->getOption($option); + + if (null !== $optionValue) { + $commandArgs[] = "--{$option}"; + $commandArgs[] = escapeshellarg($optionValue); + } + } + + return $commandArgs; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessIdentifier.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessIdentifier.php new file mode 100644 index 0000000000..48f0f1b5e0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessIdentifier.php @@ -0,0 +1,53 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +/** + * Represents identifier of single process that is handled within parallel run. + * + * @author Greg Korba + * + * @internal + */ +final class ProcessIdentifier +{ + private const IDENTIFIER_PREFIX = 'php-cs-fixer_parallel_'; + + private string $identifier; + + private function __construct(string $identifier) + { + $this->identifier = $identifier; + } + + public function toString(): string + { + return $this->identifier; + } + + public static function create(): self + { + return new self(uniqid(self::IDENTIFIER_PREFIX, true)); + } + + public static function fromRaw(string $identifier): self + { + if (!str_starts_with($identifier, self::IDENTIFIER_PREFIX)) { + throw new ParallelisationException(\sprintf('Invalid process identifier "%s".', $identifier)); + } + + return new self($identifier); + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessPool.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessPool.php new file mode 100644 index 0000000000..fe13d0faa0 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/ProcessPool.php @@ -0,0 +1,90 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +use React\Socket\ServerInterface; + +/** + * Represents collection of active processes that are being run in parallel. + * Inspired by {@see https://github.com/phpstan/phpstan-src/blob/ed68345a82992775112acc2c2bd639d1bd3a1a02/src/Parallel/ProcessPool.php}. + * + * @author Greg Korba + * + * @internal + */ +final class ProcessPool +{ + private ServerInterface $server; + + /** @var null|(callable(): void) */ + private $onServerClose; + + /** @var array */ + private array $processes = []; + + /** + * @param null|(callable(): void) $onServerClose + */ + public function __construct(ServerInterface $server, ?callable $onServerClose = null) + { + $this->server = $server; + $this->onServerClose = $onServerClose; + } + + public function getProcess(ProcessIdentifier $identifier): Process + { + if (!isset($this->processes[$identifier->toString()])) { + throw ParallelisationException::forUnknownIdentifier($identifier); + } + + return $this->processes[$identifier->toString()]; + } + + public function addProcess(ProcessIdentifier $identifier, Process $process): void + { + $this->processes[$identifier->toString()] = $process; + } + + public function endProcessIfKnown(ProcessIdentifier $identifier): void + { + if (!isset($this->processes[$identifier->toString()])) { + return; + } + + $this->endProcess($identifier); + } + + public function endAll(): void + { + foreach (array_keys($this->processes) as $identifier) { + $this->endProcessIfKnown(ProcessIdentifier::fromRaw($identifier)); + } + } + + private function endProcess(ProcessIdentifier $identifier): void + { + $this->getProcess($identifier)->quit(); + + unset($this->processes[$identifier->toString()]); + + if (0 === \count($this->processes)) { + $this->server->close(); + + if (null !== $this->onServerClose) { + ($this->onServerClose)(); + } + } + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/WorkerException.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/WorkerException.php new file mode 100644 index 0000000000..714306d364 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Parallel/WorkerException.php @@ -0,0 +1,66 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner\Parallel; + +use Throwable; + +/** + * @author Greg Korba + * + * @internal + */ +final class WorkerException extends \RuntimeException +{ + private string $originalTraceAsString; + + private function __construct(string $message, int $code) + { + parent::__construct($message, $code); + } + + /** + * @param array{ + * class: class-string, + * message: string, + * file: string, + * line: int, + * code: int, + * trace: string + * } $data + */ + public static function fromRaw(array $data): self + { + $exception = new self( + \sprintf('[%s] %s', $data['class'], $data['message']), + $data['code'] + ); + $exception->file = $data['file']; + $exception->line = $data['line']; + $exception->originalTraceAsString = \sprintf( + '## %s(%d)%s%s', + $data['file'], + $data['line'], + PHP_EOL, + $data['trace'] + ); + + return $exception; + } + + public function getOriginalTraceAsString(): string + { + return $this->originalTraceAsString; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php index a1b43de7a3..df41411324 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/Runner.php @@ -14,37 +14,61 @@ namespace PhpCsFixer\Runner; +use Clue\React\NDJson\Decoder; +use Clue\React\NDJson\Encoder; use PhpCsFixer\AbstractFixer; use PhpCsFixer\Cache\CacheManagerInterface; use PhpCsFixer\Cache\Directory; use PhpCsFixer\Cache\DirectoryInterface; +use PhpCsFixer\Console\Command\WorkerCommand; use PhpCsFixer\Differ\DifferInterface; use PhpCsFixer\Error\Error; use PhpCsFixer\Error\ErrorsManager; +use PhpCsFixer\Error\SourceExceptionFactory; use PhpCsFixer\FileReader; use PhpCsFixer\Fixer\FixerInterface; use PhpCsFixer\FixerFileProcessedEvent; use PhpCsFixer\Linter\LinterInterface; use PhpCsFixer\Linter\LintingException; use PhpCsFixer\Linter\LintingResultInterface; +use PhpCsFixer\Preg; +use PhpCsFixer\Runner\Parallel\ParallelAction; +use PhpCsFixer\Runner\Parallel\ParallelConfig; +use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; +use PhpCsFixer\Runner\Parallel\ParallelisationException; +use PhpCsFixer\Runner\Parallel\ProcessFactory; +use PhpCsFixer\Runner\Parallel\ProcessIdentifier; +use PhpCsFixer\Runner\Parallel\ProcessPool; +use PhpCsFixer\Runner\Parallel\WorkerException; use PhpCsFixer\Tokenizer\Tokens; +use React\EventLoop\StreamSelectLoop; +use React\Socket\ConnectionInterface; +use React\Socket\TcpServer; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Contracts\EventDispatcher\Event; /** * @author Dariusz Rumiński + * @author Greg Korba + * + * @phpstan-type _RunResult array, diff: string}> */ final class Runner { + /** + * Buffer size used in the NDJSON decoder for communication between main process and workers. + * + * @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/8068 + */ + private const PARALLEL_BUFFER_SIZE = 16 * (1_024 * 1_024 /* 1MB */); + private DifferInterface $differ; private ?DirectoryInterface $directory; - /** - * @var null|EventDispatcherInterface - */ - private $eventDispatcher; + private ?EventDispatcherInterface $eventDispatcher; private ErrorsManager $errorsManager; @@ -55,19 +79,31 @@ final class Runner private LinterInterface $linter; /** - * @var \Traversable + * @var null|\Traversable */ - private $finder; + private $fileIterator; + + private int $fileCount; /** - * @var FixerInterface[] + * @var list */ private array $fixers; private bool $stopOnViolation; + private ParallelConfig $parallelConfig; + + private ?InputInterface $input; + + private ?string $configFile; + + /** + * @param null|\Traversable $fileIterator + * @param list $fixers + */ public function __construct( - $finder, + ?\Traversable $fileIterator, array $fixers, DifferInterface $differ, ?EventDispatcherInterface $eventDispatcher, @@ -76,9 +112,16 @@ public function __construct( bool $isDryRun, CacheManagerInterface $cacheManager, ?DirectoryInterface $directory = null, - bool $stopOnViolation = false + bool $stopOnViolation = false, + // @TODO Make these arguments required in 4.0 + ?ParallelConfig $parallelConfig = null, + ?InputInterface $input = null, + ?string $configFile = null ) { - $this->finder = $finder; + // Required only for main process (calculating workers count) + $this->fileCount = null !== $fileIterator ? \count(iterator_to_array($fileIterator)) : 0; + + $this->fileIterator = $fileIterator; $this->fixers = $fixers; $this->differ = $differ; $this->eventDispatcher = $eventDispatcher; @@ -86,27 +129,249 @@ public function __construct( $this->linter = $linter; $this->isDryRun = $isDryRun; $this->cacheManager = $cacheManager; - $this->directory = $directory ?: new Directory(''); + $this->directory = $directory ?? new Directory(''); $this->stopOnViolation = $stopOnViolation; + $this->parallelConfig = $parallelConfig ?? ParallelConfigFactory::sequential(); + $this->input = $input; + $this->configFile = $configFile; + } + + /** + * @TODO consider to drop this method and make iterator parameter obligatory in constructor, + * more in https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777/files#r1590447581 + * + * @param \Traversable $fileIterator + */ + public function setFileIterator(iterable $fileIterator): void + { + $this->fileIterator = $fileIterator; + + // Required only for main process (calculating workers count) + $this->fileCount = \count(iterator_to_array($fileIterator)); } + /** + * @return _RunResult + */ public function fix(): array + { + if (0 === $this->fileCount) { + return []; + } + + // @TODO Remove condition for the input argument in 4.0, as it should be required in the constructor + return $this->parallelConfig->getMaxProcesses() > 1 && null !== $this->input + ? $this->fixParallel() + : $this->fixSequential(); + } + + /** + * Heavily inspired by {@see https://github.com/phpstan/phpstan-src/blob/9ce425bca5337039fb52c0acf96a20a2b8ace490/src/Parallel/ParallelAnalyser.php}. + * + * @return _RunResult + */ + private function fixParallel(): array { $changed = []; + $streamSelectLoop = new StreamSelectLoop(); + $server = new TcpServer('127.0.0.1:0', $streamSelectLoop); + $serverPort = parse_url($server->getAddress() ?? '', PHP_URL_PORT); + + if (!is_numeric($serverPort)) { + throw new ParallelisationException(\sprintf( + 'Unable to parse server port from "%s"', + $server->getAddress() ?? '' + )); + } - $finder = $this->finder; - $finderIterator = $finder instanceof \IteratorAggregate ? $finder->getIterator() : $finder; - $fileFilteredFileIterator = new FileFilterIterator( - $finderIterator, - $this->eventDispatcher, - $this->cacheManager + $processPool = new ProcessPool($server); + $maxFilesPerProcess = $this->parallelConfig->getFilesPerProcess(); + $fileIterator = $this->getFilteringFileIterator(); + $fileIterator->rewind(); + + $getFileChunk = static function () use ($fileIterator, $maxFilesPerProcess): array { + $files = []; + + while (\count($files) < $maxFilesPerProcess) { + $current = $fileIterator->current(); + + if (null === $current) { + break; + } + + $files[] = $current->getRealPath(); + + $fileIterator->next(); + } + + return $files; + }; + + // [REACT] Handle worker's handshake (init connection) + $server->on('connection', static function (ConnectionInterface $connection) use ($processPool, $getFileChunk): void { + $jsonInvalidUtf8Ignore = \defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; + $decoder = new Decoder( + $connection, + true, + 512, + $jsonInvalidUtf8Ignore, + self::PARALLEL_BUFFER_SIZE + ); + $encoder = new Encoder($connection, $jsonInvalidUtf8Ignore); + + // [REACT] Bind connection when worker's process requests "hello" action (enables 2-way communication) + $decoder->on('data', static function (array $data) use ($processPool, $getFileChunk, $decoder, $encoder): void { + if (ParallelAction::WORKER_HELLO !== $data['action']) { + return; + } + + $identifier = ProcessIdentifier::fromRaw($data['identifier']); + $process = $processPool->getProcess($identifier); + $process->bindConnection($decoder, $encoder); + $fileChunk = $getFileChunk(); + + if (0 === \count($fileChunk)) { + $process->request(['action' => ParallelAction::RUNNER_THANK_YOU]); + $processPool->endProcessIfKnown($identifier); + + return; + } + + $process->request(['action' => ParallelAction::RUNNER_REQUEST_ANALYSIS, 'files' => $fileChunk]); + }); + }); + + $processesToSpawn = min( + $this->parallelConfig->getMaxProcesses(), + max( + 1, + (int) ceil($this->fileCount / $this->parallelConfig->getFilesPerProcess()), + ) ); + $processFactory = new ProcessFactory($this->input); + + for ($i = 0; $i < $processesToSpawn; ++$i) { + $identifier = ProcessIdentifier::create(); + $process = $processFactory->create( + $streamSelectLoop, + new RunnerConfig( + $this->isDryRun, + $this->stopOnViolation, + $this->parallelConfig, + $this->configFile + ), + $identifier, + $serverPort, + ); + $processPool->addProcess($identifier, $process); + $process->start( + // [REACT] Handle workers' responses (multiple actions possible) + function (array $workerResponse) use ($processPool, $process, $identifier, $getFileChunk, &$changed): void { + // File analysis result (we want close-to-realtime progress with frequent cache savings) + if (ParallelAction::WORKER_RESULT === $workerResponse['action']) { + $fileAbsolutePath = $workerResponse['file']; + $fileRelativePath = $this->directory->getRelativePathTo($fileAbsolutePath); + + // Dispatch an event for each file processed and dispatch its status (required for progress output) + $this->dispatchEvent( + FixerFileProcessedEvent::NAME, + new FixerFileProcessedEvent($workerResponse['status']) + ); + + if (isset($workerResponse['fileHash'])) { + $this->cacheManager->setFileHash($fileRelativePath, $workerResponse['fileHash']); + } + + foreach ($workerResponse['errors'] ?? [] as $error) { + $this->errorsManager->report(new Error( + $error['type'], + $error['filePath'], + null !== $error['source'] + ? SourceExceptionFactory::fromArray($error['source']) + : null, + $error['appliedFixers'], + $error['diff'] + )); + } + + // Pass-back information about applied changes (only if there are any) + if (isset($workerResponse['fixInfo'])) { + $changed[$fileRelativePath] = $workerResponse['fixInfo']; + + if ($this->stopOnViolation) { + $processPool->endAll(); + + return; + } + } + + return; + } + + if (ParallelAction::WORKER_GET_FILE_CHUNK === $workerResponse['action']) { + // Request another chunk of files, if still available + $fileChunk = $getFileChunk(); + + if (0 === \count($fileChunk)) { + $process->request(['action' => ParallelAction::RUNNER_THANK_YOU]); + $processPool->endProcessIfKnown($identifier); + + return; + } + + $process->request(['action' => ParallelAction::RUNNER_REQUEST_ANALYSIS, 'files' => $fileChunk]); + + return; + } + + if (ParallelAction::WORKER_ERROR_REPORT === $workerResponse['action']) { + throw WorkerException::fromRaw($workerResponse); // @phpstan-ignore-line + } + + throw new ParallelisationException('Unsupported action: '.($workerResponse['action'] ?? 'n/a')); + }, + + // [REACT] Handle errors encountered during worker's execution + static function (\Throwable $error) use ($processPool): void { + $processPool->endAll(); + + throw new ParallelisationException($error->getMessage(), $error->getCode(), $error); + }, + + // [REACT] Handle worker's shutdown + static function ($exitCode, string $output) use ($processPool, $identifier): void { + $processPool->endProcessIfKnown($identifier); + + if (0 === $exitCode || null === $exitCode) { + return; + } + + $errorsReported = Preg::matchAll( + \sprintf('/^(?:%s)([^\n]+)+/m', WorkerCommand::ERROR_PREFIX), + $output, + $matches + ); + + if ($errorsReported > 0) { + throw WorkerException::fromRaw(json_decode($matches[1][0], true)); + } + } + ); + } - $collection = $this->linter->isAsync() - ? new FileCachingLintingIterator($fileFilteredFileIterator, $this->linter) - : new FileLintingIterator($fileFilteredFileIterator, $this->linter); + $streamSelectLoop->run(); + + return $changed; + } + + /** + * @return _RunResult + */ + private function fixSequential(): array + { + $changed = []; + $collection = $this->getLintingFileIterator(); - /** @var \SplFileInfo $file */ foreach ($collection as $file) { $fixInfo = $this->fixFile($file, $collection->currentLintingResult()); @@ -126,6 +391,9 @@ public function fix(): array return $changed; } + /** + * @return null|array{appliedFixers: list, diff: string} + */ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult): ?array { $name = $file->getPathname(); @@ -148,8 +416,8 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu $tokens = Tokens::fromCode($old); $oldHash = $tokens->getCodeHash(); - $newHash = $oldHash; $new = $old; + $newHash = $oldHash; $appliedFixers = []; @@ -189,7 +457,7 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu $fixInfo = null; - if (!empty($appliedFixers)) { + if ([] !== $appliedFixers) { $new = $tokens->generateCode(); $newHash = $tokens->getCodeHash(); } @@ -222,7 +490,7 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu if (!file_exists($fileName)) { throw new IOException( - sprintf('Failed to write file "%s" (no longer) exists.', $file->getPathname()), + \sprintf('Failed to write file "%s" (no longer) exists.', $file->getPathname()), 0, null, $file->getPathname() @@ -231,7 +499,7 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu if (is_dir($fileName)) { throw new IOException( - sprintf('Cannot write file "%s" as the location exists as directory.', $fileName), + \sprintf('Cannot write file "%s" as the location exists as directory.', $fileName), 0, null, $fileName @@ -240,7 +508,7 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu if (!is_writable($fileName)) { throw new IOException( - sprintf('Cannot write to file "%s" as it is not writable.', $fileName), + \sprintf('Cannot write to file "%s" as it is not writable.', $fileName), 0, null, $fileName @@ -251,7 +519,7 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu $error = error_get_last(); throw new IOException( - sprintf('Failed to write file "%s", "%s".', $fileName, $error ? $error['message'] : 'no reason available'), + \sprintf('Failed to write file "%s", "%s".', $fileName, null !== $error ? $error['message'] : 'no reason available'), 0, null, $fileName @@ -260,11 +528,11 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu } } - $this->cacheManager->setFile($name, $new); + $this->cacheManager->setFileHash($name, $newHash); $this->dispatchEvent( FixerFileProcessedEvent::NAME, - new FixerFileProcessedEvent($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES) + new FixerFileProcessedEvent(null !== $fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES, $name, $newHash) ); return $fixInfo; @@ -291,4 +559,28 @@ private function dispatchEvent(string $name, Event $event): void $this->eventDispatcher->dispatch($event, $name); } + + private function getLintingFileIterator(): LintingResultAwareFileIteratorInterface + { + $fileFilterIterator = $this->getFilteringFileIterator(); + + return $this->linter->isAsync() + ? new FileCachingLintingFileIterator($fileFilterIterator, $this->linter) + : new LintingFileIterator($fileFilterIterator, $this->linter); + } + + private function getFilteringFileIterator(): FileFilterIterator + { + if (null === $this->fileIterator) { + throw new \RuntimeException('File iterator is not configured. Pass paths during Runner initialisation or set them after with `setFileIterator()`.'); + } + + return new FileFilterIterator( + $this->fileIterator instanceof \IteratorAggregate + ? $this->fileIterator->getIterator() + : $this->fileIterator, + $this->eventDispatcher, + $this->cacheManager + ); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Runner/RunnerConfig.php b/vendor/friendsofphp/php-cs-fixer/src/Runner/RunnerConfig.php new file mode 100644 index 0000000000..1d6855eb33 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Runner/RunnerConfig.php @@ -0,0 +1,62 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Runner; + +use PhpCsFixer\Runner\Parallel\ParallelConfig; + +/** + * @author Greg Korba + * + * @internal + */ +final class RunnerConfig +{ + private bool $isDryRun; + private bool $stopOnViolation; + private ParallelConfig $parallelConfig; + private ?string $configFile; + + public function __construct( + bool $isDryRun, + bool $stopOnViolation, + ParallelConfig $parallelConfig, + ?string $configFile = null + ) { + $this->isDryRun = $isDryRun; + $this->stopOnViolation = $stopOnViolation; + $this->parallelConfig = $parallelConfig; + $this->configFile = $configFile; + } + + public function isDryRun(): bool + { + return $this->isDryRun; + } + + public function shouldStopOnViolation(): bool + { + return $this->stopOnViolation; + } + + public function getParallelConfig(): ParallelConfig + { + return $this->parallelConfig; + } + + public function getConfigFile(): ?string + { + return $this->configFile; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php b/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php index bbb66acf31..86dee2e5ff 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php +++ b/vendor/friendsofphp/php-cs-fixer/src/StdinFileInfo.php @@ -23,6 +23,7 @@ final class StdinFileInfo extends \SplFileInfo { public function __construct() { + parent::__construct(__FILE__); } public function __toString(): string @@ -57,9 +58,9 @@ public function getExtension(): string return '.php'; } - public function getFileInfo($className = null): \SplFileInfo + public function getFileInfo($class = null): \SplFileInfo { - throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('Method "%s" is not implemented.', __METHOD__)); } public function getFilename(): string @@ -104,9 +105,9 @@ public function getPath(): string return ''; } - public function getPathInfo($className = null): \SplFileInfo + public function getPathInfo($class = null): \SplFileInfo { - throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('Method "%s" is not implemented.', __METHOD__)); } public function getPathname(): string @@ -161,14 +162,10 @@ public function isWritable(): bool public function openFile($openMode = 'r', $useIncludePath = false, $context = null): \SplFileObject { - throw new \BadMethodCallException(sprintf('Method "%s" is not implemented.', __METHOD__)); + throw new \BadMethodCallException(\sprintf('Method "%s" is not implemented.', __METHOD__)); } - public function setFileClass($className = null): void - { - } + public function setFileClass($className = null): void {} - public function setInfoClass($className = null): void - { - } + public function setInfoClass($className = null): void {} } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php index 8b0fae82f0..5c8de2eb56 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTransformer.php @@ -23,9 +23,6 @@ */ abstract class AbstractTransformer implements TransformerInterface { - /** - * {@inheritdoc} - */ public function getName(): string { $nameParts = explode('\\', static::class); @@ -34,16 +31,10 @@ public function getName(): string return Utils::camelCaseToUnderscore($name); } - /** - * {@inheritdoc} - */ public function getPriority(): int { return 0; } - /** - * {@inheritdoc} - */ abstract public function getCustomTokens(): array; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php index c8636eee0c..8c5476b420 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/AbstractTypeTransformer.php @@ -21,63 +21,69 @@ */ abstract class AbstractTypeTransformer extends AbstractTransformer { + private const TYPE_END_TOKENS = [')', [T_CALLABLE], [T_NS_SEPARATOR], [T_STATIC], [T_STRING], [CT::T_ARRAY_TYPEHINT]]; + + private const TYPE_TOKENS = [ + '|', '&', '(', + ...self::TYPE_END_TOKENS, + [CT::T_TYPE_ALTERNATION], [CT::T_TYPE_INTERSECTION], // some siblings may already be transformed + [T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], // technically these can be inside of type tokens array + ]; + + abstract protected function replaceToken(Tokens $tokens, int $index): void; + + /** + * @param array{0: int, 1: string}|string $originalToken + */ protected function doProcess(Tokens $tokens, int $index, $originalToken): void { if (!$tokens[$index]->equals($originalToken)) { return; } - $prevIndex = $tokens->getTokenNotOfKindsSibling($index, -1, [T_CALLABLE, T_NS_SEPARATOR, T_STRING, CT::T_ARRAY_TYPEHINT, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT]); - - /** @var Token $prevToken */ - $prevToken = $tokens[$prevIndex]; - - if ($prevToken->isGivenKind([ - CT::T_TYPE_COLON, // `:` is part of a function return type `foo(): X|Y` - CT::T_TYPE_ALTERNATION, // `|` is part of a union (chain) `X|Y` - CT::T_TYPE_INTERSECTION, - T_STATIC, T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, // `var X|Y $a;`, `private X|Y $a` or `public static X|Y $a` - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, // promoted properties - ])) { - $this->replaceToken($tokens, $index); - + if (!$this->isPartOfType($tokens, $index)) { return; } - if (\defined('T_READONLY') && $prevToken->isGivenKind(T_READONLY)) { // @TODO: drop condition when PHP 8.1+ is required - $this->replaceToken($tokens, $index); - - return; - } + $this->replaceToken($tokens, $index); + } - if (!$prevToken->equalsAny(['(', ','])) { - return; + private function isPartOfType(Tokens $tokens, int $index): bool + { + // return types and non-capturing catches + $typeColonIndex = $tokens->getTokenNotOfKindSibling($index, -1, self::TYPE_TOKENS); + if ($tokens[$typeColonIndex]->isGivenKind([T_CATCH, CT::T_TYPE_COLON, T_CONST])) { + return true; } - $prevPrevTokenIndex = $tokens->getPrevMeaningfulToken($prevIndex); - - if ($tokens[$prevPrevTokenIndex]->isGivenKind(T_CATCH)) { - $this->replaceToken($tokens, $index); + // for parameter there will be splat operator or variable after the type ("&" is ambiguous and can be reference or bitwise and) + $afterTypeIndex = $tokens->getTokenNotOfKindSibling($index, 1, self::TYPE_TOKENS); - return; + if ($tokens[$afterTypeIndex]->isGivenKind(T_ELLIPSIS)) { + return true; } - $functionKinds = [[T_FUNCTION], [T_FN]]; - $functionIndex = $tokens->getPrevTokenOfKind($prevIndex, $functionKinds); - - if (null === $functionIndex) { - return; + if (!$tokens[$afterTypeIndex]->isGivenKind(T_VARIABLE)) { + return false; } - $braceOpenIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); - $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex); - - if ($braceCloseIndex < $index) { - return; + $beforeVariableIndex = $tokens->getPrevMeaningfulToken($afterTypeIndex); + if ($tokens[$beforeVariableIndex]->equals('&')) { + $prevIndex = $tokens->getPrevTokenOfKind( + $index, + [ + '{', + '}', + ';', + [T_CLOSE_TAG], + [T_FN], + [T_FUNCTION], + ], + ); + + return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]); } - $this->replaceToken($tokens, $index); + return $tokens[$beforeVariableIndex]->equalsAny(self::TYPE_END_TOKENS); } - - abstract protected function replaceToken(Tokens $tokens, int $index): void; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php index cf60325fdc..242aedd7a8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.php @@ -18,10 +18,21 @@ /** * @internal + * * @TODO 4.0 remove this analyzer and move this logic into a transformer */ final class AlternativeSyntaxAnalyzer { + private const ALTERNATIVE_SYNTAX_BLOCK_EDGES = [ + T_IF => [T_ENDIF, T_ELSE, T_ELSEIF], + T_ELSE => [T_ENDIF], + T_ELSEIF => [T_ENDIF, T_ELSE, T_ELSEIF], + T_FOR => [T_ENDFOR], + T_FOREACH => [T_ENDFOREACH], + T_WHILE => [T_ENDWHILE], + T_SWITCH => [T_ENDSWITCH], + ]; + public function belongsToAlternativeSyntax(Tokens $tokens, int $index): bool { if (!$tokens[$index]->equals(':')) { @@ -51,4 +62,60 @@ public function belongsToAlternativeSyntax(Tokens $tokens, int $index): bool T_WHILE, ]); } + + public function findAlternativeSyntaxBlockEnd(Tokens $tokens, int $index): int + { + if (!isset($tokens[$index])) { + throw new \InvalidArgumentException("There is no token at index {$index}."); + } + + if (!$this->isStartOfAlternativeSyntaxBlock($tokens, $index)) { + throw new \InvalidArgumentException("Token at index {$index} is not the start of an alternative syntax block."); + } + + $startTokenKind = $tokens[$index]->getId(); + + if (!isset(self::ALTERNATIVE_SYNTAX_BLOCK_EDGES[$startTokenKind])) { + throw new \LogicException(\sprintf('Unknown startTokenKind: %s', $tokens[$index]->toJson())); + } + + $endTokenKinds = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES[$startTokenKind]; + + $findKinds = [[$startTokenKind]]; + foreach ($endTokenKinds as $endTokenKind) { + $findKinds[] = [$endTokenKind]; + } + + while (true) { + $index = $tokens->getNextTokenOfKind($index, $findKinds); + + if ($tokens[$index]->isGivenKind($endTokenKinds)) { + return $index; + } + + if ($this->isStartOfAlternativeSyntaxBlock($tokens, $index)) { + $index = $this->findAlternativeSyntaxBlockEnd($tokens, $index); + } + } + } + + private function isStartOfAlternativeSyntaxBlock(Tokens $tokens, int $index): bool + { + $map = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES; + $startTokenKind = $tokens[$index]->getId(); + + if (null === $startTokenKind || !isset($map[$startTokenKind])) { + return false; + } + + $index = $tokens->getNextMeaningfulToken($index); + + if ($tokens[$index]->equals('(')) { + $index = $tokens->getNextMeaningfulToken( + $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) + ); + } + + return $tokens[$index]->equals(':'); + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php index b5c3030afd..a4e72a706a 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.php @@ -22,12 +22,12 @@ final class ArgumentAnalysis /** * The name of the argument. */ - private string $name; + private ?string $name; /** * The index where the name is located in the supplied Tokens object. */ - private int $nameIndex; + private ?int $nameIndex; /** * The default value of the argument. @@ -39,12 +39,12 @@ final class ArgumentAnalysis */ private ?TypeAnalysis $typeAnalysis; - public function __construct(string $name, int $nameIndex, ?string $default, ?TypeAnalysis $typeAnalysis = null) + public function __construct(?string $name, ?int $nameIndex, ?string $default, ?TypeAnalysis $typeAnalysis = null) { $this->name = $name; $this->nameIndex = $nameIndex; - $this->default = $default ?: null; - $this->typeAnalysis = $typeAnalysis ?: null; + $this->default = $default ?? null; + $this->typeAnalysis = $typeAnalysis ?? null; } public function getDefault(): ?string @@ -57,12 +57,12 @@ public function hasDefault(): bool return null !== $this->default; } - public function getName(): string + public function getName(): ?string { return $this->name; } - public function getNameIndex(): int + public function getNameIndex(): ?int { return $this->nameIndex; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/AttributeAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/AttributeAnalysis.php new file mode 100644 index 0000000000..c817a6ffb4 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/AttributeAnalysis.php @@ -0,0 +1,73 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +/** + * @internal + * + * @phpstan-type _AttributeItems list + */ +final class AttributeAnalysis +{ + private int $startIndex; + private int $endIndex; + private int $openingBracketIndex; + private int $closingBracketIndex; + + /** + * @var _AttributeItems + */ + private array $attributes; + + /** + * @param _AttributeItems $attributes + */ + public function __construct(int $startIndex, int $endIndex, int $openingBracketIndex, int $closingBracketIndex, array $attributes) + { + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + $this->openingBracketIndex = $openingBracketIndex; + $this->closingBracketIndex = $closingBracketIndex; + $this->attributes = $attributes; + } + + public function getStartIndex(): int + { + return $this->startIndex; + } + + public function getEndIndex(): int + { + return $this->endIndex; + } + + public function getOpeningBracketIndex(): int + { + return $this->openingBracketIndex; + } + + public function getClosingBracketIndex(): int + { + return $this->closingBracketIndex; + } + + /** + * @return _AttributeItems + */ + public function getAttributes(): array + { + return $this->attributes; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php new file mode 100644 index 0000000000..48b3644d31 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php @@ -0,0 +1,63 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; + +use PhpCsFixer\Console\Application; +use PhpCsFixer\Utils; + +final class DataProviderAnalysis +{ + private string $name; + + private int $nameIndex; + + /** @var list */ + private array $usageIndices; + + /** + * @param list $usageIndices + */ + public function __construct(string $name, int $nameIndex, array $usageIndices) + { + if (!array_is_list($usageIndices)) { + Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf( + 'Parameter "usageIndices" should be a list. This will be enforced in version %d.0.', + Application::getMajorVersion() + 1 + ))); + } + + $this->name = $name; + $this->nameIndex = $nameIndex; + $this->usageIndices = $usageIndices; + } + + public function getName(): string + { + return $this->name; + } + + public function getNameIndex(): int + { + return $this->nameIndex; + } + + /** + * @return list + */ + public function getUsageIndices(): array + { + return $this->usageIndices; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php index 97fe9ce573..6260cca17f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/EnumAnalysis.php @@ -20,12 +20,12 @@ final class EnumAnalysis extends AbstractControlCaseStructuresAnalysis { /** - * @var CaseAnalysis[] + * @var list */ private array $cases; /** - * @param CaseAnalysis[] $cases + * @param list $cases */ public function __construct(int $index, int $open, int $close, array $cases) { @@ -35,7 +35,7 @@ public function __construct(int $index, int $open, int $close, array $cases) } /** - * @return CaseAnalysis[] + * @return list */ public function getCases(): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php index 59127b2fe3..9056bf87d2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php @@ -15,6 +15,9 @@ namespace PhpCsFixer\Tokenizer\Analyzer\Analysis; /** + * @author VeeWee + * @author Greg Korba + * * @internal */ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis @@ -33,6 +36,11 @@ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis */ private string $shortName; + /** + * Is the use statement part of multi-use (`use A, B, C;`, `use A\{B, C};`)? + */ + private bool $isInMulti; + /** * Is the use statement being aliased? */ @@ -48,19 +56,50 @@ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis */ private int $endIndex; + /** + * The start index of the single import in the multi-use statement. + */ + private ?int $chunkStartIndex; + + /** + * The end index of the single import in the multi-use statement. + */ + private ?int $chunkEndIndex; + /** * The type of import: class, function or constant. + * + * @var self::TYPE_* */ private int $type; - public function __construct(string $fullName, string $shortName, bool $isAliased, int $startIndex, int $endIndex, int $type) - { + /** + * @param self::TYPE_* $type + */ + public function __construct( + int $type, + string $fullName, + string $shortName, + bool $isAliased, + bool $isInMulti, + int $startIndex, + int $endIndex, + ?int $chunkStartIndex = null, + ?int $chunkEndIndex = null + ) { + if (true === $isInMulti && (null === $chunkStartIndex || null === $chunkEndIndex)) { + throw new \LogicException('Chunk start and end index must be set when the import is part of a multi-use statement.'); + } + + $this->type = $type; $this->fullName = $fullName; $this->shortName = $shortName; $this->isAliased = $isAliased; + $this->isInMulti = $isInMulti; $this->startIndex = $startIndex; $this->endIndex = $endIndex; - $this->type = $type; + $this->chunkStartIndex = $chunkStartIndex; + $this->chunkEndIndex = $chunkEndIndex; } public function getFullName(): string @@ -78,6 +117,11 @@ public function isAliased(): bool return $this->isAliased; } + public function isInMulti(): bool + { + return $this->isInMulti; + } + public function getStartIndex(): int { return $this->startIndex; @@ -88,6 +132,19 @@ public function getEndIndex(): int return $this->endIndex; } + public function getChunkStartIndex(): ?int + { + return $this->chunkStartIndex; + } + + public function getChunkEndIndex(): ?int + { + return $this->chunkEndIndex; + } + + /** + * @return self::TYPE_* + */ public function getType(): int { return $this->type; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php index 75ed4af87c..f81b48e2df 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.php @@ -20,17 +20,14 @@ final class SwitchAnalysis extends AbstractControlCaseStructuresAnalysis { /** - * @var CaseAnalysis[] + * @var list */ private array $cases; - /** - * @var null|DefaultAnalysis - */ - private $defaultAnalysis; + private ?DefaultAnalysis $defaultAnalysis; /** - * @param CaseAnalysis[] $cases + * @param list $cases */ public function __construct(int $index, int $open, int $close, array $cases, ?DefaultAnalysis $defaultAnalysis) { @@ -41,7 +38,7 @@ public function __construct(int $index, int $open, int $close, array $cases, ?De } /** - * @return CaseAnalysis[] + * @return list */ public function getCases(): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php index 95d20ed0e9..34d68be956 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php @@ -26,24 +26,28 @@ final class TypeAnalysis implements StartEndTokenAwareAnalysis * * @see https://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types * @see https://php.net/manual/en/reserved.other-reserved-words.php - * @see https://php.net/manual/en/language.pseudo-types.php * - * @var string[] + * @var list */ private static array $reservedTypes = [ 'array', 'bool', 'callable', + 'false', 'float', 'int', 'iterable', + 'list', 'mixed', 'never', - 'numeric', + 'null', 'object', + 'parent', 'resource', 'self', + 'static', 'string', + 'true', 'void', ]; @@ -53,20 +57,26 @@ final class TypeAnalysis implements StartEndTokenAwareAnalysis private int $endIndex; - private bool $nullable; + private bool $nullable = false; - public function __construct(string $name, int $startIndex, int $endIndex) + /** + * @param ($startIndex is null ? null : int) $endIndex + */ + public function __construct(string $name, ?int $startIndex = null, ?int $endIndex = null) { $this->name = $name; - $this->nullable = false; if (str_starts_with($name, '?')) { $this->name = substr($name, 1); $this->nullable = true; + } elseif (\PHP_VERSION_ID >= 8_00_00) { + $this->nullable = \in_array('null', array_map('trim', explode('|', strtolower($name))), true); } - $this->startIndex = $startIndex; - $this->endIndex = $endIndex; + if (null !== $startIndex) { + $this->startIndex = $startIndex; + $this->endIndex = $endIndex; + } } public function getName(): string @@ -86,7 +96,7 @@ public function getEndIndex(): int public function isReservedType(): bool { - return \in_array($this->name, self::$reservedTypes, true); + return \in_array(strtolower($this->name), self::$reservedTypes, true); } public function isNullable(): bool diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php index 39d9c8922c..94322071ef 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php @@ -147,11 +147,15 @@ public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumen } } + if (null === $info['name']) { + $info['type'] = null; + } + return new ArgumentAnalysis( $info['name'], $info['name_index'], $info['default'], - $info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null + null !== $info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null ); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php index c67b76bc52..3dffcd73cf 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/AttributeAnalyzer.php @@ -14,11 +14,15 @@ namespace PhpCsFixer\Tokenizer\Analyzer; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\AttributeAnalysis; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Tokens; /** * @internal + * + * @phpstan-import-type _AttributeItems from AttributeAnalysis */ final class AttributeAnalyzer { @@ -67,4 +71,111 @@ public static function isAttribute(Tokens $tokens, int $index): bool return 0 === $count; } + + /** + * Find all consecutive elements that start with #[ and end with ] and the attributes inside. + * + * @return list + */ + public static function collect(Tokens $tokens, int $index): array + { + if (!$tokens[$index]->isGivenKind(T_ATTRIBUTE)) { + throw new \InvalidArgumentException('Given index must point to an attribute.'); + } + + // Rewind to first attribute in group + while ($tokens[$prevIndex = $tokens->getPrevMeaningfulToken($index)]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $prevIndex); + } + + /** @var list $elements */ + $elements = []; + + $openingIndex = $index; + do { + $elements[] = $element = self::collectOne($tokens, $openingIndex); + $openingIndex = $tokens->getNextMeaningfulToken($element->getEndIndex()); + } while ($tokens[$openingIndex]->isGivenKind(T_ATTRIBUTE)); + + return $elements; + } + + /** + * Find one element that starts with #[ and ends with ] and the attributes inside. + */ + public static function collectOne(Tokens $tokens, int $index): AttributeAnalysis + { + if (!$tokens[$index]->isGivenKind(T_ATTRIBUTE)) { + throw new \InvalidArgumentException('Given index must point to an attribute.'); + } + + $startIndex = $index; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + // Include comments/PHPDoc if they are present + $startIndex = $tokens->getNextNonWhitespace($prevIndex); + } + + $closingIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); + $endIndex = $tokens->getNextNonWhitespace($closingIndex); + + return new AttributeAnalysis( + $startIndex, + $endIndex - 1, + $index, + $closingIndex, + self::collectAttributes($tokens, $index, $closingIndex), + ); + } + + /** + * @return _AttributeItems + */ + private static function collectAttributes(Tokens $tokens, int $index, int $closingIndex): array + { + /** @var _AttributeItems $elements */ + $elements = []; + + do { + $attributeStartIndex = $index + 1; + + $nameStartIndex = $tokens->getNextTokenOfKind($index, [[T_STRING], [T_NS_SEPARATOR]]); + $index = $tokens->getNextTokenOfKind($attributeStartIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]); + $attributeName = $tokens->generatePartialCode($nameStartIndex, $tokens->getPrevMeaningfulToken($index)); + + // Find closing parentheses, we need to do this in case there's a comma inside the parentheses + if ($tokens[$index]->equals('(')) { + $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $index = $tokens->getNextTokenOfKind($index, [',', [CT::T_ATTRIBUTE_CLOSE]]); + } + + $elements[] = [ + 'start' => $attributeStartIndex, + 'end' => $index - 1, + 'name' => $attributeName, + ]; + + $nextIndex = $index; + + // In case there's a comma right before T_ATTRIBUTE_CLOSE + if ($nextIndex < $closingIndex) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + } + } while ($nextIndex < $closingIndex); + + // End last element at newline if it exists and there's no trailing comma + --$index; + while ($tokens[$index]->isWhitespace()) { + if (Preg::match('/\R/', $tokens[$index]->getContent())) { + $lastElementKey = array_key_last($elements); + $elements[$lastElementKey]['end'] = $index - 1; + + break; + } + --$index; + } + + return $elements; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php index bc726300cc..0f2f7735d6 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/BlocksAnalyzer.php @@ -24,18 +24,14 @@ */ final class BlocksAnalyzer { - public function isBlock(Tokens $tokens, ?int $openIndex, ?int $closeIndex): bool + public function isBlock(Tokens $tokens, int $openIndex, int $closeIndex): bool { - if (null === $openIndex || null === $closeIndex) { - return false; - } - if (!$tokens->offsetExists($openIndex)) { - return false; + throw new \InvalidArgumentException(\sprintf('Tokex index %d for potential block opening does not exist.', $openIndex)); } if (!$tokens->offsetExists($closeIndex)) { - return false; + throw new \InvalidArgumentException(\sprintf('Token index %d for potential block closure does not exist.', $closeIndex)); } $blockType = $this->getBlockType($tokens[$openIndex]); @@ -47,6 +43,9 @@ public function isBlock(Tokens $tokens, ?int $openIndex, ?int $closeIndex): bool return $closeIndex === $tokens->findBlockEnd($blockType, $openIndex); } + /** + * @return Tokens::BLOCK_TYPE_* + */ private function getBlockType(Token $token): ?int { foreach (Tokens::getBlockEdgeDefinitions() as $blockType => $definition) { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php index 8a68d91c91..31e0b051cd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ClassyAnalyzer.php @@ -27,10 +27,10 @@ public function isClassyInvocation(Tokens $tokens, int $index): bool $token = $tokens[$index]; if (!$token->isGivenKind(T_STRING)) { - throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $tokens[$index]->getName())); + throw new \LogicException(\sprintf('No T_STRING at given index %d, got "%s".', $index, $tokens[$index]->getName())); } - if (\in_array(strtolower($token->getContent()), ['bool', 'float', 'int', 'iterable', 'object', 'parent', 'self', 'string', 'void', 'null', 'false', 'never'], true)) { + if ((new Analysis\TypeAnalysis($token->getContent()))->isReservedType()) { return false; } @@ -57,6 +57,10 @@ public function isClassyInvocation(Tokens $tokens, int $index): bool return true; } + if (\PHP_VERSION_ID >= 8_00_00 && $nextToken->equals(')') && $prevToken->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($prev)]->isGivenKind(T_CATCH)) { + return true; + } + if (AttributeAnalyzer::isAttribute($tokens, $index)) { return true; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php index 37e12d3a0f..c2822c60b8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/CommentsAnalyzer.php @@ -73,18 +73,7 @@ public function isBeforeStructuralElement(Tokens $tokens, int $index): bool throw new \InvalidArgumentException('Given index must point to a comment.'); } - $nextIndex = $index; - do { - $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); - - // @TODO: drop condition when PHP 8.0+ is required - if (\defined('T_ATTRIBUTE')) { - while (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_ATTRIBUTE)) { - $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $nextIndex); - $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); - } - } - } while (null !== $nextIndex && $tokens[$nextIndex]->equals('(')); + $nextIndex = $this->getNextTokenIndex($tokens, $index); if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { return false; @@ -102,19 +91,43 @@ public function isBeforeStructuralElement(Tokens $tokens, int $index): bool return true; } - if ($this->isValidLanguageConstruct($tokens, $token, $nextIndex)) { + if ($this->isValidVariableAssignment($tokens, $token, $nextIndex)) { + return true; + } + + if ($tokens[$nextIndex]->isGivenKind(CT::T_USE_TRAIT)) { return true; } return false; } + /** + * Check if comment at given index precedes return statement. + */ + public function isBeforeReturn(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) { + throw new \InvalidArgumentException('Given index must point to a comment.'); + } + + $nextIndex = $this->getNextTokenIndex($tokens, $index); + + if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) { + return false; + } + + return $tokens[$nextIndex]->isGivenKind(T_RETURN); + } + /** * Return array of indices that are part of a comment started at given index. * * @param int $index T_COMMENT index + * + * @return non-empty-list */ - public function getCommentBlockIndices(Tokens $tokens, int $index): ?array + public function getCommentBlockIndices(Tokens $tokens, int $index): array { if (!$tokens[$index]->isGivenKind(T_COMMENT)) { throw new \InvalidArgumentException('Given index must point to a comment.'); @@ -163,6 +176,7 @@ private function isStructuralElement(Tokens $tokens, int $index): bool T_PUBLIC, T_VAR, T_FUNCTION, + T_FN, T_ABSTRACT, T_CONST, T_NAMESPACE, @@ -187,6 +201,12 @@ private function isStructuralElement(Tokens $tokens, int $index): bool return true; } + if ($token->isGivenKind(T_CASE) && \defined('T_ENUM')) { + $caseParent = $tokens->getPrevTokenOfKind($index, [[T_ENUM], [T_SWITCH]]); + + return $tokens[$caseParent]->isGivenKind([T_ENUM]); + } + if ($token->isGivenKind(T_STATIC)) { return !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON); } @@ -214,11 +234,11 @@ private function isValidControl(Tokens $tokens, Token $docsToken, int $controlIn return false; } - $index = $tokens->getNextMeaningfulToken($controlIndex); - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $openParenthesisIndex = $tokens->getNextMeaningfulToken($controlIndex); + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); $docsContent = $docsToken->getContent(); - for ($index = $index + 1; $index < $endIndex; ++$index) { + for ($index = $openParenthesisIndex + 1; $index < $closeParenthesisIndex; ++$index) { $token = $tokens[$index]; if ( @@ -238,7 +258,7 @@ private function isValidControl(Tokens $tokens, Token $docsToken, int $controlIn * @param Token $docsToken docs Token * @param int $languageConstructIndex index of variable Token */ - private function isValidLanguageConstruct(Tokens $tokens, Token $docsToken, int $languageConstructIndex): bool + private function isValidVariableAssignment(Tokens $tokens, Token $docsToken, int $languageConstructIndex): bool { static $languageStructures = [ T_LIST, @@ -308,4 +328,23 @@ private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEn return $lineCount; } + + private function getNextTokenIndex(Tokens $tokens, int $startIndex): ?int + { + $nextIndex = $startIndex; + + do { + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_ATTRIBUTE')) { + while (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_ATTRIBUTE)) { + $nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $nextIndex); + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + } + } + } while (null !== $nextIndex && $tokens[$nextIndex]->equals('(')); + + return $nextIndex; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php index 30efbe1c7b..6c9f2543f7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.php @@ -25,7 +25,9 @@ final class ControlCaseStructuresAnalyzer { /** - * @param int[] $types Token types of interest of which analyzes must be returned + * @param list $types Token types of interest of which analyzes must be returned + * + * @return \Generator */ public static function findControlStructures(Tokens $tokens, array $types): \Generator { @@ -37,7 +39,7 @@ public static function findControlStructures(Tokens $tokens, array $types): \Gen foreach ($types as $type) { if (!\in_array($type, $typesWithCaseOrDefault, true)) { - throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $type)); + throw new \InvalidArgumentException(\sprintf('Unexpected type "%d".', $type)); } } @@ -46,6 +48,17 @@ public static function findControlStructures(Tokens $tokens, array $types): \Gen } $depth = -1; + + /** + * @var list, + * default: array{index: int, open: int}|null, + * alternative_syntax: bool, + * }> $stack + */ $stack = []; $isTypeOfInterest = false; @@ -87,13 +100,13 @@ public static function findControlStructures(Tokens $tokens, array $types): \Gen } if ($token->equals('{')) { - ++$stack[$depth]['brace_count']; // @phpstan-ignore-line + ++$stack[$depth]['brace_count']; continue; } if ($token->equals('}')) { - --$stack[$depth]['brace_count']; // @phpstan-ignore-line + --$stack[$depth]['brace_count']; if (0 === $stack[$depth]['brace_count']) { if ($stack[$depth]['alternative_syntax']) { @@ -170,12 +183,21 @@ public static function findControlStructures(Tokens $tokens, array $types): \Gen } } + /** + * @param array{ + * kind: int, + * index: int, + * open: int, + * end: int, + * cases: list, + * default: null|array{index: int, open: int}, + * } $analysis + */ private static function buildControlCaseStructureAnalysis(array $analysis): AbstractControlCaseStructuresAnalysis { $default = null === $analysis['default'] ? null - : new DefaultAnalysis($analysis['default']['index'], $analysis['default']['open']) - ; + : new DefaultAnalysis($analysis['default']['index'], $analysis['default']['open']); $cases = []; @@ -213,7 +235,7 @@ private static function buildControlCaseStructureAnalysis(array $analysis): Abst ); } - throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $analysis['kind'])); + throw new \InvalidArgumentException(\sprintf('Unexpected type "%d".', $analysis['kind'])); } private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int @@ -251,7 +273,7 @@ private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int return $tokens->getNextTokenOfKind($index, ['=', ';']); } - throw new \InvalidArgumentException(sprintf('Unexpected case for type "%d".', $kind)); + throw new \InvalidArgumentException(\sprintf('Unexpected case for type "%d".', $kind)); } private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): int @@ -264,9 +286,12 @@ private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): return $tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]); } - throw new \InvalidArgumentException(sprintf('Unexpected default for type "%d".', $kind)); + throw new \InvalidArgumentException(\sprintf('Unexpected default for type "%d".', $kind)); } + /** + * @return list + */ private static function getTypesWithCaseOrDefault(): array { $supportedTypes = [T_SWITCH]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/DataProviderAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/DataProviderAnalyzer.php new file mode 100644 index 0000000000..78040f3ac6 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/DataProviderAnalyzer.php @@ -0,0 +1,98 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\DocBlock\TypeExpression; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\DataProviderAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + * + * @internal + */ +final class DataProviderAnalyzer +{ + private const REGEX_CLASS = '(?:\\\?+'.TypeExpression::REGEX_IDENTIFIER + .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)'; + + /** + * @return list + */ + public function getDataProviders(Tokens $tokens, int $startIndex, int $endIndex): array + { + $methods = $this->getMethods($tokens, $startIndex, $endIndex); + + $dataProviders = []; + foreach ($methods as $methodIndex) { + $docCommentIndex = $tokens->getTokenNotOfKindSibling( + $methodIndex, + -1, + [[T_ABSTRACT], [T_COMMENT], [T_FINAL], [T_FUNCTION], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_STATIC], [T_WHITESPACE]] + ); + + if (!$tokens[$docCommentIndex]->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + Preg::matchAll('/@dataProvider\h+(('.self::REGEX_CLASS.'::)?'.TypeExpression::REGEX_IDENTIFIER.')/', $tokens[$docCommentIndex]->getContent(), $matches); + + foreach ($matches[1] as $dataProviderName) { + $dataProviders[$dataProviderName][] = $docCommentIndex; + } + } + + $dataProviderAnalyses = []; + foreach ($dataProviders as $dataProviderName => $dataProviderUsages) { + $lowercaseDataProviderName = strtolower($dataProviderName); + if (!\array_key_exists($lowercaseDataProviderName, $methods)) { + continue; + } + $dataProviderAnalyses[$methods[$lowercaseDataProviderName]] = new DataProviderAnalysis( + $tokens[$methods[$lowercaseDataProviderName]]->getContent(), + $methods[$lowercaseDataProviderName], + $dataProviderUsages, + ); + } + + ksort($dataProviderAnalyses); + + return array_values($dataProviderAnalyses); + } + + /** + * @return array + */ + private function getMethods(Tokens $tokens, int $startIndex, int $endIndex): array + { + $functions = []; + for ($index = $startIndex; $index < $endIndex; ++$index) { + if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { + continue; + } + + $functionNameIndex = $tokens->getNextNonWhitespace($index); + + if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING)) { + continue; + } + + $functions[strtolower($tokens[$functionNameIndex]->getContent())] = $functionNameIndex; + } + + return $functions; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php index e5b6cd7f9f..65d0e6ac95 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/FunctionsAnalyzer.php @@ -26,6 +26,9 @@ */ final class FunctionsAnalyzer { + /** + * @var array{tokens: string, imports: list, declarations: list} + */ private array $functionsAnalysis = ['tokens' => '', 'imports' => [], 'declarations' => []]; /** @@ -51,7 +54,10 @@ public function isGlobalFunctionCall(Tokens $tokens, int $index): bool $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); } - $possibleKind = array_merge([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, CT::T_RETURN_REF, T_STRING], Token::getObjectOperatorKinds()); + $possibleKind = [ + T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, CT::T_RETURN_REF, T_STRING, + ...Token::getObjectOperatorKinds(), + ]; // @TODO: drop condition when PHP 8.0+ is required if (\defined('T_ATTRIBUTE')) { @@ -62,27 +68,24 @@ public function isGlobalFunctionCall(Tokens $tokens, int $index): bool return false; } - if ($previousIsNamespaceSeparator) { - return true; - } - if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) { return false; } + if ($previousIsNamespaceSeparator) { + return true; + } + if ($tokens->isChanged() || $tokens->getCodeHash() !== $this->functionsAnalysis['tokens']) { $this->buildFunctionsAnalysis($tokens); } // figure out in which namespace we are - $namespaceAnalyzer = new NamespacesAnalyzer(); - - $declarations = $namespaceAnalyzer->getDeclarations($tokens); $scopeStartIndex = 0; $scopeEndIndex = \count($tokens) - 1; $inGlobalNamespace = false; - foreach ($declarations as $declaration) { + foreach ($tokens->getNamespaceDeclarations() as $declaration) { $scopeStartIndex = $declaration->getScopeStartIndex(); $scopeEndIndex = $declaration->getScopeEndIndex(); @@ -134,7 +137,7 @@ public function isGlobalFunctionCall(Tokens $tokens, int $index): bool } /** - * @return ArgumentAnalysis[] + * @return array */ public function getFunctionArguments(Tokens $tokens, int $functionIndex): array { @@ -181,7 +184,7 @@ public function getFunctionReturnType(Tokens $tokens, int $methodIndex): ?TypeAn public function isTheSameClassCall(Tokens $tokens, int $index): bool { if (!$tokens->offsetExists($index)) { - return false; + throw new \InvalidArgumentException(\sprintf('Token index %d does not exist.', $index)); } $operatorIndex = $tokens->getPrevMeaningfulToken($index); @@ -200,7 +203,11 @@ public function isTheSameClassCall(Tokens $tokens, int $index): bool return false; } - return $tokens[$referenceIndex]->equalsAny([[T_VARIABLE, '$this'], [T_STRING, 'self'], [T_STATIC, 'static']], false); + if (!$tokens[$referenceIndex]->equalsAny([[T_VARIABLE, '$this'], [T_STRING, 'self'], [T_STATIC, 'static']], false)) { + return false; + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); } private function buildFunctionsAnalysis(Tokens $tokens): void diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php index 94b12054a6..f220ccaf76 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php @@ -21,29 +21,34 @@ use PhpCsFixer\Tokenizer\TokensAnalyzer; /** + * @author VeeWee + * @author Greg Korba + * * @internal + * + * @TODO Drop `allowMultiUses` opt-in flag when all fixers are updated and can handle multi-use statements. */ final class NamespaceUsesAnalyzer { /** - * @return NamespaceUseAnalysis[] + * @return list */ - public function getDeclarationsFromTokens(Tokens $tokens): array + public function getDeclarationsFromTokens(Tokens $tokens, bool $allowMultiUses = false): array { $tokenAnalyzer = new TokensAnalyzer($tokens); $useIndices = $tokenAnalyzer->getImportUseIndexes(); - return $this->getDeclarations($tokens, $useIndices); + return $this->getDeclarations($tokens, $useIndices, $allowMultiUses); } /** - * @return NamespaceUseAnalysis[] + * @return list */ - public function getDeclarationsInNamespace(Tokens $tokens, NamespaceAnalysis $namespace): array + public function getDeclarationsInNamespace(Tokens $tokens, NamespaceAnalysis $namespace, bool $allowMultiUses = false): array { $namespaceUses = []; - foreach ($this->getDeclarationsFromTokens($tokens) as $namespaceUse) { + foreach ($this->getDeclarationsFromTokens($tokens, $allowMultiUses) as $namespaceUse) { if ($namespaceUse->getStartIndex() >= $namespace->getScopeStartIndex() && $namespaceUse->getStartIndex() <= $namespace->getScopeEndIndex()) { $namespaceUses[] = $namespaceUse; } @@ -53,47 +58,128 @@ public function getDeclarationsInNamespace(Tokens $tokens, NamespaceAnalysis $na } /** - * @return NamespaceUseAnalysis[] + * @param list $useIndices + * + * @return list */ - private function getDeclarations(Tokens $tokens, array $useIndices): array + private function getDeclarations(Tokens $tokens, array $useIndices, bool $allowMultiUses = false): array { $uses = []; foreach ($useIndices as $index) { $endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]); - $analysis = $this->parseDeclaration($tokens, $index, $endIndex); - if (null !== $analysis) { - $uses[] = $analysis; + $declarations = $this->parseDeclarations($index, $endIndex, $tokens); + if (false === $allowMultiUses) { + $declarations = array_filter($declarations, static fn (NamespaceUseAnalysis $declaration) => !$declaration->isInMulti()); + } + + if ([] !== $declarations) { + $uses = array_merge($uses, $declarations); } } return $uses; } - private function parseDeclaration(Tokens $tokens, int $startIndex, int $endIndex): ?NamespaceUseAnalysis + /** + * @return list + */ + private function parseDeclarations(int $startIndex, int $endIndex, Tokens $tokens): array { - $fullName = $shortName = ''; - $aliased = false; + $type = $this->determineImportType($tokens, $startIndex); + $potentialMulti = $tokens->getNextTokenOfKind($startIndex, [',', [CT::T_GROUP_IMPORT_BRACE_OPEN]]); + $multi = null !== $potentialMulti && $potentialMulti < $endIndex; + $index = $tokens->getNextTokenOfKind($startIndex, [[T_STRING], [T_NS_SEPARATOR]]); + $imports = []; + + while (null !== $index && $index <= $endIndex) { + $qualifiedName = $this->getNearestQualifiedName($tokens, $index); + $token = $tokens[$qualifiedName['afterIndex']]; + + if ($token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { + $groupStart = $groupIndex = $qualifiedName['afterIndex']; + $groupEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_GROUP_IMPORT_BRACE, $groupStart); + + while ($groupIndex < $groupEnd) { + $chunkStart = $tokens->getNextMeaningfulToken($groupIndex); + + // Finish parsing on trailing comma (no more chunks there) + if ($tokens[$chunkStart]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + break; + } + + $groupQualifiedName = $this->getNearestQualifiedName($tokens, $chunkStart); + $imports[] = new NamespaceUseAnalysis( + $type, + $qualifiedName['fullName'].$groupQualifiedName['fullName'], + $groupQualifiedName['shortName'], + $groupQualifiedName['aliased'], + true, + $startIndex, + $endIndex, + $chunkStart, + $tokens->getPrevMeaningfulToken($groupQualifiedName['afterIndex']) + ); + + $groupIndex = $groupQualifiedName['afterIndex']; + } - $type = NamespaceUseAnalysis::TYPE_CLASS; - for ($i = $startIndex; $i <= $endIndex; ++$i) { - $token = $tokens[$i]; - if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { - // do not touch group use declarations until the logic of this is added (for example: `use some\a\{ClassD};`) - // ignore multiple use statements that should be split into few separate statements (for example: `use BarB, BarC as C;`) - return null; - } + $index = $groupIndex; + } elseif ($token->equalsAny([',', ';', [T_CLOSE_TAG]])) { + $previousToken = $tokens->getPrevMeaningfulToken($qualifiedName['afterIndex']); + + if (!$tokens[$previousToken]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) { + $imports[] = new NamespaceUseAnalysis( + $type, + $qualifiedName['fullName'], + $qualifiedName['shortName'], + $qualifiedName['aliased'], + $multi, + $startIndex, + $endIndex, + $multi ? $index : null, + $multi ? $previousToken : null + ); + } - if ($token->isGivenKind(CT::T_FUNCTION_IMPORT)) { - $type = NamespaceUseAnalysis::TYPE_FUNCTION; - } elseif ($token->isGivenKind(CT::T_CONST_IMPORT)) { - $type = NamespaceUseAnalysis::TYPE_CONSTANT; + $index = $qualifiedName['afterIndex']; } - if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) { - continue; - } + $index = $tokens->getNextMeaningfulToken($index); + } + + return $imports; + } + + /** + * @return NamespaceUseAnalysis::TYPE_* + */ + private function determineImportType(Tokens $tokens, int $startIndex): int + { + $potentialType = $tokens[$tokens->getNextMeaningfulToken($startIndex)]; + + if ($potentialType->isGivenKind(CT::T_FUNCTION_IMPORT)) { + return NamespaceUseAnalysis::TYPE_FUNCTION; + } + + if ($potentialType->isGivenKind(CT::T_CONST_IMPORT)) { + return NamespaceUseAnalysis::TYPE_CONSTANT; + } + + return NamespaceUseAnalysis::TYPE_CLASS; + } + + /** + * @return array{fullName: string, shortName: string, aliased: bool, afterIndex: int} + */ + private function getNearestQualifiedName(Tokens $tokens, int $index): array + { + $fullName = $shortName = ''; + $aliased = false; + + while (null !== $index) { + $token = $tokens[$index]; if ($token->isGivenKind(T_STRING)) { $shortName = $token->getContent(); @@ -104,16 +190,24 @@ private function parseDeclaration(Tokens $tokens, int $startIndex, int $endIndex $fullName .= $token->getContent(); } elseif ($token->isGivenKind(T_AS)) { $aliased = true; + } elseif ($token->equalsAny([ + ',', + ';', + [CT::T_GROUP_IMPORT_BRACE_OPEN], + [CT::T_GROUP_IMPORT_BRACE_CLOSE], + [T_CLOSE_TAG], + ])) { + break; } + + $index = $tokens->getNextMeaningfulToken($index); } - return new NamespaceUseAnalysis( - trim($fullName), - $shortName, - $aliased, - $startIndex, - $endIndex, - $type - ); + return [ + 'fullName' => $fullName, + 'shortName' => $shortName, + 'aliased' => $aliased, + 'afterIndex' => $index, + ]; } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php index e5e3f572ac..69d3b41920 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/NamespacesAnalyzer.php @@ -23,7 +23,7 @@ final class NamespacesAnalyzer { /** - * @return NamespaceAnalysis[] + * @return list */ public function getDeclarations(Tokens $tokens): array { @@ -64,8 +64,15 @@ public function getDeclarations(Tokens $tokens): array $index = $scopeEndIndex; } - if (0 === \count($namespaces)) { - $namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1); + if (0 === \count($namespaces) && $tokens->isTokenKindFound(T_OPEN_TAG)) { + $namespaces[] = new NamespaceAnalysis( + '', + '', + $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0, + $openTagIndex, + $openTagIndex, + \count($tokens) - 1, + ); } return $namespaces; @@ -74,7 +81,7 @@ public function getDeclarations(Tokens $tokens): array public function getNamespaceAt(Tokens $tokens, int $index): NamespaceAnalysis { if (!$tokens->offsetExists($index)) { - throw new \InvalidArgumentException(sprintf('Token index %d does not exist.', $index)); + throw new \InvalidArgumentException(\sprintf('Token index %d does not exist.', $index)); } foreach ($this->getDeclarations($tokens) as $namespace) { @@ -83,6 +90,6 @@ public function getNamespaceAt(Tokens $tokens, int $index): NamespaceAnalysis } } - throw new \LogicException(sprintf('Unable to get the namespace at index %d.', $index)); + throw new \LogicException(\sprintf('Unable to get the namespace at index %d.', $index)); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php index 985db266b1..51a2abdeaa 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/RangeAnalyzer.php @@ -29,6 +29,9 @@ private function __construct() /** * Meaningful compare of tokens within ranges. + * + * @param array{start: int, end: int} $range1 + * @param array{start: int, end: int} $range2 */ public static function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/SwitchAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/SwitchAnalyzer.php new file mode 100644 index 0000000000..fce1f9e35a --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Analyzer/SwitchAnalyzer.php @@ -0,0 +1,69 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Analyzer; + +use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + */ +final class SwitchAnalyzer +{ + /** @var array> */ + private static array $cache = []; + + public static function belongsToSwitch(Tokens $tokens, int $index): bool + { + if (!$tokens[$index]->equals(':')) { + return false; + } + + $tokensHash = md5(serialize($tokens->toArray())); + + if (!\array_key_exists($tokensHash, self::$cache)) { + self::$cache[$tokensHash] = self::getColonIndicesForSwitch(clone $tokens); + } + + return \in_array($index, self::$cache[$tokensHash], true); + } + + /** + * @return list + */ + private static function getColonIndicesForSwitch(Tokens $tokens): array + { + $colonIndices = []; + + /** @var SwitchAnalysis $analysis */ + foreach (ControlCaseStructuresAnalyzer::findControlStructures($tokens, [T_SWITCH]) as $analysis) { + if ($tokens[$analysis->getOpenIndex()]->equals(':')) { + $colonIndices[] = $analysis->getOpenIndex(); + } + + foreach ($analysis->getCases() as $case) { + $colonIndices[] = $case->getColonIndex(); + } + + $defaultAnalysis = $analysis->getDefaultAnalysis(); + + if (null !== $defaultAnalysis) { + $colonIndices[] = $defaultAnalysis->getColonIndex(); + } + } + + return $colonIndices; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php index 8d9c8f6e55..0230f7c57b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CT.php @@ -19,45 +19,47 @@ */ final class CT { - public const T_ARRAY_INDEX_CURLY_BRACE_CLOSE = 10001; - public const T_ARRAY_INDEX_CURLY_BRACE_OPEN = 10002; - public const T_ARRAY_SQUARE_BRACE_CLOSE = 10003; - public const T_ARRAY_SQUARE_BRACE_OPEN = 10004; - public const T_ARRAY_TYPEHINT = 10005; - public const T_BRACE_CLASS_INSTANTIATION_CLOSE = 10006; - public const T_BRACE_CLASS_INSTANTIATION_OPEN = 10007; - public const T_CLASS_CONSTANT = 10008; - public const T_CONST_IMPORT = 10009; - public const T_CURLY_CLOSE = 10010; - public const T_DESTRUCTURING_SQUARE_BRACE_CLOSE = 10011; - public const T_DESTRUCTURING_SQUARE_BRACE_OPEN = 10012; - public const T_DOLLAR_CLOSE_CURLY_BRACES = 10013; - public const T_DYNAMIC_PROP_BRACE_CLOSE = 10014; - public const T_DYNAMIC_PROP_BRACE_OPEN = 10015; - public const T_DYNAMIC_VAR_BRACE_CLOSE = 10016; - public const T_DYNAMIC_VAR_BRACE_OPEN = 10017; - public const T_FUNCTION_IMPORT = 10018; - public const T_GROUP_IMPORT_BRACE_CLOSE = 10019; - public const T_GROUP_IMPORT_BRACE_OPEN = 10020; - public const T_NAMESPACE_OPERATOR = 10021; - public const T_NULLABLE_TYPE = 10022; - public const T_RETURN_REF = 10023; - public const T_TYPE_ALTERNATION = 10024; - public const T_TYPE_COLON = 10025; - public const T_USE_LAMBDA = 10026; - public const T_USE_TRAIT = 10027; - public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC = 10028; - public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED = 10029; - public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE = 10030; - public const T_ATTRIBUTE_CLOSE = 10031; - public const T_NAMED_ARGUMENT_NAME = 10032; - public const T_NAMED_ARGUMENT_COLON = 10033; - public const T_FIRST_CLASS_CALLABLE = 10034; - public const T_TYPE_INTERSECTION = 10035; + public const T_ARRAY_INDEX_CURLY_BRACE_CLOSE = 10_001; + public const T_ARRAY_INDEX_CURLY_BRACE_OPEN = 10_002; + public const T_ARRAY_SQUARE_BRACE_CLOSE = 10_003; + public const T_ARRAY_SQUARE_BRACE_OPEN = 10_004; + public const T_ARRAY_TYPEHINT = 10_005; + public const T_BRACE_CLASS_INSTANTIATION_CLOSE = 10_006; + public const T_BRACE_CLASS_INSTANTIATION_OPEN = 10_007; + public const T_CLASS_CONSTANT = 10_008; + public const T_CONST_IMPORT = 10_009; + public const T_CURLY_CLOSE = 10_010; + public const T_DESTRUCTURING_SQUARE_BRACE_CLOSE = 10_011; + public const T_DESTRUCTURING_SQUARE_BRACE_OPEN = 10_012; + public const T_DOLLAR_CLOSE_CURLY_BRACES = 10_013; + public const T_DYNAMIC_PROP_BRACE_CLOSE = 10_014; + public const T_DYNAMIC_PROP_BRACE_OPEN = 10_015; + public const T_DYNAMIC_VAR_BRACE_CLOSE = 10_016; + public const T_DYNAMIC_VAR_BRACE_OPEN = 10_017; + public const T_FUNCTION_IMPORT = 10_018; + public const T_GROUP_IMPORT_BRACE_CLOSE = 10_019; + public const T_GROUP_IMPORT_BRACE_OPEN = 10_020; + public const T_NAMESPACE_OPERATOR = 10_021; + public const T_NULLABLE_TYPE = 10_022; + public const T_RETURN_REF = 10_023; + public const T_TYPE_ALTERNATION = 10_024; + public const T_TYPE_COLON = 10_025; + public const T_USE_LAMBDA = 10_026; + public const T_USE_TRAIT = 10_027; + public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC = 10_028; + public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED = 10_029; + public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE = 10_030; + public const T_ATTRIBUTE_CLOSE = 10_031; + public const T_NAMED_ARGUMENT_NAME = 10_032; + public const T_NAMED_ARGUMENT_COLON = 10_033; + public const T_FIRST_CLASS_CALLABLE = 10_034; + public const T_TYPE_INTERSECTION = 10_035; + public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN = 10_036; + public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE = 10_037; + public const T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN = 10_038; + public const T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE = 10_039; - private function __construct() - { - } + private function __construct() {} /** * Get name for custom token. @@ -67,7 +69,7 @@ private function __construct() public static function getName(int $value): string { if (!self::has($value)) { - throw new \InvalidArgumentException(sprintf('No custom token was found for "%s".', $value)); + throw new \InvalidArgumentException(\sprintf('No custom token was found for "%s".', $value)); } $tokens = self::getMapById(); @@ -87,12 +89,15 @@ public static function has(int $value): bool return isset($tokens[$value]); } + /** + * @return array + */ private static function getMapById(): array { static $constants; if (null === $constants) { - $reflection = new \ReflectionClass(__CLASS__); + $reflection = new \ReflectionClass(self::class); $constants = array_flip($reflection->getConstants()); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php index 2c9a7cc57e..86a4a68ade 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/CodeHasher.php @@ -28,9 +28,11 @@ private function __construct() /** * Calculate hash for code. + * + * @return non-empty-string */ public static function calculateCodeHash(string $code): string { - return (string) crc32($code); + return md5($code); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Processor/ImportProcessor.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Processor/ImportProcessor.php new file mode 100644 index 0000000000..35fb0f9d42 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Processor/ImportProcessor.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Processor; + +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\WhitespacesFixerConfig; + +/** + * @author Greg Korba + */ +final class ImportProcessor +{ + private WhitespacesFixerConfig $whitespacesConfig; + + public function __construct(WhitespacesFixerConfig $whitespacesConfig) + { + $this->whitespacesConfig = $whitespacesConfig; + } + + /** + * @param array{ + * const?: array, + * class?: array, + * function?: array + * } $imports + */ + public function insertImports(Tokens $tokens, array $imports, int $atIndex): void + { + $lineEnding = $this->whitespacesConfig->getLineEnding(); + + if (!$tokens[$atIndex]->isWhitespace() || !str_contains($tokens[$atIndex]->getContent(), "\n")) { + $tokens->insertAt($atIndex, new Token([T_WHITESPACE, $lineEnding])); + } + + foreach ($imports as $type => $typeImports) { + sort($typeImports); + + $items = []; + + foreach ($typeImports as $name) { + $items = array_merge($items, [ + new Token([T_WHITESPACE, $lineEnding]), + new Token([T_USE, 'use']), + new Token([T_WHITESPACE, ' ']), + ]); + + if ('const' === $type) { + $items[] = new Token([CT::T_CONST_IMPORT, 'const']); + $items[] = new Token([T_WHITESPACE, ' ']); + } elseif ('function' === $type) { + $items[] = new Token([CT::T_FUNCTION_IMPORT, 'function']); + $items[] = new Token([T_WHITESPACE, ' ']); + } + + $items = array_merge($items, self::tokenizeName($name)); + $items[] = new Token(';'); + } + + $tokens->insertAt($atIndex, $items); + } + } + + /** + * @param class-string $name + * + * @return list + */ + public static function tokenizeName(string $name): array + { + $parts = explode('\\', $name); + $newTokens = []; + + if ('' === $parts[0]) { + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + array_shift($parts); + } + + foreach ($parts as $part) { + $newTokens[] = new Token([T_STRING, $part]); + $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); + } + + array_pop($newTokens); + + return $newTokens; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php index 6cf45fe31a..a15a0c615d 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Token.php @@ -14,6 +14,8 @@ namespace PhpCsFixer\Tokenizer; +use PhpCsFixer\Utils; + /** * Representation of single token. * As a token prototype you should understand a single element generated by token_get_all. @@ -24,17 +26,13 @@ final class Token { /** * Content of token prototype. - * - * @var string */ - private $content; + private string $content; /** * ID of token prototype, if available. - * - * @var null|int */ - private $id; + private ?int $id = null; /** * If token prototype is an array. @@ -47,20 +45,20 @@ final class Token private bool $changed = false; /** - * @param array|string $token token prototype + * @param array{int, string}|string $token token prototype */ public function __construct($token) { if (\is_array($token)) { if (!\is_int($token[0])) { - throw new \InvalidArgumentException(sprintf( + throw new \InvalidArgumentException(\sprintf( 'Id must be an int, got "%s".', get_debug_type($token[0]) )); } if (!\is_string($token[1])) { - throw new \InvalidArgumentException(sprintf( + throw new \InvalidArgumentException(\sprintf( 'Content must be a string, got "%s".', get_debug_type($token[1]) )); @@ -77,12 +75,12 @@ public function __construct($token) $this->isArray = false; $this->content = $token; } else { - throw new \InvalidArgumentException(sprintf('Cannot recognize input value as valid Token prototype, got "%s".', get_debug_type($token))); + throw new \InvalidArgumentException(\sprintf('Cannot recognize input value as valid Token prototype, got "%s".', get_debug_type($token))); } } /** - * @return int[] + * @return list */ public static function getCastTokenKinds(): array { @@ -94,7 +92,7 @@ public static function getCastTokenKinds(): array /** * Get classy tokens kinds: T_CLASS, T_INTERFACE and T_TRAIT. * - * @return int[] + * @return list */ public static function getClassyTokenKinds(): array { @@ -114,7 +112,7 @@ public static function getClassyTokenKinds(): array /** * Get object operator tokens kinds: T_OBJECT_OPERATOR and (if available) T_NULLSAFE_OBJECT_OPERATOR. * - * @return int[] + * @return list */ public static function getObjectOperatorKinds(): array { @@ -135,12 +133,12 @@ public static function getObjectOperatorKinds(): array * * If tokens are arrays, then only keys defined in parameter token are checked. * - * @param array|string|Token $other token or it's prototype - * @param bool $caseSensitive perform a case sensitive comparison + * @param array{0: int, 1?: string}|string|Token $other token or it's prototype + * @param bool $caseSensitive perform a case sensitive comparison */ public function equals($other, bool $caseSensitive = true): bool { - if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition with new MAJOR release 4.0 + if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required if ('&' === $other) { return '&' === $this->content && (null === $this->id || $this->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG])); } @@ -190,14 +188,14 @@ public function equals($other, bool $caseSensitive = true): bool // detect unknown keys unset($otherPrototype[0], $otherPrototype[1]); - return empty($otherPrototype); + return [] === $otherPrototype; } /** * Check if token is equals to one of given. * - * @param array $others array of tokens or token prototypes - * @param bool $caseSensitive perform a case sensitive comparison + * @param list $others array of tokens or token prototypes + * @param bool $caseSensitive perform a case sensitive comparison */ public function equalsAny(array $others, bool $caseSensitive = true): bool { @@ -214,12 +212,19 @@ public function equalsAny(array $others, bool $caseSensitive = true): bool * A helper method used to find out whether a certain input token has to be case-sensitively matched. * * @param array|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match - * the ones used in $others. If any is missing, the default case-sensitive + * the ones used in $sequence. If any is missing, the default case-sensitive * comparison is used * @param int $key the key of the token that has to be looked up + * + * @deprecated */ public static function isKeyCaseSensitive($caseSensitive, int $key): bool { + Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf( + 'Method "%s" is deprecated and will be removed in the next major version.', + __METHOD__ + ))); + if (\is_array($caseSensitive)) { return $caseSensitive[$key] ?? true; } @@ -228,7 +233,7 @@ public static function isKeyCaseSensitive($caseSensitive, int $key): bool } /** - * @return array|string token prototype + * @return array{int, string}|string */ public function getPrototype() { @@ -246,6 +251,8 @@ public function getPrototype() * Get token's content. * * It shall be used only for getting the content of token, not for checking it against excepted value. + * + * @return non-empty-string */ public function getContent(): string { @@ -299,7 +306,7 @@ public static function getNameForId(int $id): ?string /** * Generate array containing all keywords that exists in PHP version in use. * - * @return array + * @return list */ public static function getKeywords(): array { @@ -398,7 +405,7 @@ public function isObjectOperator(): bool /** * Check if token is one of given kind. * - * @param int|int[] $possibleKind kind or array of kinds + * @param int|list $possibleKind kind or array of kinds */ public function isGivenKind($possibleKind): bool { @@ -410,7 +417,7 @@ public function isGivenKind($possibleKind): bool */ public function isKeyword(): bool { - $keywords = static::getKeywords(); + $keywords = self::getKeywords(); return $this->isArray && isset($keywords[$this->id]); } @@ -432,7 +439,7 @@ public function isNativeConstant(): bool */ public function isMagicConstant(): bool { - $magicConstants = static::getMagicConstants(); + $magicConstants = self::getMagicConstants(); return $this->isArray && isset($magicConstants[$this->id]); } @@ -455,6 +462,15 @@ public function isWhitespace(?string $whitespaces = " \t\n\r\0\x0B"): bool return '' === trim($this->content, $whitespaces); } + /** + * @return array{ + * id: int|null, + * name: string|null, + * content: string, + * isArray: bool, + * changed: bool, + * } + */ public function toArray(): array { return [ @@ -484,7 +500,7 @@ public function toJson(): string } /** - * @param string[] $tokenNames + * @param list $tokenNames * * @return array */ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php index b3d725c887..f72e64c9e4 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php @@ -14,7 +14,11 @@ namespace PhpCsFixer\Tokenizer; +use PhpCsFixer\Console\Application; use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; +use PhpCsFixer\Utils; /** * Collection of code tokens. @@ -27,6 +31,8 @@ * * @extends \SplFixedArray * + * @method Token offsetGet($offset) + * * @final */ class Tokens extends \SplFixedArray @@ -42,9 +48,14 @@ class Tokens extends \SplFixedArray public const BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE = 9; public const BLOCK_TYPE_BRACE_CLASS_INSTANTIATION = 10; public const BLOCK_TYPE_ATTRIBUTE = 11; + public const BLOCK_TYPE_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS = 12; + public const BLOCK_TYPE_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE = 13; + public const BLOCK_TYPE_COMPLEX_STRING_VARIABLE = 14; /** * Static class cache. + * + * @var array */ private static array $cache = []; @@ -63,11 +74,11 @@ class Tokens extends \SplFixedArray private array $blockEndCache = []; /** - * crc32 hash of code string. + * A MD5 hash of the code string. * - * @var string + * @var ?non-empty-string */ - private $codeHash; + private ?string $codeHash = null; /** * Flag is collection was changed. @@ -83,10 +94,15 @@ class Tokens extends \SplFixedArray * was ever seen inside the collection (but may not be part of it any longer). * The key is token kind and the value is always true. * - * @var array + * @var array */ private array $foundTokenKinds = []; + /** + * @var null|list + */ + private ?array $namespaceDeclarations = null; + /** * Clone tokens collection. */ @@ -100,7 +116,7 @@ public function __clone() /** * Clear cache - one position or all of them. * - * @param null|string $key position to clear, when null clear all + * @param null|non-empty-string $key position to clear, when null clear all */ public static function clearCache(?string $key = null): void { @@ -110,50 +126,58 @@ public static function clearCache(?string $key = null): void return; } - if (self::hasCache($key)) { - unset(self::$cache[$key]); - } + unset(self::$cache[$key]); } /** * Detect type of block. * - * @param Token $token token - * - * @return null|array array with 'type' and 'isStart' keys or null if not found + * @return null|array{type: self::BLOCK_TYPE_*, isStart: bool} */ public static function detectBlockType(Token $token): ?array { - foreach (self::getBlockEdgeDefinitions() as $type => $definition) { - if ($token->equals($definition['start'])) { - return ['type' => $type, 'isStart' => true]; - } - - if ($token->equals($definition['end'])) { - return ['type' => $type, 'isStart' => false]; + static $blockEdgeKinds = null; + + if (null === $blockEdgeKinds) { + $blockEdgeKinds = []; + foreach (self::getBlockEdgeDefinitions() as $type => $definition) { + $blockEdgeKinds[ + \is_string($definition['start']) ? $definition['start'] : $definition['start'][0] + ] = ['type' => $type, 'isStart' => true]; + $blockEdgeKinds[ + \is_string($definition['end']) ? $definition['end'] : $definition['end'][0] + ] = ['type' => $type, 'isStart' => false]; } } - return null; + // inlined extractTokenKind() call on the hot path + /** @var int|non-empty-string */ + $tokenKind = $token->isArray() ? $token->getId() : $token->getContent(); + + return $blockEdgeKinds[$tokenKind] ?? null; } /** * Create token collection from array. * - * @param Token[] $array the array to import - * @param ?bool $saveIndices save the numeric indices used in the original array, default is yes + * @param array $array the array to import + * @param ?bool $saveIndices save the numeric indices used in the original array, default is yes */ public static function fromArray($array, $saveIndices = null): self { $tokens = new self(\count($array)); - if (null === $saveIndices || $saveIndices) { + if (false !== $saveIndices && !array_is_list($array)) { + Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf( + 'Parameter "array" should be a list. This will be enforced in version %d.0.', + Application::getMajorVersion() + 1 + ))); + foreach ($array as $key => $val) { $tokens[$key] = $val; } } else { $index = 0; - foreach ($array as $val) { $tokens[$index++] = $val; } @@ -195,57 +219,75 @@ public static function fromCode(string $code): self return $tokens; } + /** + * @return array + */ public static function getBlockEdgeDefinitions(): array { - $definitions = [ - self::BLOCK_TYPE_CURLY_BRACE => [ - 'start' => '{', - 'end' => '}', - ], - self::BLOCK_TYPE_PARENTHESIS_BRACE => [ - 'start' => '(', - 'end' => ')', - ], - self::BLOCK_TYPE_INDEX_SQUARE_BRACE => [ - 'start' => '[', - 'end' => ']', - ], - self::BLOCK_TYPE_ARRAY_SQUARE_BRACE => [ - 'start' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, '['], - 'end' => [CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']'], - ], - self::BLOCK_TYPE_DYNAMIC_PROP_BRACE => [ - 'start' => [CT::T_DYNAMIC_PROP_BRACE_OPEN, '{'], - 'end' => [CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}'], - ], - self::BLOCK_TYPE_DYNAMIC_VAR_BRACE => [ - 'start' => [CT::T_DYNAMIC_VAR_BRACE_OPEN, '{'], - 'end' => [CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}'], - ], - self::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE => [ - 'start' => [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{'], - 'end' => [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}'], - ], - self::BLOCK_TYPE_GROUP_IMPORT_BRACE => [ - 'start' => [CT::T_GROUP_IMPORT_BRACE_OPEN, '{'], - 'end' => [CT::T_GROUP_IMPORT_BRACE_CLOSE, '}'], - ], - self::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE => [ - 'start' => [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '['], - 'end' => [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']'], - ], - self::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION => [ - 'start' => [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('], - 'end' => [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'], - ], - ]; - - // @TODO: drop condition when PHP 8.0+ is required - if (\defined('T_ATTRIBUTE')) { - $definitions[self::BLOCK_TYPE_ATTRIBUTE] = [ - 'start' => [T_ATTRIBUTE, '#['], - 'end' => [CT::T_ATTRIBUTE_CLOSE, ']'], + static $definitions = null; + if (null === $definitions) { + $definitions = [ + self::BLOCK_TYPE_CURLY_BRACE => [ + 'start' => '{', + 'end' => '}', + ], + self::BLOCK_TYPE_PARENTHESIS_BRACE => [ + 'start' => '(', + 'end' => ')', + ], + self::BLOCK_TYPE_INDEX_SQUARE_BRACE => [ + 'start' => '[', + 'end' => ']', + ], + self::BLOCK_TYPE_ARRAY_SQUARE_BRACE => [ + 'start' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, '['], + 'end' => [CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']'], + ], + self::BLOCK_TYPE_DYNAMIC_PROP_BRACE => [ + 'start' => [CT::T_DYNAMIC_PROP_BRACE_OPEN, '{'], + 'end' => [CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_DYNAMIC_VAR_BRACE => [ + 'start' => [CT::T_DYNAMIC_VAR_BRACE_OPEN, '{'], + 'end' => [CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE => [ + 'start' => [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{'], + 'end' => [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_GROUP_IMPORT_BRACE => [ + 'start' => [CT::T_GROUP_IMPORT_BRACE_OPEN, '{'], + 'end' => [CT::T_GROUP_IMPORT_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE => [ + 'start' => [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '['], + 'end' => [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']'], + ], + self::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION => [ + 'start' => [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('], + 'end' => [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'], + ], + self::BLOCK_TYPE_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS => [ + 'start' => [CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '('], + 'end' => [CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')'], + ], + self::BLOCK_TYPE_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE => [ + 'start' => [CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, '{'], + 'end' => [CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, '}'], + ], + self::BLOCK_TYPE_COMPLEX_STRING_VARIABLE => [ + 'start' => [T_DOLLAR_OPEN_CURLY_BRACES, '${'], + 'end' => [CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}'], + ], ]; + + // @TODO: drop condition when PHP 8.0+ is required + if (\defined('T_ATTRIBUTE')) { + $definitions[self::BLOCK_TYPE_ATTRIBUTE] = [ + 'start' => [T_ATTRIBUTE, '#['], + 'end' => [CT::T_ATTRIBUTE_CLOSE, ']'], + ]; + } } return $definitions; @@ -256,10 +298,12 @@ public static function getBlockEdgeDefinitions(): array * * @param int $size */ + #[\ReturnTypeWillChange] public function setSize($size): bool { - if ($this->getSize() !== $size) { + if (\count($this) !== $size) { $this->changed = true; + $this->namespaceDeclarations = null; return parent::setSize($size); } @@ -274,8 +318,27 @@ public function setSize($size): bool */ public function offsetUnset($index): void { - $this->changed = true; - $this->unregisterFoundToken($this[$index]); + if (\count($this) - 1 !== $index) { + Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf( + 'Tokens should be a list - only the last index can be unset. This will be enforced in version %d.0.', + Application::getMajorVersion() + 1 + ))); + } + + if (isset($this[$index])) { + if (isset($this->blockStartCache[$index])) { + unset($this->blockEndCache[$this->blockStartCache[$index]], $this->blockStartCache[$index]); + } + if (isset($this->blockEndCache[$index])) { + unset($this->blockStartCache[$this->blockEndCache[$index]], $this->blockEndCache[$index]); + } + + $this->unregisterFoundToken($this[$index]); + + $this->changed = true; + $this->namespaceDeclarations = null; + } + parent::offsetUnset($index); } @@ -289,16 +352,28 @@ public function offsetUnset($index): void */ public function offsetSet($index, $newval): void { - $this->blockStartCache = []; - $this->blockEndCache = []; + if (0 > $index || \count($this) <= $index) { + Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf( + 'Tokens should be a list - index must be within the existing range. This will be enforced in version %d.0.', + Application::getMajorVersion() + 1 + ))); + } if (!isset($this[$index]) || !$this[$index]->equals($newval)) { - $this->changed = true; - if (isset($this[$index])) { + if (isset($this->blockStartCache[$index])) { + unset($this->blockEndCache[$this->blockStartCache[$index]], $this->blockStartCache[$index]); + } + if (isset($this->blockEndCache[$index])) { + unset($this->blockStartCache[$this->blockEndCache[$index]], $this->blockEndCache[$index]); + } + $this->unregisterFoundToken($this[$index]); } + $this->changed = true; + $this->namespaceDeclarations = null; + $this->registerFoundToken($newval); } @@ -320,10 +395,9 @@ public function clearChanged(): void */ public function clearEmptyTokens(): void { - $limit = $this->count(); - $index = 0; + $limit = \count($this); - for (; $index < $limit; ++$index) { + for ($index = 0; $index < $limit; ++$index) { if ($this->isEmptyAt($index)) { break; } @@ -341,7 +415,14 @@ public function clearEmptyTokens(): void } } - // we are moving the tokens, we need to clear the indices Cache + // should already be true + if (!$this->changed) { + // must never happen + throw new \LogicException('Unexpected non-changed collection with _EMPTY_ Tokens. Fix the code!'); + } + + // we are moving the tokens, we need to clear the index-based Cache + $this->namespaceDeclarations = null; $this->blockStartCache = []; $this->blockEndCache = []; @@ -369,18 +450,16 @@ public function ensureWhitespaceAtIndex(int $index, int $indexOffset, string $wh if (str_starts_with($whitespace, "\r\n")) { $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent())."\r\n"]); - return \strlen($whitespace) > 2 // can be removed on PHP 7; https://php.net/manual/en/function.substr.php + return \strlen($whitespace) > 2 // @TODO: can be removed on PHP 8; https://php.net/manual/en/function.substr.php ? substr($whitespace, 2) - : '' - ; + : ''; } $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent()).$whitespace[0]]); - return \strlen($whitespace) > 1 // can be removed on PHP 7; https://php.net/manual/en/function.substr.php + return \strlen($whitespace) > 1 // @TODO: can be removed on PHP 8; https://php.net/manual/en/function.substr.php ? substr($whitespace, 1) - : '' - ; + : ''; } return $whitespace; @@ -413,10 +492,10 @@ public function ensureWhitespaceAtIndex(int $index, int $indexOffset, string $wh } /** - * @param int $type type of block, one of BLOCK_TYPE_* - * @param int $searchIndex index of opening brace + * @param self::BLOCK_TYPE_* $type type of block + * @param int $searchIndex index of opening brace * - * @return int index of closing brace + * @return int<0, max> index of closing brace */ public function findBlockEnd(int $type, int $searchIndex): int { @@ -424,10 +503,10 @@ public function findBlockEnd(int $type, int $searchIndex): int } /** - * @param int $type type of block, one of BLOCK_TYPE_* - * @param int $searchIndex index of closing brace + * @param self::BLOCK_TYPE_* $type type of block + * @param int $searchIndex index of closing brace * - * @return int index of opening brace + * @return int<0, max> index of opening brace */ public function findBlockStart(int $type, int $searchIndex): int { @@ -435,16 +514,16 @@ public function findBlockStart(int $type, int $searchIndex): int } /** - * @param array|int $possibleKind kind or array of kind - * @param int $start optional offset - * @param null|int $end optional limit + * @param int|non-empty-list $possibleKind kind or array of kinds + * @param int $start optional offset + * @param null|int $end optional limit * - * @return array array of tokens of given kinds or assoc array of arrays + * @return ($possibleKind is int ? array, Token> : array, Token>>) */ public function findGivenKind($possibleKind, int $start = 0, ?int $end = null): array { if (null === $end) { - $end = $this->count(); + $end = \count($this); } $elements = []; @@ -454,9 +533,7 @@ public function findGivenKind($possibleKind, int $start = 0, ?int $end = null): $elements[$kind] = []; } - $possibleKinds = array_filter($possibleKinds, function ($kind): bool { - return $this->isTokenKindFound($kind); - }); + $possibleKinds = array_filter($possibleKinds, fn ($kind): bool => $this->isTokenKindFound($kind)); if (\count($possibleKinds) > 0) { for ($i = $start; $i < $end; ++$i) { @@ -521,9 +598,9 @@ public function getNextNonWhitespace(int $index, ?string $whitespaces = null): ? * * This method is shorthand for getTokenOfKindSibling method. * - * @param int $index token index - * @param array $tokens possible tokens - * @param bool $caseSensitive perform a case sensitive comparison + * @param int $index token index + * @param list $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison */ public function getNextTokenOfKind(int $index, array $tokens = [], bool $caseSensitive = true): ?int { @@ -534,14 +611,13 @@ public function getNextTokenOfKind(int $index, array $tokens = [], bool $caseSen * Get index for closest sibling token which is non whitespace. * * @param int $index token index - * @param int $direction direction for looking, +1 or -1 + * @param -1|1 $direction * @param null|string $whitespaces whitespaces characters for Token::isWhitespace */ public function getNonWhitespaceSibling(int $index, int $direction, ?string $whitespaces = null): ?int { while (true) { $index += $direction; - if (!$this->offsetExists($index)) { return null; } @@ -569,9 +645,9 @@ public function getPrevNonWhitespace(int $index, ?string $whitespaces = null): ? * Get index for closest previous token of given kind. * This method is shorthand for getTokenOfKindSibling method. * - * @param int $index token index - * @param array $tokens possible tokens - * @param bool $caseSensitive perform a case sensitive comparison + * @param int $index token index + * @param list $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison */ public function getPrevTokenOfKind(int $index, array $tokens = [], bool $caseSensitive = true): ?int { @@ -581,16 +657,14 @@ public function getPrevTokenOfKind(int $index, array $tokens = [], bool $caseSen /** * Get index for closest sibling token of given kind. * - * @param int $index token index - * @param int $direction direction for looking, +1 or -1 - * @param array $tokens possible tokens - * @param bool $caseSensitive perform a case sensitive comparison + * @param int $index token index + * @param -1|1 $direction + * @param list $tokens possible tokens + * @param bool $caseSensitive perform a case sensitive comparison */ public function getTokenOfKindSibling(int $index, int $direction, array $tokens = [], bool $caseSensitive = true): ?int { - $tokens = array_filter($tokens, function ($token): bool { - return $this->isTokenKindFound($this->extractTokenKind($token)); - }); + $tokens = array_filter($tokens, fn ($token): bool => $this->isTokenKindFound($this->extractTokenKind($token))); if (0 === \count($tokens)) { return null; @@ -598,7 +672,6 @@ public function getTokenOfKindSibling(int $index, int $direction, array $tokens while (true) { $index += $direction; - if (!$this->offsetExists($index)) { return null; } @@ -612,44 +685,40 @@ public function getTokenOfKindSibling(int $index, int $direction, array $tokens /** * Get index for closest sibling token not of given kind. * - * @param int $index token index - * @param int $direction direction for looking, +1 or -1 - * @param array $tokens possible tokens + * @param int $index token index + * @param -1|1 $direction + * @param list $tokens possible tokens */ public function getTokenNotOfKindSibling(int $index, int $direction, array $tokens = []): ?int { return $this->getTokenNotOfKind( $index, $direction, - function (int $a) use ($tokens): bool { - return $this[$a]->equalsAny($tokens); - } + fn (int $a): bool => $this[$a]->equalsAny($tokens), ); } /** * Get index for closest sibling token not of given kind. * - * @param int $index token index - * @param int $direction direction for looking, +1 or -1 - * @param array $kinds possible tokens kinds + * @param int $index token index + * @param -1|1 $direction + * @param list $kinds possible tokens kinds */ public function getTokenNotOfKindsSibling(int $index, int $direction, array $kinds = []): ?int { return $this->getTokenNotOfKind( $index, $direction, - function (int $index) use ($kinds): bool { - return $this[$index]->isGivenKind($kinds); - } + fn (int $index): bool => $this[$index]->isGivenKind($kinds), ); } /** * Get index for closest sibling token that is not a whitespace, comment or attribute. * - * @param int $index token index - * @param int $direction direction for looking, +1 or -1 + * @param int $index token index + * @param -1|1 $direction */ public function getMeaningfulTokenSibling(int $index, int $direction): ?int { @@ -663,14 +732,13 @@ public function getMeaningfulTokenSibling(int $index, int $direction): ?int /** * Get index for closest sibling token which is not empty. * - * @param int $index token index - * @param int $direction direction for looking, +1 or -1 + * @param int $index token index + * @param -1|1 $direction */ public function getNonEmptySibling(int $index, int $direction): ?int { while (true) { $index += $direction; - if (!$this->offsetExists($index)) { return null; } @@ -704,14 +772,14 @@ public function getPrevMeaningfulToken(int $index): ?int /** * Find a sequence of meaningful tokens and returns the array of their locations. * - * @param array $sequence an array of tokens (kinds) (same format used by getNextTokenOfKind) - * @param int $start start index, defaulting to the start of the file - * @param null|int $end end index, defaulting to the end of the file - * @param array|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match - * the ones used in $others. If any is missing, the default case-sensitive - * comparison is used + * @param non-empty-list $sequence an array of token (kinds) + * @param int $start start index, defaulting to the start of the file + * @param null|int $end end index, defaulting to the end of the file + * @param array|bool $caseSensitive global case sensitiveness or a list of booleans, whose keys should match + * the ones used in $sequence. If any is missing, the default case-sensitive + * comparison is used * - * @return null|array an array containing the tokens matching the sequence elements, indexed by their position + * @return null|non-empty-array, Token> an array containing the tokens matching the sequence elements, indexed by their position */ public function findSequence(array $sequence, int $start = 0, ?int $end = null, $caseSensitive = true): ?array { @@ -743,11 +811,11 @@ public function findSequence(array $sequence, int $start = 0, ?int $end = null, } if ($token->isGivenKind($nonMeaningFullKind)) { - throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: "%s".', $key)); + throw new \InvalidArgumentException(\sprintf('Non-meaningful token at position: "%s".', $key)); } if ('' === $token->getContent()) { - throw new \InvalidArgumentException(sprintf('Non-meaningful (empty) token at position: "%s".', $key)); + throw new \InvalidArgumentException(\sprintf('Non-meaningful (empty) token at position: "%s".', $key)); } } @@ -759,10 +827,10 @@ public function findSequence(array $sequence, int $start = 0, ?int $end = null, // remove the first token from the sequence, so we can freely iterate through the sequence after a match to // the first one is found - $key = key($sequence); - $firstCs = Token::isKeyCaseSensitive($caseSensitive, $key); - $firstToken = $sequence[$key]; - unset($sequence[$key]); + $firstKey = array_key_first($sequence); + $firstCs = self::isKeyCaseSensitive($caseSensitive, $firstKey); + $firstToken = $sequence[$firstKey]; + unset($sequence[$firstKey]); // begin searching for the first token in the sequence (start included) $index = $start - 1; @@ -789,7 +857,7 @@ public function findSequence(array $sequence, int $start = 0, ?int $end = null, return null; } - if (!$this[$currIdx]->equals($token, Token::isKeyCaseSensitive($caseSensitive, $key))) { + if (!$this[$currIdx]->equals($token, self::isKeyCaseSensitive($caseSensitive, $key))) { // not a match, restart the outer loop continue 2; } @@ -811,13 +879,11 @@ public function findSequence(array $sequence, int $start = 0, ?int $end = null, /** * Insert instances of Token inside collection. * - * @param int $index start inserting index - * @param array|Token|Tokens $items instances of Token to insert + * @param int $index start inserting index + * @param list|Token|Tokens $items instances of Token to insert */ public function insertAt(int $index, $items): void { - $items = \is_array($items) || $items instanceof self ? $items : [$items]; - $this->insertSlices([$index => $items]); } @@ -825,7 +891,7 @@ public function insertAt(int $index, $items): void * Insert a slices or individual Tokens into multiple places in a single run. * * This approach is kind-of an experiment - it's proven to improve performance a lot for big files that needs plenty of new tickets to be inserted, - * like edge case example of 3.7h vs 4s (https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3996#issuecomment-455617637), + * like edge case example of 3.7h vs 4s (https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/3996#issuecomment-455617637), * yet at same time changing a logic of fixers in not-always easy way. * * To be discussed: @@ -838,7 +904,7 @@ public function insertAt(int $index, $items): void * * @internal * - * @param array|Token|Tokens> $slices + * @param array|Token|Tokens> $slices */ public function insertSlices(array $slices): void { @@ -854,16 +920,17 @@ public function insertSlices(array $slices): void $oldSize = \count($this); $this->changed = true; + $this->namespaceDeclarations = null; $this->blockStartCache = []; $this->blockEndCache = []; $this->setSize($oldSize + $itemsCount); krsort($slices); - $farthestSliceIndex = key($slices); + $farthestSliceIndex = array_key_first($slices); // We check only the farthest index, if it's within the size of collection, other indices will be valid too. if (!\is_int($farthestSliceIndex) || $farthestSliceIndex > $oldSize) { - throw new \OutOfBoundsException(sprintf('Cannot insert index "%s" outside of collection.', $farthestSliceIndex)); + throw new \OutOfBoundsException(\sprintf('Cannot insert index "%s" outside of collection.', $farthestSliceIndex)); } $previousSliceIndex = $oldSize; @@ -872,14 +939,14 @@ public function insertSlices(array $slices): void // that way we get around additional overhead this class adds with overridden offset* methods. foreach ($slices as $index => $slice) { if (!\is_int($index) || $index < 0) { - throw new \OutOfBoundsException(sprintf('Invalid index "%s".', $index)); + throw new \OutOfBoundsException(\sprintf('Invalid index "%s".', $index)); } $slice = \is_array($slice) || $slice instanceof self ? $slice : [$slice]; $sliceCount = \count($slice); for ($i = $previousSliceIndex - 1; $i >= $index; --$i) { - parent::offsetSet($i + $itemsCount, parent::offsetGet($i)); + parent::offsetSet($i + $itemsCount, $this[$i]); } $previousSliceIndex = $index; @@ -920,9 +987,9 @@ public function clearAt(int $index): void /** * Override tokens at given range. * - * @param int $indexStart start overriding index - * @param int $indexEnd end overriding index - * @param array|Tokens $items tokens to insert + * @param int $indexStart start overriding index + * @param int $indexEnd end overriding index + * @param array|Tokens $items tokens to insert */ public function overrideRange(int $indexStart, int $indexEnd, iterable $items): void { @@ -985,6 +1052,8 @@ public function setCode(string $code): void // clear memory $this->setSize(0); + $this->blockStartCache = []; + $this->blockEndCache = []; $tokens = token_get_all($code, TOKEN_PARSE); @@ -1002,12 +1071,13 @@ public function setCode(string $code): void $this->registerFoundToken($token); } - if (\PHP_VERSION_ID < 80000) { + if (\PHP_VERSION_ID < 8_00_00) { $this->rewind(); } $this->changeCodeHash(self::calculateCodeHash($code)); $this->changed = true; + $this->namespaceDeclarations = null; } public function toJson(): string @@ -1018,20 +1088,22 @@ public function toJson(): string $output[$index] = $token->toArray(); } - if (\PHP_VERSION_ID < 80000) { + if (\PHP_VERSION_ID < 8_00_00) { $this->rewind(); } - return json_encode($output, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK); + return json_encode($output, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK); } /** * Check if all token kinds given as argument are found. + * + * @param list $tokenKinds */ public function isAllTokenKindsFound(array $tokenKinds): bool { foreach ($tokenKinds as $tokenKind) { - if (empty($this->foundTokenKinds[$tokenKind])) { + if (!isset($this->foundTokenKinds[$tokenKind])) { return false; } } @@ -1041,11 +1113,13 @@ public function isAllTokenKindsFound(array $tokenKinds): bool /** * Check if any token kind given as argument is found. + * + * @param list $tokenKinds */ public function isAnyTokenKindsFound(array $tokenKinds): bool { foreach ($tokenKinds as $tokenKind) { - if (!empty($this->foundTokenKinds[$tokenKind])) { + if (isset($this->foundTokenKinds[$tokenKind])) { return true; } } @@ -1060,7 +1134,7 @@ public function isAnyTokenKindsFound(array $tokenKinds): bool */ public function isTokenKindFound($tokenKind): bool { - return !empty($this->foundTokenKinds[$tokenKind]); + return isset($this->foundTokenKinds[$tokenKind]); } /** @@ -1089,19 +1163,12 @@ public function clearRange(int $indexStart, int $indexEnd): void */ public function isMonolithicPhp(): bool { - if (0 === $this->count()) { + if (1 !== ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO))) { return false; } - if ($this->countTokenKind(T_INLINE_HTML) > 1) { - return false; - } - - if (1 === $this->countTokenKind(T_INLINE_HTML)) { - return 1 === Preg::match('/^#!.+$/', $this[0]->getContent()); - } - - return 1 === ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO)); + return 0 === $this->countTokenKind(T_INLINE_HTML) + || (1 === $this->countTokenKind(T_INLINE_HTML) && Preg::match('/^#!.+$/', $this[0]->getContent())); } /** @@ -1157,6 +1224,20 @@ public function clearTokenAndMergeSurroundingWhitespace(int $index): void $this->clearAt($nextIndex); } + /** + * @internal This is performance-related workaround for lack of proper DI, may be removed at some point + * + * @return list + */ + public function getNamespaceDeclarations(): array + { + if (null === $this->namespaceDeclarations) { + $this->namespaceDeclarations = (new NamespacesAnalyzer())->getDeclarations($this); + } + + return $this->namespaceDeclarations; + } + /** * @internal */ @@ -1166,6 +1247,9 @@ protected function applyTransformers(): void $transformers->transform($this); } + /** + * @param -1|1 $direction + */ private function removeWhitespaceSafely(int $index, int $direction, ?string $whitespaces = null): void { $whitespaceIndex = $this->getNonEmptySibling($index, $direction); @@ -1197,18 +1281,18 @@ private function removeWhitespaceSafely(int $index, int $direction, ?string $whi } /** - * @param int $type type of block, one of BLOCK_TYPE_* - * @param int $searchIndex index of starting brace - * @param bool $findEnd if method should find block's end or start + * @param self::BLOCK_TYPE_* $type type of block + * @param int $searchIndex index of starting brace + * @param bool $findEnd if method should find block's end or start * - * @return int index of opposite brace + * @return int<0, max> index of opposite brace */ private function findOppositeBlockEdge(int $type, int $searchIndex, bool $findEnd): int { $blockEdgeDefinitions = self::getBlockEdgeDefinitions(); if (!isset($blockEdgeDefinitions[$type])) { - throw new \InvalidArgumentException(sprintf('Invalid param type: "%s".', $type)); + throw new \InvalidArgumentException(\sprintf('Invalid param type: "%s".', $type)); } if ($findEnd && isset($this->blockStartCache[$searchIndex])) { @@ -1222,7 +1306,7 @@ private function findOppositeBlockEdge(int $type, int $searchIndex, bool $findEn $startEdge = $blockEdgeDefinitions[$type]['start']; $endEdge = $blockEdgeDefinitions[$type]['end']; $startIndex = $searchIndex; - $endIndex = $this->count() - 1; + $endIndex = \count($this) - 1; $indexOffset = 1; if (!$findEnd) { @@ -1232,7 +1316,7 @@ private function findOppositeBlockEdge(int $type, int $searchIndex, bool $findEn } if (!$this[$startIndex]->equals($startEdge)) { - throw new \InvalidArgumentException(sprintf('Invalid param $startIndex - not a proper block "%s".', $findEnd ? 'start' : 'end')); + throw new \InvalidArgumentException(\sprintf('Invalid param $startIndex - not a proper block "%s".', $findEnd ? 'start' : 'end')); } $blockLevel = 0; @@ -1256,7 +1340,7 @@ private function findOppositeBlockEdge(int $type, int $searchIndex, bool $findEn } if (!$this[$index]->equals($endEdge)) { - throw new \UnexpectedValueException(sprintf('Missing block "%s".', $findEnd ? 'end' : 'start')); + throw new \UnexpectedValueException(\sprintf('Missing block "%s".', $findEnd ? 'end' : 'start')); } if ($startIndex < $index) { @@ -1272,6 +1356,8 @@ private function findOppositeBlockEdge(int $type, int $searchIndex, bool $findEn /** * Calculate hash for code. + * + * @return non-empty-string */ private static function calculateCodeHash(string $code): string { @@ -1281,12 +1367,12 @@ private static function calculateCodeHash(string $code): string /** * Get cache value for given key. * - * @param string $key item key + * @param non-empty-string $key item key */ private static function getCache(string $key): self { if (!self::hasCache($key)) { - throw new \OutOfBoundsException(sprintf('Unknown cache key: "%s".', $key)); + throw new \OutOfBoundsException(\sprintf('Unknown cache key: "%s".', $key)); } return self::$cache[$key]; @@ -1295,7 +1381,7 @@ private static function getCache(string $key): self /** * Check if given key exists in cache. * - * @param string $key item key + * @param non-empty-string $key item key */ private static function hasCache(string $key): bool { @@ -1303,8 +1389,8 @@ private static function hasCache(string $key): bool } /** - * @param string $key item key - * @param Tokens $value item value + * @param non-empty-string $key item key + * @param Tokens $value item value */ private static function setCache(string $key, self $value): void { @@ -1316,7 +1402,7 @@ private static function setCache(string $key, self $value): void * * Remove old cache and set new one. * - * @param string $codeHash new code hash + * @param non-empty-string $codeHash new code hash */ private function changeCodeHash(string $codeHash): void { @@ -1331,65 +1417,61 @@ private function changeCodeHash(string $codeHash): void /** * Register token as found. * - * @param array|string|Token $token token prototype + * @param array{int}|string|Token $token token prototype */ private function registerFoundToken($token): void { // inlined extractTokenKind() call on the hot path + /** @var int|non-empty-string */ $tokenKind = $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) - : (\is_array($token) ? $token[0] : $token) - ; - - if (!isset($this->foundTokenKinds[$tokenKind])) { - $this->foundTokenKinds[$tokenKind] = 0; - } + : (\is_array($token) ? $token[0] : $token); + $this->foundTokenKinds[$tokenKind] ??= 0; ++$this->foundTokenKinds[$tokenKind]; } /** - * Register token as found. + * Unregister token as not found. * - * @param array|string|Token $token token prototype + * @param array{int}|string|Token $token token prototype */ private function unregisterFoundToken($token): void { // inlined extractTokenKind() call on the hot path + /** @var int|non-empty-string */ $tokenKind = $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) - : (\is_array($token) ? $token[0] : $token) - ; + : (\is_array($token) ? $token[0] : $token); - if (!isset($this->foundTokenKinds[$tokenKind])) { - return; + if (1 === $this->foundTokenKinds[$tokenKind]) { + unset($this->foundTokenKinds[$tokenKind]); + } else { + --$this->foundTokenKinds[$tokenKind]; } - - --$this->foundTokenKinds[$tokenKind]; } /** - * @param array|string|Token $token token prototype + * @param array{int}|string|Token $token token prototype * - * @return int|string + * @return int|non-empty-string */ private function extractTokenKind($token) { return $token instanceof Token ? ($token->isArray() ? $token->getId() : $token->getContent()) - : (\is_array($token) ? $token[0] : $token) - ; + : (\is_array($token) ? $token[0] : $token); } /** - * @param int $index token index - * @param int $direction direction for looking, +1 or -1 + * @param int $index token index + * @param -1|1 $direction + * @param callable(int): bool $filter */ private function getTokenNotOfKind(int $index, int $direction, callable $filter): ?int { while (true) { $index += $direction; - if (!$this->offsetExists($index)) { return null; } @@ -1401,4 +1483,21 @@ private function getTokenNotOfKind(int $index, int $direction, callable $filter) return $index; } } + + /** + * A helper method used to find out whether a certain input token has to be case-sensitively matched. + * + * @param array|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match + * the ones used in $sequence. If any is missing, the default case-sensitive + * comparison is used + * @param int $key the key of the token that has to be looked up + */ + private static function isKeyCaseSensitive($caseSensitive, int $key): bool + { + if (\is_array($caseSensitive)) { + return $caseSensitive[$key] ?? true; + } + + return $caseSensitive; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php index faf8f61748..50f97c1eff 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TokensAnalyzer.php @@ -26,6 +26,8 @@ * @author Gregor Harlan * * @internal + * + * @phpstan-type _ClassyElementType 'case'|'const'|'method'|'property'|'trait_import' */ final class TokensAnalyzer { @@ -34,10 +36,7 @@ final class TokensAnalyzer */ private Tokens $tokens; - /** - * @var ?GotoLabelAnalyzer - */ - private $gotoLabelAnalyzer; + private ?GotoLabelAnalyzer $gotoLabelAnalyzer = null; public function __construct(Tokens $tokens) { @@ -47,7 +46,7 @@ public function __construct(Tokens $tokens) /** * Get indices of methods and properties in classy code (classes, interfaces and traits). * - * @return array[] + * @return array */ public function getClassyElements(): array { @@ -65,12 +64,47 @@ public function getClassyElements(): array return $elements; } + /** + * Get indices of modifiers of a classy code (classes, interfaces and traits). + * + * @return array{ + * final: int|null, + * abstract: int|null, + * readonly: int|null + * } + */ + public function getClassyModifiers(int $index): array + { + if (!$this->tokens[$index]->isClassy()) { + throw new \InvalidArgumentException(\sprintf('Not an "classy" at given index %d.', $index)); + } + + $readOnlyPossible = \defined('T_READONLY'); // @TODO: drop condition when PHP 8.2+ is required + $modifiers = ['final' => null, 'abstract' => null, 'readonly' => null]; + + while (true) { + $index = $this->tokens->getPrevMeaningfulToken($index); + + if ($this->tokens[$index]->isGivenKind(T_FINAL)) { + $modifiers['final'] = $index; + } elseif ($this->tokens[$index]->isGivenKind(T_ABSTRACT)) { + $modifiers['abstract'] = $index; + } elseif ($readOnlyPossible && $this->tokens[$index]->isGivenKind(T_READONLY)) { + $modifiers['readonly'] = $index; + } else { // no need to skip attributes as it is not possible on PHP8.2 + break; + } + } + + return $modifiers; + } + /** * Get indices of namespace uses. * * @param bool $perNamespace Return namespace uses per namespace * - * @return int[]|int[][] + * @return ($perNamespace is true ? array> : list) */ public function getImportUseIndexes(bool $perNamespace = false): array { @@ -125,7 +159,7 @@ public function isArray(int $index): bool public function isArrayMultiLine(int $index): bool { if (!$this->isArray($index)) { - throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index)); + throw new \InvalidArgumentException(\sprintf('Not an array at given index %d.', $index)); } $tokens = $this->tokens; @@ -144,7 +178,7 @@ public function isBlockMultiline(Tokens $tokens, int $index): bool $blockType = Tokens::detectBlockType($tokens[$index]); if (null === $blockType || !$blockType['isStart']) { - throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index)); + throw new \InvalidArgumentException(\sprintf('Not an block start at given index %d.', $index)); } $endIndex = $tokens->findBlockEnd($blockType['type'], $index); @@ -172,23 +206,14 @@ public function isBlockMultiline(Tokens $tokens, int $index): bool } /** - * Returns the attributes of the method under the given index. - * - * The array has the following items: - * 'visibility' int|null T_PRIVATE, T_PROTECTED or T_PUBLIC - * 'static' bool - * 'abstract' bool - * 'final' bool + * @param int $index Index of the T_FUNCTION token * - * @param int $index Token index of the method (T_FUNCTION) + * @return array{visibility: null|T_PRIVATE|T_PROTECTED|T_PUBLIC, static: bool, abstract: bool, final: bool} */ public function getMethodAttributes(int $index): array { - $tokens = $this->tokens; - $token = $tokens[$index]; - - if (!$token->isGivenKind(T_FUNCTION)) { - throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $token->getName())); + if (!$this->tokens[$index]->isGivenKind(T_FUNCTION)) { + throw new \LogicException(\sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); } $attributes = [ @@ -199,10 +224,8 @@ public function getMethodAttributes(int $index): array ]; for ($i = $index; $i >= 0; --$i) { - $tokenIndex = $tokens->getPrevMeaningfulToken($i); - - $i = $tokenIndex; - $token = $tokens[$tokenIndex]; + $i = $this->tokens->getPrevMeaningfulToken($i); + $token = $this->tokens[$i]; if ($token->isGivenKind(T_STATIC)) { $attributes['static'] = true; @@ -256,7 +279,7 @@ public function getMethodAttributes(int $index): array public function isAnonymousClass(int $index): bool { if (!$this->tokens[$index]->isClassy()) { - throw new \LogicException(sprintf('No classy token at given index %d.', $index)); + throw new \LogicException(\sprintf('No classy token at given index %d.', $index)); } if (!$this->tokens[$index]->isGivenKind(T_CLASS)) { @@ -265,6 +288,10 @@ public function isAnonymousClass(int $index): bool $index = $this->tokens->getPrevMeaningfulToken($index); + if (\defined('T_READONLY') && $this->tokens[$index]->isGivenKind(T_READONLY)) { // @TODO: drop condition when PHP 8.1+ is required + $index = $this->tokens->getPrevMeaningfulToken($index); + } + while ($this->tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { $index = $this->tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index); $index = $this->tokens->getPrevMeaningfulToken($index); @@ -279,7 +306,7 @@ public function isAnonymousClass(int $index): bool public function isLambda(int $index): bool { if (!$this->tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) { - throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); + throw new \LogicException(\sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); } $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index); @@ -294,13 +321,47 @@ public function isLambda(int $index): bool return $startParenthesisToken->equals('('); } + public function getLastTokenIndexOfArrowFunction(int $index): int + { + if (!$this->tokens[$index]->isGivenKind(T_FN)) { + throw new \InvalidArgumentException(\sprintf('Not an "arrow function" at given index %d.', $index)); + } + + $stopTokens = [')', ']', ',', ';', [T_CLOSE_TAG]]; + $index = $this->tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]); + + while (true) { + $index = $this->tokens->getNextMeaningfulToken($index); + + if ($this->tokens[$index]->equalsAny($stopTokens)) { + break; + } + + $blockType = Tokens::detectBlockType($this->tokens[$index]); + + if (null === $blockType) { + continue; + } + + if ($blockType['isStart']) { + $index = $this->tokens->findBlockEnd($blockType['type'], $index); + + continue; + } + + break; + } + + return $this->tokens->getPrevMeaningfulToken($index); + } + /** * Check if the T_STRING under given index is a constant invocation. */ public function isConstantInvocation(int $index): bool { if (!$this->tokens[$index]->isGivenKind(T_STRING)) { - throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); + throw new \LogicException(\sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); } $nextIndex = $this->tokens->getNextMeaningfulToken($index); @@ -318,11 +379,11 @@ public function isConstantInvocation(int $index): bool return false; } - while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) { + while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING, CT::T_ARRAY_TYPEHINT])) { $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); } - if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT])) { + if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT, CT::T_TYPE_INTERSECTION, CT::T_TYPE_ALTERNATION, T_CONST, CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE])) { return false; } @@ -493,13 +554,15 @@ public function isUnaryPredecessorOperator(int $index): bool ';', '{', '}', + [T_DOUBLE_ARROW], + [T_FN], [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO], ]; $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)]; - return $prevToken->isGivenKind(T_FUNCTION); + return $prevToken->isGivenKind([T_FN, T_FUNCTION]); } /** @@ -565,7 +628,7 @@ public function isBinaryOperator(int $index): bool $tokens = $this->tokens; $token = $tokens[$index]; - if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + if ($token->isGivenKind([T_INLINE_HTML, T_ENCAPSED_AND_WHITESPACE, CT::T_TYPE_INTERSECTION])) { return false; } @@ -594,7 +657,7 @@ public function isWhilePartOfDoWhile(int $index): bool $token = $tokens[$index]; if (!$token->isGivenKind(T_WHILE)) { - throw new \LogicException(sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName())); + throw new \LogicException(\sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName())); } $endIndex = $tokens->getPrevMeaningfulToken($index); @@ -608,6 +671,31 @@ public function isWhilePartOfDoWhile(int $index): bool return $tokens[$beforeStartIndex]->isGivenKind(T_DO); } + /** + * @throws \LogicException when provided index does not point to token containing T_CASE + */ + public function isEnumCase(int $caseIndex): bool + { + $tokens = $this->tokens; + $token = $tokens[$caseIndex]; + + if (!$token->isGivenKind(T_CASE)) { + throw new \LogicException(\sprintf( + 'No T_CASE given at index %d, got %s instead.', + $caseIndex, + $token->getName() ?? $token->getContent() + )); + } + + if (!\defined('T_ENUM') || !$tokens->isTokenKindFound(T_ENUM)) { + return false; + } + + $prevIndex = $tokens->getPrevTokenOfKind($caseIndex, [[T_ENUM], [T_SWITCH]]); + + return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind(T_ENUM); + } + public function isSuperGlobal(int $index): bool { static $superNames = [ @@ -638,6 +726,8 @@ public function isSuperGlobal(int $index): bool * Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array). * * @param int $classIndex classy index + * + * @return array{int, array} */ private function findClassyElements(int $classIndex, int $index): array { diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php index 69410f7b91..8e5b9bf10f 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ArrayTypehintTransformer.php @@ -28,17 +28,11 @@ */ final class ArrayTypehintTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50000; + return 5_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->isGivenKind(T_ARRAY)) { @@ -53,9 +47,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_ARRAY_TYPEHINT]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php index c099f8875a..d19d3b88e7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/AttributeTransformer.php @@ -26,26 +26,17 @@ */ final class AttributeTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // must run before all other transformers that might touch attributes return 200; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 80000; + return 8_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$tokens[$index]->isGivenKind(T_ATTRIBUTE)) { @@ -67,9 +58,6 @@ public function process(Tokens $tokens, Token $token, int $index): void $tokens[$index] = new Token([CT::T_ATTRIBUTE_CLOSE, ']']); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php index 2736c83c25..1a690b89aa 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceClassInstantiationTransformer.php @@ -29,26 +29,17 @@ */ final class BraceClassInstantiationTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // must run after CurlyBraceTransformer and SquareBraceTransformer return -2; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50000; + return 5_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$tokens[$index]->equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NEW)) { @@ -56,9 +47,11 @@ public function process(Tokens $tokens, Token $token, int $index): void } if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([ + ')', ']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], [CT::T_ARRAY_SQUARE_BRACE_CLOSE], + [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE], [T_ARRAY], [T_CLASS], [T_ELSEIF], @@ -80,9 +73,6 @@ public function process(Tokens $tokens, Token $token, int $index): void $tokens[$closeIndex] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')']); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, CT::T_BRACE_CLASS_INSTANTIATION_CLOSE]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceTransformer.php similarity index 75% rename from vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php rename to vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceTransformer.php index 14333dc0af..85bfb86b63 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/CurlyBraceTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/BraceTransformer.php @@ -34,19 +34,13 @@ * * @internal */ -final class CurlyBraceTransformer extends AbstractTransformer +final class BraceTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50000; + return 5_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { $this->transformIntoCurlyCloseBrace($tokens, $token, $index); @@ -55,11 +49,9 @@ public function process(Tokens $tokens, Token $token, int $index): void $this->transformIntoDynamicVarBraces($tokens, $token, $index); $this->transformIntoCurlyIndexBraces($tokens, $token, $index); $this->transformIntoGroupUseBraces($tokens, $token, $index); + $this->transformIntoDynamicClassConstantFetchBraces($tokens, $token, $index); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [ @@ -73,6 +65,8 @@ public function getCustomTokens(): array CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE, + CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, + CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, ]; } @@ -209,6 +203,45 @@ private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $ $tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']); } + private function transformIntoDynamicClassConstantFetchBraces(Tokens $tokens, Token $token, int $index): void + { + if (\PHP_VERSION_ID < 8_03_00) { + return; // @TODO: drop condition when PHP 8.3+ is required or majority of the users are using 8.3+ + } + + if (!$token->equals('{')) { + return; + } + + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index); + + while (!$tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_DOUBLE_COLON)) { + if (!$tokens[$prevMeaningfulTokenIndex]->equals(')')) { + return; + } + + $prevMeaningfulTokenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevMeaningfulTokenIndex); + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex); + + if (!$tokens[$prevMeaningfulTokenIndex]->equals('}')) { + return; + } + + $prevMeaningfulTokenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $prevMeaningfulTokenIndex); + $prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex); + } + + $closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index); + $nextMeaningfulTokenIndexAfterCloseIndex = $tokens->getNextMeaningfulToken($closeIndex); + + if (!$tokens[$nextMeaningfulTokenIndexAfterCloseIndex]->equalsAny([';', [T_CLOSE_TAG], [T_DOUBLE_COLON]])) { + return; + } + + $tokens[$index] = new Token([CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_OPEN, '{']); + $tokens[$closeIndex] = new Token([CT::T_DYNAMIC_CLASS_CONSTANT_FETCH_CURLY_BRACE_CLOSE, '}']); + } + /** * We do not want to rely on `$tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)` here, * as it relies on block types that are assuming that `}` tokens are already transformed to Custom Tokens that are allowing to distinguish different block types. @@ -217,11 +250,11 @@ private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $ private function naivelyFindCurlyBlockEnd(Tokens $tokens, int $startIndex): int { if (!$tokens->offsetExists($startIndex)) { - throw new \OutOfBoundsException(sprintf('Unavailable index: "%s".', $startIndex)); + throw new \OutOfBoundsException(\sprintf('Unavailable index: "%s".', $startIndex)); } if ('{' !== $tokens[$startIndex]->getContent()) { - throw new \InvalidArgumentException(sprintf('Wrong start index: "%s".', $startIndex)); + throw new \InvalidArgumentException(\sprintf('Wrong start index: "%s".', $startIndex)); } $blockLevel = 1; @@ -240,7 +273,7 @@ private function naivelyFindCurlyBlockEnd(Tokens $tokens, int $startIndex): int if (0 === $blockLevel) { if (!$token->equals('}')) { - throw new \UnexpectedValueException(sprintf('Detected block end for index: "%s" was already transformed into other token type: "%s".', $startIndex, $token->getName())); + throw new \UnexpectedValueException(\sprintf('Detected block end for index: "%s" was already transformed into other token type: "%s".', $startIndex, $token->getName())); } return $index; @@ -248,6 +281,6 @@ private function naivelyFindCurlyBlockEnd(Tokens $tokens, int $startIndex): int } } - throw new \UnexpectedValueException(sprintf('Missing block end for index: "%s".', $startIndex)); + throw new \UnexpectedValueException(\sprintf('Missing block end for index: "%s".', $startIndex)); } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php index 5a60092142..bb75b727a7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ClassConstantTransformer.php @@ -28,17 +28,11 @@ */ final class ClassConstantTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50500; + return 5_05_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->equalsAny([ @@ -56,9 +50,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_CLASS_CONSTANT]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php index aa3d495c3a..cdb282d35b 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ConstructorPromotionTransformer.php @@ -28,47 +28,38 @@ */ final class ConstructorPromotionTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 80000; + return 8_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { return; } - $index = $tokens->getNextMeaningfulToken($index); + $functionNameIndex = $tokens->getNextMeaningfulToken($index); - if (!$tokens[$index]->isGivenKind(T_STRING) || '__construct' !== strtolower($tokens[$index]->getContent())) { + if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING) || '__construct' !== strtolower($tokens[$functionNameIndex]->getContent())) { return; } - /** @var int $openIndex */ - $openIndex = $tokens->getNextMeaningfulToken($index); // we are @ '(' now - $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + /** @var int $openParenthesisIndex */ + $openParenthesisIndex = $tokens->getNextMeaningfulToken($functionNameIndex); // we are @ '(' now + $closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex); - for ($index = $openIndex; $index < $closeIndex; ++$index) { - if ($tokens[$index]->isGivenKind(T_PUBLIC)) { - $tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $tokens[$index]->getContent()]); - } elseif ($tokens[$index]->isGivenKind(T_PROTECTED)) { - $tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $tokens[$index]->getContent()]); - } elseif ($tokens[$index]->isGivenKind(T_PRIVATE)) { - $tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $tokens[$index]->getContent()]); + for ($argsIndex = $openParenthesisIndex; $argsIndex < $closeParenthesisIndex; ++$argsIndex) { + if ($tokens[$argsIndex]->isGivenKind(T_PUBLIC)) { + $tokens[$argsIndex] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $tokens[$argsIndex]->getContent()]); + } elseif ($tokens[$argsIndex]->isGivenKind(T_PROTECTED)) { + $tokens[$argsIndex] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $tokens[$argsIndex]->getContent()]); + } elseif ($tokens[$argsIndex]->isGivenKind(T_PRIVATE)) { + $tokens[$argsIndex] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $tokens[$argsIndex]->getContent()]); } } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.php new file mode 100644 index 0000000000..21d062b006 --- /dev/null +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.php @@ -0,0 +1,65 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tokenizer\Transformer; + +use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * Transform DNF parentheses into CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN and CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE. + * + * @see https://wiki.php.net/rfc/dnf_types + * + * @internal + */ +final class DisjunctiveNormalFormTypeParenthesisTransformer extends AbstractTransformer +{ + public function getPriority(): int + { + // needs to run after TypeAlternationTransformer + return -16; + } + + public function getRequiredPhpVersionId(): int + { + return 8_02_00; + } + + public function process(Tokens $tokens, Token $token, int $index): void + { + if ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(CT::T_TYPE_ALTERNATION)) { + $openIndex = $index; + $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + } elseif ($token->equals(')') && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(CT::T_TYPE_ALTERNATION)) { + $openIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index); + $closeIndex = $index; + } else { + return; + } + + $tokens[$openIndex] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '(']); + $tokens[$closeIndex] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')']); + } + + public function getCustomTokens(): array + { + return [ + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, + CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, + ]; + } +} diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php index f96a3e6111..4781e7b850 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php @@ -24,17 +24,11 @@ */ final class FirstClassCallableTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 80100; + return 8_01_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if ( @@ -46,9 +40,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php index 873956640c..1a5b0107a7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ImportTransformer.php @@ -32,26 +32,17 @@ */ final class ImportTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // Should run after CurlyBraceTransformer and ReturnRefTransformer return -1; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50600; + return 5_06_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->isGivenKind([T_CONST, T_FUNCTION])) { @@ -74,9 +65,6 @@ public function process(Tokens $tokens, Token $token, int $index): void ]); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php index d84037730e..ed8353c8d7 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NameQualifiedTransformer.php @@ -15,6 +15,7 @@ namespace PhpCsFixer\Tokenizer\Transformer; use PhpCsFixer\Tokenizer\AbstractTransformer; +use PhpCsFixer\Tokenizer\Processor\ImportProcessor; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -25,25 +26,16 @@ */ final class NameQualifiedTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { return 1; // must run before NamespaceOperatorTransformer } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 80000; + return 8_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if ($token->isGivenKind([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) { @@ -53,9 +45,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return []; @@ -63,38 +52,15 @@ public function getCustomTokens(): array private function transformQualified(Tokens $tokens, Token $token, int $index): void { - $parts = explode('\\', $token->getContent()); - $newTokens = []; - - if ('' === $parts[0]) { - $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); - array_shift($parts); - } - - foreach ($parts as $part) { - $newTokens[] = new Token([T_STRING, $part]); - $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); - } - - array_pop($newTokens); + $newTokens = ImportProcessor::tokenizeName($token->getContent()); $tokens->overrideRange($index, $index, $newTokens); } private function transformRelative(Tokens $tokens, Token $token, int $index): void { - $parts = explode('\\', $token->getContent()); - $newTokens = [ - new Token([T_NAMESPACE, array_shift($parts)]), - new Token([T_NS_SEPARATOR, '\\']), - ]; - - foreach ($parts as $part) { - $newTokens[] = new Token([T_STRING, $part]); - $newTokens[] = new Token([T_NS_SEPARATOR, '\\']); - } - - array_pop($newTokens); + $newTokens = ImportProcessor::tokenizeName($token->getContent()); + $newTokens[0] = new Token([T_NAMESPACE, 'namespace']); $tokens->overrideRange($index, $index, $newTokens); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php index e72993f50a..0f64717bac 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamedArgumentTransformer.php @@ -26,26 +26,17 @@ */ final class NamedArgumentTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // needs to run after TypeColonTransformer return -15; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 80000; + return 8_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$tokens[$index]->equals(':')) { @@ -72,9 +63,6 @@ public function process(Tokens $tokens, Token $token, int $index): void $tokens[$index] = new Token([CT::T_NAMED_ARGUMENT_COLON, ':']); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php index fc740e2a70..5244481acb 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NamespaceOperatorTransformer.php @@ -28,17 +28,11 @@ */ final class NamespaceOperatorTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50300; + return 5_03_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->isGivenKind(T_NAMESPACE)) { @@ -52,9 +46,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_NAMESPACE_OPERATOR]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php index c8f17ecf72..07f481f4e6 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/NullableTypeTransformer.php @@ -28,26 +28,17 @@ */ final class NullableTypeTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // needs to run after TypeColonTransformer return -20; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 70100; + return 7_01_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->equals('?')) { @@ -70,6 +61,7 @@ public function process(Tokens $tokens, Token $token, int $index): void [T_PUBLIC], [T_VAR], [T_STATIC], + [T_CONST], ]; if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required @@ -84,9 +76,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_NULLABLE_TYPE]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php index c2b2b4a81f..13f26d05a1 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/ReturnRefTransformer.php @@ -28,17 +28,11 @@ */ final class ReturnRefTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50000; + return 5_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if ($token->equals('&') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_FUNCTION, T_FN])) { @@ -46,9 +40,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_RETURN_REF]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php index c4ace5d9c2..46e5b12deb 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/SquareBraceTransformer.php @@ -32,29 +32,20 @@ */ final class SquareBraceTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // must run after CurlyBraceTransformer and AttributeTransformer return -1; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { // Short array syntax was introduced in PHP 5.4, but the fixer is smart // enough to handle it even before 5.4. // Same for array destructing syntax sugar `[` introduced in PHP 7.1. - return 50000; + return 5_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if ($this->isArrayDestructing($tokens, $index)) { @@ -68,9 +59,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [ diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php index 380ceb4c82..91035f90f4 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeAlternationTransformer.php @@ -29,34 +29,22 @@ */ final class TypeAlternationTransformer extends AbstractTypeTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { - // needs to run after ArrayTypehintTransformer and TypeColonTransformer + // needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer return -15; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 70100; + return 7_01_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { $this->doProcess($tokens, $index, '|'); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_TYPE_ALTERNATION]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php index 9969f9bcaa..7fed9de27e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeColonTransformer.php @@ -28,9 +28,6 @@ */ final class TypeColonTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // needs to run after ReturnRefTransformer and UseTransformer @@ -38,17 +35,11 @@ public function getPriority(): int return -10; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 70000; + return 7_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->equals(':')) { @@ -57,6 +48,15 @@ public function process(Tokens $tokens, Token $token, int $index): void $endIndex = $tokens->getPrevMeaningfulToken($index); + if ( + \defined('T_ENUM') // @TODO: drop condition when PHP 8.1+ is required + && $tokens[$tokens->getPrevMeaningfulToken($endIndex)]->isGivenKind(T_ENUM) + ) { + $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']); + + return; + } + if (!$tokens[$endIndex]->equals(')')) { return; } @@ -76,9 +76,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_TYPE_COLON]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php index ea967f9117..8018155ea2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/TypeIntersectionTransformer.php @@ -27,34 +27,22 @@ */ final class TypeIntersectionTransformer extends AbstractTypeTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { - // needs to run after ArrayTypehintTransformer and TypeColonTransformer + // needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer return -15; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 80100; + return 8_01_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { $this->doProcess($tokens, $index, [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, '&']); } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_TYPE_INTERSECTION]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php index f2e3ce0b0e..5aa0c027be 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/UseTransformer.php @@ -30,26 +30,17 @@ */ final class UseTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getPriority(): int { // Should run after CurlyBraceTransformer and before TypeColonTransformer return -5; } - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50300; + return 5_03_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if ($token->isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) { @@ -93,9 +84,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return [CT::T_USE_TRAIT, CT::T_USE_LAMBDA]; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php index bf81faac7b..91ab4cf2d8 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php @@ -27,17 +27,11 @@ */ final class WhitespacyCommentTransformer extends AbstractTransformer { - /** - * {@inheritdoc} - */ public function getRequiredPhpVersionId(): int { - return 50000; + return 5_00_00; } - /** - * {@inheritdoc} - */ public function process(Tokens $tokens, Token $token, int $index): void { if (!$token->isComment()) { @@ -63,9 +57,6 @@ public function process(Tokens $tokens, Token $token, int $index): void } } - /** - * {@inheritdoc} - */ public function getCustomTokens(): array { return []; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php index ba89a56af7..9d7f860037 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/TransformerInterface.php @@ -31,7 +31,7 @@ interface TransformerInterface /** * Get tokens created by Transformer. * - * @return int[] + * @return list */ public function getCustomTokens(): array; diff --git a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformers.php b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformers.php index b2587e25ab..e5650659b2 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformers.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformers.php @@ -29,7 +29,7 @@ final class Transformers /** * The registered transformers. * - * @var TransformerInterface[] + * @var list */ private array $items = []; @@ -40,9 +40,7 @@ private function __construct() { $this->registerBuiltInTransformers(); - usort($this->items, static function (TransformerInterface $a, TransformerInterface $b): int { - return $b->getPriority() <=> $a->getPriority(); - }); + usort($this->items, static fn (TransformerInterface $a, TransformerInterface $b): int => $b->getPriority() <=> $a->getPriority()); } public static function createSingleton(): self @@ -96,14 +94,14 @@ private function registerBuiltInTransformers(): void } /** - * @return \Generator|TransformerInterface[] + * @return \Generator */ private function findBuiltInTransformers(): iterable { /** @var SplFileInfo $file */ foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) { $relativeNamespace = $file->getRelativePath(); - $class = __NAMESPACE__.'\\Transformer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); + $class = __NAMESPACE__.'\Transformer\\'.('' !== $relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); yield new $class(); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php b/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php index f8e2aba388..767c680fcc 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ToolInfo.php @@ -30,7 +30,7 @@ final class ToolInfo implements ToolInfoInterface public const COMPOSER_LEGACY_PACKAGE_NAME = 'fabpot/php-cs-fixer'; /** - * @var null|array + * @var null|array{name: string, version: string, dist: array{reference?: string}} */ private $composerInstallationDetails; @@ -46,7 +46,7 @@ public function getComposerInstallationDetails(): array } if (null === $this->composerInstallationDetails) { - $composerInstalled = json_decode(file_get_contents($this->getComposerInstalledFile()), true); + $composerInstalled = json_decode(file_get_contents($this->getComposerInstalledFile()), true, 512, JSON_THROW_ON_ERROR); $packages = $composerInstalled['packages'] ?? $composerInstalled; @@ -98,10 +98,19 @@ public function isInstalledByComposer(): bool return $this->isInstalledByComposer; } + /** + * Determines if the tool is run inside our pre-built Docker image. + * The `/fixer/` path comes from our Dockerfile, tool is installed there and added to global PATH via symlinked binary. + */ + public function isRunInsideDocker(): bool + { + return is_file('/.dockerenv') && str_starts_with(__FILE__, '/fixer/'); + } + public function getPharDownloadUri(string $version): string { - return sprintf( - 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/%s/php-cs-fixer.phar', + return \sprintf( + 'https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/download/%s/php-cs-fixer.phar', $version ); } diff --git a/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php b/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php index 4e65411a7f..bc2a5e0b7e 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php +++ b/vendor/friendsofphp/php-cs-fixer/src/ToolInfoInterface.php @@ -19,6 +19,9 @@ */ interface ToolInfoInterface { + /** + * @return array{name: string, version: string, dist: array{reference?: string}} + */ public function getComposerInstallationDetails(): array; public function getComposerVersion(): string; @@ -29,5 +32,7 @@ public function isInstalledAsPhar(): bool; public function isInstalledByComposer(): bool; + public function isRunInsideDocker(): bool; + public function getPharDownloadUri(string $version): string; } diff --git a/vendor/friendsofphp/php-cs-fixer/src/Utils.php b/vendor/friendsofphp/php-cs-fixer/src/Utils.php index 5eacb3e13f..89f6c43fcd 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/Utils.php +++ b/vendor/friendsofphp/php-cs-fixer/src/Utils.php @@ -23,11 +23,13 @@ * @author Odín del Río * * @internal + * + * @deprecated This is a God Class anti-pattern. Don't expand it. It is fine to use logic that is already here (that's why we don't trigger deprecation warnings), but over time logic should be moved to dedicated, single-responsibility classes. */ final class Utils { /** - * @var array + * @var array */ private static array $deprecations = []; @@ -41,7 +43,7 @@ private function __construct() */ public static function camelCaseToUnderscore(string $string): string { - return mb_strtolower(Preg::replace('/(?isWhitespace()) { - throw new \InvalidArgumentException(sprintf('The given token must be whitespace, got "%s".', $token->getName())); + throw new \InvalidArgumentException(\sprintf('The given token must be whitespace, got "%s".', $token->getName())); } $str = strrchr( @@ -72,11 +74,14 @@ public static function calculateTrailingWhitespaceIndent(Token $token): string * * Stability is ensured by using Schwartzian transform. * - * @param mixed[] $elements - * @param callable $getComparedValue a callable that takes a single element and returns the value to compare - * @param callable $compareValues a callable that compares two values + * @template T + * @template R + * + * @param list $elements + * @param callable(T): R $getComparedValue a callable that takes a single element and returns the value to compare + * @param callable(R, R): int $compareValues a callable that compares two values * - * @return mixed[] + * @return list */ public static function stableSort(array $elements, callable $getComparedValue, callable $compareValues): array { @@ -94,17 +99,15 @@ public static function stableSort(array $elements, callable $getComparedValue, c return $a[1] <=> $b[1]; }); - return array_map(static function (array $item) { - return $item[0]; - }, $elements); + return array_map(static fn (array $item) => $item[0], $elements); } /** * Sort fixers by their priorities. * - * @param FixerInterface[] $fixers + * @param list $fixers * - * @return FixerInterface[] + * @return list */ public static function sortFixers(array $fixers): array { @@ -112,31 +115,29 @@ public static function sortFixers(array $fixers): array // `usort(): Array was modified by the user comparison function` warning for mocked objects. return self::stableSort( $fixers, - static function (FixerInterface $fixer): int { - return $fixer->getPriority(); - }, - static function (int $a, int $b): int { - return $b <=> $a; - } + static fn (FixerInterface $fixer): int => $fixer->getPriority(), + static fn (int $a, int $b): int => $b <=> $a ); } /** - * Join names in natural language wrapped in backticks, e.g. `a`, `b` and `c`. + * Join names in natural language using specified wrapper (double quote by default). * - * @param string[] $names + * @param list $names * * @throws \InvalidArgumentException */ - public static function naturalLanguageJoinWithBackticks(array $names): string + public static function naturalLanguageJoin(array $names, string $wrapper = '"'): string { if (0 === \count($names)) { throw new \InvalidArgumentException('Array of names cannot be empty.'); } - $names = array_map(static function (string $name): string { - return sprintf('`%s`', $name); - }, $names); + if (\strlen($wrapper) > 1) { + throw new \InvalidArgumentException('Wrapper should be a single-char string or empty.'); + } + + $names = array_map(static fn (string $name): string => \sprintf('%2$s%1$s%2$s', $name, $wrapper), $names); $last = array_pop($names); @@ -147,9 +148,29 @@ public static function naturalLanguageJoinWithBackticks(array $names): string return $last; } + /** + * Join names in natural language wrapped in backticks, e.g. `a`, `b` and `c`. + * + * @param list $names + * + * @throws \InvalidArgumentException + */ + public static function naturalLanguageJoinWithBackticks(array $names): string + { + return self::naturalLanguageJoin($names, '`'); + } + + public static function isFutureModeEnabled(): bool + { + return filter_var( + getenv('PHP_CS_FIXER_FUTURE_MODE'), + FILTER_VALIDATE_BOOL + ); + } + public static function triggerDeprecation(\Exception $futureException): void { - if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { + if (self::isFutureModeEnabled()) { throw new \RuntimeException( 'Your are using something deprecated, see previous exception. Aborting execution because `PHP_CS_FIXER_FUTURE_MODE` environment variable is set.', 0, @@ -163,6 +184,9 @@ public static function triggerDeprecation(\Exception $futureException): void @trigger_error($message, E_USER_DEPRECATED); } + /** + * @return list + */ public static function getTriggeredDeprecations(): array { $triggeredDeprecations = array_keys(self::$deprecations); @@ -170,4 +194,57 @@ public static function getTriggeredDeprecations(): array return $triggeredDeprecations; } + + public static function convertArrayTypeToList(string $type): string + { + $parts = explode('[]', $type); + $count = \count($parts) - 1; + + return str_repeat('list<', $count).$parts[0].str_repeat('>', $count); + } + + /** + * @param mixed $value + */ + public static function toString($value): string + { + return \is_array($value) + ? self::arrayToString($value) + : self::scalarToString($value); + } + + /** + * @param mixed $value + */ + private static function scalarToString($value): string + { + $str = var_export($value, true); + + return Preg::replace('/\bNULL\b/', 'null', $str); + } + + /** + * @param array $value + */ + private static function arrayToString(array $value): string + { + if (0 === \count($value)) { + return '[]'; + } + + $isHash = !array_is_list($value); + $str = '['; + + foreach ($value as $k => $v) { + if ($isHash) { + $str .= self::scalarToString($k).' => '; + } + + $str .= \is_array($v) + ? self::arrayToString($v).', ' + : self::scalarToString($v).', '; + } + + return substr($str, 0, -2).']'; + } } diff --git a/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php b/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php index 36265a885f..1525cf51e9 100644 --- a/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php +++ b/vendor/friendsofphp/php-cs-fixer/src/WordMatcher.php @@ -22,12 +22,12 @@ final class WordMatcher { /** - * @var string[] + * @var list */ private array $candidates; /** - * @param string[] $candidates + * @param list $candidates */ public function __construct(array $candidates) { diff --git a/vendor/php-cs-fixer/diff/README.md b/vendor/php-cs-fixer/diff/README.md deleted file mode 100644 index 19cddca773..0000000000 --- a/vendor/php-cs-fixer/diff/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# PHP-CS-Fixer/diff - -This version is for PHP CS Fixer only! Do not use it! - -Code from `sebastian/diff` has been forked a republished by permission of Sebastian Bergmann. -Main change is to make the package compatible with older PHP engines. -Licenced with BSD-3-Clause @ see LICENSE, copyright (c) Sebastian Bergmann -https://github.com/sebastianbergmann/diff - -For questions visit us @ https://gitter.im/PHP-CS-Fixer/Lobby diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md deleted file mode 100644 index 58ddab05aa..0000000000 --- a/vendor/psr/cache/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file, in reverse chronological order by release. - -## 1.0.1 - 2016-08-06 - -### Fixed - -- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr -- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr -- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell -- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell - -## 1.0.0 - 2015-12-11 - -Initial stable release; reflects accepted PSR-6 specification diff --git a/vendor/psr/cache/README.md b/vendor/psr/cache/README.md deleted file mode 100644 index c8706ceea6..0000000000 --- a/vendor/psr/cache/README.md +++ /dev/null @@ -1,9 +0,0 @@ -PSR Cache -========= - -This repository holds all interfaces defined by -[PSR-6](http://www.php-fig.org/psr/psr-6/). - -Note that this is not a Cache implementation of its own. It is merely an -interface that describes a Cache implementation. See the specification for more -details. diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json deleted file mode 100644 index e828fec943..0000000000 --- a/vendor/psr/cache/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "psr/cache", - "description": "Common interface for caching libraries", - "keywords": ["psr", "psr-6", "cache"], - "license": "MIT", - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "require": { - "php": ">=5.3.0" - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - } -} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php deleted file mode 100644 index e27f22f8d6..0000000000 --- a/vendor/psr/cache/src/CacheException.php +++ /dev/null @@ -1,10 +0,0 @@ - Contains no other changes, so it's actually fully compatible with the v0.6.0 release. + +## 0.6.0 (2019-07-04) + +* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()` + supporting multiple cache items (inspired by PSR-16). + (#32 by @krlv and #37 by @clue) + +* Documentation for TTL precision with millisecond accuracy or below and + use high-resolution timer for cache TTL on PHP 7.3+. + (#35 and #38 by @clue) + +* Improve API documentation and allow legacy HHVM to fail in Travis CI config. + (#34 and #36 by @clue) + +* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function. + (#31 by @WyriHaximus) + +## 0.5.0 (2018-06-25) + +* Improve documentation by describing what is expected of a class implementing `CacheInterface`. + (#21, #22, #23, #27 by @WyriHaximus) + +* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`. + (#26 by @clue) + +* Added support for cache expiration (TTL). + (#29 by @clue and @WyriHaximus) + +* Renamed `remove` to `delete` making it more in line with `PSR-16`. + (#30 by @clue) + +## 0.4.2 (2017-12-20) + +* Improve documentation with usage and installation instructions + (#10 by @clue) + +* Improve test suite by adding PHPUnit to `require-dev` and + add forward compatibility with PHPUnit 5 and PHPUnit 6 and + sanitize Composer autoload paths + (#14 by @shaunbramley and #12 and #18 by @clue) + +## 0.4.1 (2016-02-25) + +* Repository maintenance, split off from main repo, improve test suite and documentation +* First class support for PHP7 and HHVM (#9 by @clue) +* Adjust compatibility to 5.3 (#7 by @clue) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 + +## 0.3.2 (2013-05-10) + +* Version bump + +## 0.3.0 (2013-04-14) + +* Version bump + +## 0.2.6 (2012-12-26) + +* Feature: New cache component, used by DNS diff --git a/vendor/doctrine/annotations/LICENSE b/vendor/react/cache/LICENSE similarity index 55% rename from vendor/doctrine/annotations/LICENSE rename to vendor/react/cache/LICENSE index 5e781fce4b..d6f8901f9a 100644 --- a/vendor/doctrine/annotations/LICENSE +++ b/vendor/react/cache/LICENSE @@ -1,11 +1,13 @@ -Copyright (c) 2006-2013 Doctrine Project +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -15,5 +17,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/react/cache/README.md b/vendor/react/cache/README.md new file mode 100644 index 0000000000..7a86be9cf3 --- /dev/null +++ b/vendor/react/cache/README.md @@ -0,0 +1,367 @@ +# Cache + +[![CI status](https://github.com/reactphp/cache/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/cache/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/cache?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/cache) + +Async, [Promise](https://github.com/reactphp/promise)-based cache interface +for [ReactPHP](https://reactphp.org/). + +The cache component provides a +[Promise](https://github.com/reactphp/promise)-based +[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache) +implementation of that. +This allows consumers to type hint against the interface and third parties to +provide alternate implementations. +This project is heavily inspired by +[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/), +but uses an interface more suited for async, non-blocking applications. + +**Table of Contents** + +* [Usage](#usage) + * [CacheInterface](#cacheinterface) + * [get()](#get) + * [set()](#set) + * [delete()](#delete) + * [getMultiple()](#getmultiple) + * [setMultiple()](#setmultiple) + * [deleteMultiple()](#deletemultiple) + * [clear()](#clear) + * [has()](#has) + * [ArrayCache](#arraycache) +* [Common usage](#common-usage) + * [Fallback get](#fallback-get) + * [Fallback-get-and-set](#fallback-get-and-set) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Usage + +### CacheInterface + +The `CacheInterface` describes the main interface of this component. +This allows consumers to type hint against the interface and third parties to +provide alternate implementations. + +#### get() + +The `get(string $key, mixed $default = null): PromiseInterface` method can be used to +retrieve an item from the cache. + +This method will resolve with the cached value on success or with the +given `$default` value when no item can be found or when an error occurs. +Similarly, an expired cache item (once the time-to-live is expired) is +considered a cache miss. + +```php +$cache + ->get('foo') + ->then('var_dump'); +``` + +This example fetches the value of the key `foo` and passes it to the +`var_dump` function. You can use any of the composition provided by +[promises](https://github.com/reactphp/promise). + +#### set() + +The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface` method can be used to +store an item in the cache. + +This method will resolve with `true` on success or `false` when an error +occurs. If the cache implementation has to go over the network to store +it, it may take a while. + +The optional `$ttl` parameter sets the maximum time-to-live in seconds +for this cache item. If this parameter is omitted (or `null`), the item +will stay in the cache for as long as the underlying implementation +supports. Trying to access an expired cache item results in a cache miss, +see also [`get()`](#get). + +```php +$cache->set('foo', 'bar', 60); +``` + +This example eventually sets the value of the key `foo` to `bar`. If it +already exists, it is overridden. + +This interface does not enforce any particular TTL resolution, so special +care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Cache implementations SHOULD work on a +best effort basis and SHOULD provide at least second accuracy unless +otherwise noted. Many existing cache implementations are known to provide +microsecond or millisecond accuracy, but it's generally not recommended +to rely on this high precision. + +This interface suggests that cache implementations SHOULD use a monotonic +time source if available. Given that a monotonic time source is only +available as of PHP 7.3 by default, cache implementations MAY fall back +to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you store a cache item with a TTL of 30s and then +adjust your system time forward by 20s, the cache item SHOULD still +expire in 30s. + +#### delete() + +The `delete(string $key): PromiseInterface` method can be used to +delete an item from the cache. + +This method will resolve with `true` on success or `false` when an error +occurs. When no item for `$key` is found in the cache, it also resolves +to `true`. If the cache implementation has to go over the network to +delete it, it may take a while. + +```php +$cache->delete('foo'); +``` + +This example eventually deletes the key `foo` from the cache. As with +`set()`, this may not happen instantly and a promise is returned to +provide guarantees whether or not the item has been removed from cache. + +#### getMultiple() + +The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface` method can be used to +retrieve multiple cache items by their unique keys. + +This method will resolve with an array of cached values on success or with the +given `$default` value when an item can not be found or when an error occurs. +Similarly, an expired cache item (once the time-to-live is expired) is +considered a cache miss. + +```php +$cache->getMultiple(array('name', 'age'))->then(function (array $values) { + $name = $values['name'] ?? 'User'; + $age = $values['age'] ?? 'n/a'; + + echo $name . ' is ' . $age . PHP_EOL; +}); +``` + +This example fetches the cache items for the `name` and `age` keys and +prints some example output. You can use any of the composition provided +by [promises](https://github.com/reactphp/promise). + +#### setMultiple() + +The `setMultiple(array $values, ?float $ttl = null): PromiseInterface` method can be used to +persist a set of key => value pairs in the cache, with an optional TTL. + +This method will resolve with `true` on success or `false` when an error +occurs. If the cache implementation has to go over the network to store +it, it may take a while. + +The optional `$ttl` parameter sets the maximum time-to-live in seconds +for these cache items. If this parameter is omitted (or `null`), these items +will stay in the cache for as long as the underlying implementation +supports. Trying to access an expired cache items results in a cache miss, +see also [`getMultiple()`](#getmultiple). + +```php +$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); +``` + +This example eventually sets the list of values - the key `foo` to `1` value +and the key `bar` to `2`. If some of the keys already exist, they are overridden. + +#### deleteMultiple() + +The `setMultiple(string[] $keys): PromiseInterface` method can be used to +delete multiple cache items in a single operation. + +This method will resolve with `true` on success or `false` when an error +occurs. When no items for `$keys` are found in the cache, it also resolves +to `true`. If the cache implementation has to go over the network to +delete it, it may take a while. + +```php +$cache->deleteMultiple(array('foo', 'bar, 'baz')); +``` + +This example eventually deletes keys `foo`, `bar` and `baz` from the cache. +As with `setMultiple()`, this may not happen instantly and a promise is returned to +provide guarantees whether or not the item has been removed from cache. + +#### clear() + +The `clear(): PromiseInterface` method can be used to +wipe clean the entire cache. + +This method will resolve with `true` on success or `false` when an error +occurs. If the cache implementation has to go over the network to +delete it, it may take a while. + +```php +$cache->clear(); +``` + +This example eventually deletes all keys from the cache. As with `deleteMultiple()`, +this may not happen instantly and a promise is returned to provide guarantees +whether or not all the items have been removed from cache. + +#### has() + +The `has(string $key): PromiseInterface` method can be used to +determine whether an item is present in the cache. + +This method will resolve with `true` on success or `false` when no item can be found +or when an error occurs. Similarly, an expired cache item (once the time-to-live +is expired) is considered a cache miss. + +```php +$cache + ->has('foo') + ->then('var_dump'); +``` + +This example checks if the value of the key `foo` is set in the cache and passes +the result to the `var_dump` function. You can use any of the composition provided by +[promises](https://github.com/reactphp/promise). + +NOTE: It is recommended that has() is only to be used for cache warming type purposes +and not to be used within your live applications operations for get/set, as this method +is subject to a race condition where your has() will return true and immediately after, +another script can remove it making the state of your app out of date. + +### ArrayCache + +The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). + +```php +$cache = new ArrayCache(); + +$cache->set('foo', 'bar'); +``` + +Its constructor accepts an optional `?int $limit` parameter to limit the +maximum number of entries to store in the LRU cache. If you add more +entries to this instance, it will automatically take care of removing +the one that was least recently used (LRU). + +For example, this snippet will overwrite the first value and only store +the last two entries: + +```php +$cache = new ArrayCache(2); + +$cache->set('foo', '1'); +$cache->set('bar', '2'); +$cache->set('baz', '3'); +``` + +This cache implementation is known to rely on wall-clock time to schedule +future cache expiration times when using any version before PHP 7.3, +because a monotonic time source is only available as of PHP 7.3 (`hrtime()`). +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you store a cache item with a TTL of 30s on PHP < 7.3 +and then adjust your system time forward by 20s, the cache item may +expire in 10s. See also [`set()`](#set) for more details. + +## Common usage + +### Fallback get + +A common use case of caches is to attempt fetching a cached value and as a +fallback retrieve it from the original data source if not found. Here is an +example of that: + +```php +$cache + ->get('foo') + ->then(function ($result) { + if ($result === null) { + return getFooFromDb(); + } + + return $result; + }) + ->then('var_dump'); +``` + +First an attempt is made to retrieve the value of `foo`. A callback function is +registered that will call `getFooFromDb` when the resulting value is null. +`getFooFromDb` is a function (can be any PHP callable) that will be called if the +key does not exist in the cache. + +`getFooFromDb` can handle the missing key by returning a promise for the +actual value from the database (or any other data source). As a result, this +chain will correctly fall back, and provide the value in both cases. + +### Fallback get and set + +To expand on the fallback get example, often you want to set the value on the +cache after fetching it from the data source. + +```php +$cache + ->get('foo') + ->then(function ($result) { + if ($result === null) { + return $this->getAndCacheFooFromDb(); + } + + return $result; + }) + ->then('var_dump'); + +public function getAndCacheFooFromDb() +{ + return $this->db + ->get('foo') + ->then(array($this, 'cacheFooFromDb')); +} + +public function cacheFooFromDb($foo) +{ + $this->cache->set('foo', $foo); + + return $foo; +} +``` + +By using chaining you can easily conditionally cache the value if it is +fetched from the database. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require react/cache:^1.2 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and +HHVM. +It's *highly recommended to use PHP 7+* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). diff --git a/vendor/react/cache/composer.json b/vendor/react/cache/composer.json new file mode 100644 index 0000000000..153439a25a --- /dev/null +++ b/vendor/react/cache/composer.json @@ -0,0 +1,45 @@ +{ + "name": "react/cache", + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": ["cache", "caching", "promise", "ReactPHP"], + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Cache\\": "tests/" + } + } +} diff --git a/vendor/react/cache/src/ArrayCache.php b/vendor/react/cache/src/ArrayCache.php new file mode 100644 index 0000000000..81f25effa1 --- /dev/null +++ b/vendor/react/cache/src/ArrayCache.php @@ -0,0 +1,181 @@ +set('foo', 'bar'); + * ``` + * + * Its constructor accepts an optional `?int $limit` parameter to limit the + * maximum number of entries to store in the LRU cache. If you add more + * entries to this instance, it will automatically take care of removing + * the one that was least recently used (LRU). + * + * For example, this snippet will overwrite the first value and only store + * the last two entries: + * + * ```php + * $cache = new ArrayCache(2); + * + * $cache->set('foo', '1'); + * $cache->set('bar', '2'); + * $cache->set('baz', '3'); + * ``` + * + * This cache implementation is known to rely on wall-clock time to schedule + * future cache expiration times when using any version before PHP 7.3, + * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`). + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you store a cache item with a TTL of 30s on PHP < 7.3 + * and then adjust your system time forward by 20s, the cache item may + * expire in 10s. See also [`set()`](#set) for more details. + * + * @param int|null $limit maximum number of entries to store in the LRU cache + */ + public function __construct($limit = null) + { + $this->limit = $limit; + + // prefer high-resolution timer, available as of PHP 7.3+ + $this->supportsHighResolution = \function_exists('hrtime'); + } + + public function get($key, $default = null) + { + // delete key if it is already expired => below will detect this as a cache miss + if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) { + unset($this->data[$key], $this->expires[$key]); + } + + if (!\array_key_exists($key, $this->data)) { + return Promise\resolve($default); + } + + // remove and append to end of array to keep track of LRU info + $value = $this->data[$key]; + unset($this->data[$key]); + $this->data[$key] = $value; + + return Promise\resolve($value); + } + + public function set($key, $value, $ttl = null) + { + // unset before setting to ensure this entry will be added to end of array (LRU info) + unset($this->data[$key]); + $this->data[$key] = $value; + + // sort expiration times if TTL is given (first will expire first) + unset($this->expires[$key]); + if ($ttl !== null) { + $this->expires[$key] = $this->now() + $ttl; + \asort($this->expires); + } + + // ensure size limit is not exceeded or remove first entry from array + if ($this->limit !== null && \count($this->data) > $this->limit) { + // first try to check if there's any expired entry + // expiration times are sorted, so we can simply look at the first one + \reset($this->expires); + $key = \key($this->expires); + + // check to see if the first in the list of expiring keys is already expired + // if the first key is not expired, we have to overwrite by using LRU info + if ($key === null || $this->now() - $this->expires[$key] < 0) { + \reset($this->data); + $key = \key($this->data); + } + unset($this->data[$key], $this->expires[$key]); + } + + return Promise\resolve(true); + } + + public function delete($key) + { + unset($this->data[$key], $this->expires[$key]); + + return Promise\resolve(true); + } + + public function getMultiple(array $keys, $default = null) + { + $values = array(); + + foreach ($keys as $key) { + $values[$key] = $this->get($key, $default); + } + + return Promise\all($values); + } + + public function setMultiple(array $values, $ttl = null) + { + foreach ($values as $key => $value) { + $this->set($key, $value, $ttl); + } + + return Promise\resolve(true); + } + + public function deleteMultiple(array $keys) + { + foreach ($keys as $key) { + unset($this->data[$key], $this->expires[$key]); + } + + return Promise\resolve(true); + } + + public function clear() + { + $this->data = array(); + $this->expires = array(); + + return Promise\resolve(true); + } + + public function has($key) + { + // delete key if it is already expired + if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) { + unset($this->data[$key], $this->expires[$key]); + } + + if (!\array_key_exists($key, $this->data)) { + return Promise\resolve(false); + } + + // remove and append to end of array to keep track of LRU info + $value = $this->data[$key]; + unset($this->data[$key]); + $this->data[$key] = $value; + + return Promise\resolve(true); + } + + /** + * @return float + */ + private function now() + { + return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true); + } +} diff --git a/vendor/react/cache/src/CacheInterface.php b/vendor/react/cache/src/CacheInterface.php new file mode 100644 index 0000000000..8e51c190c3 --- /dev/null +++ b/vendor/react/cache/src/CacheInterface.php @@ -0,0 +1,194 @@ +get('foo') + * ->then('var_dump'); + * ``` + * + * This example fetches the value of the key `foo` and passes it to the + * `var_dump` function. You can use any of the composition provided by + * [promises](https://github.com/reactphp/promise). + * + * @param string $key + * @param mixed $default Default value to return for cache miss or null if not given. + * @return PromiseInterface + */ + public function get($key, $default = null); + + /** + * Stores an item in the cache. + * + * This method will resolve with `true` on success or `false` when an error + * occurs. If the cache implementation has to go over the network to store + * it, it may take a while. + * + * The optional `$ttl` parameter sets the maximum time-to-live in seconds + * for this cache item. If this parameter is omitted (or `null`), the item + * will stay in the cache for as long as the underlying implementation + * supports. Trying to access an expired cache item results in a cache miss, + * see also [`get()`](#get). + * + * ```php + * $cache->set('foo', 'bar', 60); + * ``` + * + * This example eventually sets the value of the key `foo` to `bar`. If it + * already exists, it is overridden. + * + * This interface does not enforce any particular TTL resolution, so special + * care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Cache implementations SHOULD work on a + * best effort basis and SHOULD provide at least second accuracy unless + * otherwise noted. Many existing cache implementations are known to provide + * microsecond or millisecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * This interface suggests that cache implementations SHOULD use a monotonic + * time source if available. Given that a monotonic time source is only + * available as of PHP 7.3 by default, cache implementations MAY fall back + * to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you store a cache item with a TTL of 30s and then + * adjust your system time forward by 20s, the cache item SHOULD still + * expire in 30s. + * + * @param string $key + * @param mixed $value + * @param ?float $ttl + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function set($key, $value, $ttl = null); + + /** + * Deletes an item from the cache. + * + * This method will resolve with `true` on success or `false` when an error + * occurs. When no item for `$key` is found in the cache, it also resolves + * to `true`. If the cache implementation has to go over the network to + * delete it, it may take a while. + * + * ```php + * $cache->delete('foo'); + * ``` + * + * This example eventually deletes the key `foo` from the cache. As with + * `set()`, this may not happen instantly and a promise is returned to + * provide guarantees whether or not the item has been removed from cache. + * + * @param string $key + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function delete($key); + + /** + * Retrieves multiple cache items by their unique keys. + * + * This method will resolve with an array of cached values on success or with the + * given `$default` value when an item can not be found or when an error occurs. + * Similarly, an expired cache item (once the time-to-live is expired) is + * considered a cache miss. + * + * ```php + * $cache->getMultiple(array('name', 'age'))->then(function (array $values) { + * $name = $values['name'] ?? 'User'; + * $age = $values['age'] ?? 'n/a'; + * + * echo $name . ' is ' . $age . PHP_EOL; + * }); + * ``` + * + * This example fetches the cache items for the `name` and `age` keys and + * prints some example output. You can use any of the composition provided + * by [promises](https://github.com/reactphp/promise). + * + * @param string[] $keys A list of keys that can obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * @return PromiseInterface Returns a promise which resolves to an `array` of cached values + */ + public function getMultiple(array $keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * This method will resolve with `true` on success or `false` when an error + * occurs. If the cache implementation has to go over the network to store + * it, it may take a while. + * + * The optional `$ttl` parameter sets the maximum time-to-live in seconds + * for these cache items. If this parameter is omitted (or `null`), these items + * will stay in the cache for as long as the underlying implementation + * supports. Trying to access an expired cache items results in a cache miss, + * see also [`get()`](#get). + * + * ```php + * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); + * ``` + * + * This example eventually sets the list of values - the key `foo` to 1 value + * and the key `bar` to 2. If some of the keys already exist, they are overridden. + * + * @param array $values A list of key => value pairs for a multiple-set operation. + * @param ?float $ttl Optional. The TTL value of this item. + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function setMultiple(array $values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param string[] $keys A list of string-based keys to be deleted. + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function deleteMultiple(array $keys); + + /** + * Wipes clean the entire cache. + * + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function clear(); + + /** + * Determines whether an item is present in the cache. + * + * This method will resolve with `true` on success or `false` when no item can be found + * or when an error occurs. Similarly, an expired cache item (once the time-to-live + * is expired) is considered a cache miss. + * + * ```php + * $cache + * ->has('foo') + * ->then('var_dump'); + * ``` + * + * This example checks if the value of the key `foo` is set in the cache and passes + * the result to the `var_dump` function. You can use any of the composition provided by + * [promises](https://github.com/reactphp/promise). + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function has($key); +} diff --git a/vendor/react/child-process/CHANGELOG.md b/vendor/react/child-process/CHANGELOG.md new file mode 100644 index 0000000000..81bf50c578 --- /dev/null +++ b/vendor/react/child-process/CHANGELOG.md @@ -0,0 +1,165 @@ +# Changelog + +## 0.6.5 (2022-09-16) + +* Feature: Full support for PHP 8.1 and PHP 8.2 release. + (#91 by @SimonFrings and #99 by @WyriHaximus) + +* Feature / Fix: Improve error reporting when custom error handler is used. + (#94 by @clue) + +* Minor documentation improvements. + (#92 by @SimonFrings and #95 by @nhedger) + +* Improve test suite, skip failing tests on bugged versions and fix legacy HHVM build. + (#96 and #98 by @clue and #93 by @SimonFrings) + +## 0.6.4 (2021-10-12) + +* Feature / Fix: Skip sigchild check if `phpinfo()` has been disabled. + (#89 by @clue) + +* Fix: Fix detecting closed socket pipes on PHP 8. + (#90 by @clue) + +## 0.6.3 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop). + (#87 by @clue) + + ```php + // old (still supported) + $process = new React\ChildProcess\Process($command); + $process->start($loop); + + // new (using default loop) + $process = new React\ChildProcess\Process($command); + $process->start(); + ``` + +## 0.6.2 (2021-02-05) + +* Feature: Support PHP 8 and add non-blocking I/O support on Windows with PHP 8. + (#85 by @clue) + +* Minor documentation improvements. + (#78 by @WyriHaximus and #80 by @gdejong) + +* Improve test suite and add `.gitattributes` to exclude dev files from exports. + Run tests on PHPUnit 9, switch to GitHub actions and clean up test suite. + (#75 by @reedy, #81 by @gdejong, #82 by @SimonFrings and #84 by @clue) + +## 0.6.1 (2019-02-15) + +* Feature / Fix: Improve error reporting when spawning child process fails. + (#73 by @clue) + +## 0.6.0 (2019-01-14) + +A major feature release with some minor API improvements! +This project now has limited Windows support and supports passing custom pipes +and file descriptors to the child process. + +This update involves a few minor BC breaks. We've tried hard to avoid BC breaks +where possible and minimize impact otherwise. We expect that most consumers of +this package will actually not be affected by any BC breaks, see below for more +details. + +* Feature / BC break: Support passing custom pipes and file descriptors to child process, + expose all standard I/O pipes in an array and remove unused Windows-only options. + (#62, #64 and #65 by @clue) + + > BC note: The optional `$options` parameter in the `Process` constructor + has been removed and a new `$fds` parameter has been added instead. The + previous `$options` parameter was Windows-only, available options were not + documented or referenced anywhere else in this library, so its actual + impact is expected to be relatively small. See the documentation and the + following changelog entry if you're looking for Windows support. + +* Feature: Support spawning child process on Windows without process I/O pipes. + (#67 by @clue) + +* Feature / BC break: Improve sigchild compatibility and support explicit configuration. + (#63 by @clue) + + ```php + // advanced: not recommended by default + Process::setSigchildEnabled(true); + ``` + + > BC note: The old public sigchild methods have been removed, but its + practical impact is believed to be relatively small due to the automatic detection. + +* Improve performance by prefixing all global functions calls with \ to skip + the look up and resolve process and go straight to the global function. + (#68 by @WyriHaximus) + +* Minor documentation improvements and docblock updates. + (#59 by @iamluc and #69 by @CharlotteDunois) + +* Improve test suite to test against PHP7.2 and PHP 7.3, improve HHVM compatibility, + add forward compatibility with PHPUnit 7 and run tests on Windows via Travis CI. + (#66 and #71 by @clue) + +## 0.5.2 (2018-01-18) + +* Feature: Detect "exit" immediately if last process pipe is closed + (#58 by @clue) + + This introduces a simple check to see if the program is already known to be + closed when the last process pipe is closed instead of relying on a periodic + timer. This simple change improves "exit" detection significantly for most + programs and does not cause a noticeable penalty for more advanced use cases. + +* Fix forward compatibility with upcoming EventLoop releases + (#56 by @clue) + +## 0.5.1 (2017-12-22) + +* Fix: Update Stream dependency to work around SEGFAULT in legacy PHP < 5.4.28 + and PHP < 5.5.12 + (#50 and #52 by @clue) + +* Improve test suite by simplifying test bootstrapping logic via Composer and + adding forward compatibility with PHPUnit 6 + (#53, #54 and #55 by @clue) + +## 0.5.0 (2017-08-15) + +* Forward compatibility: react/event-loop 1.0 and 0.5, react/stream 0.7.2 and 1.0, and Événement 3.0 + (#38 and #44 by @WyriHaximus, and #46 by @clue) +* Windows compatibility: Documentate that windows isn't supported in 0.5 unless used from within WSL + (#41 and #47 by @WyriHaximus) +* Documentation: Termination examples + (#42 by @clue) +* BC: Throw LogicException in Process instanciating when on Windows or when proc_open is missing (was `RuntimeException`) + (#49 by @mdrost) + +## 0.4.3 (2017-03-14) + +* Ease getting started by improving documentation and adding examples + (#33 and #34 by @clue) + +* First class support for PHP 5.3 through PHP 7.1 and HHVM + (#29 by @clue and #32 by @WyriHaximus) + +## 0.4.2 (2017-03-10) + +* Feature: Forward compatibility with Stream v0.5 + (#26 by @clue) + +* Improve test suite by removing AppVeyor and adding PHPUnit to `require-dev` + (#27 and #28 by @clue) + +## 0.4.1 (2016-08-01) + +* Standalone component +* Test against PHP 7 and HHVM, report test coverage, AppVeyor tests +* Wait for stdout and stderr to close before watching for process exit + (#18 by @mbonneau) + +## 0.4.0 (2014-02-02) + +* Feature: Added ChildProcess to run async child processes within the event loop (@jmikola) diff --git a/vendor/react/child-process/LICENSE b/vendor/react/child-process/LICENSE new file mode 100644 index 0000000000..d6f8901f9a --- /dev/null +++ b/vendor/react/child-process/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/react/child-process/README.md b/vendor/react/child-process/README.md new file mode 100644 index 0000000000..2dc8aab797 --- /dev/null +++ b/vendor/react/child-process/README.md @@ -0,0 +1,619 @@ +# ChildProcess + +[![CI status](https://github.com/reactphp/child-process/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/child-process/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/child-process?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/child-process) + +Event-driven library for executing child processes with +[ReactPHP](https://reactphp.org/). + +This library integrates [Program Execution](http://php.net/manual/en/book.exec.php) +with the [EventLoop](https://github.com/reactphp/event-loop). +Child processes launched may be signaled and will emit an +`exit` event upon termination. +Additionally, process I/O streams (i.e. STDIN, STDOUT, STDERR) are exposed +as [Streams](https://github.com/reactphp/stream). + +**Table of contents** + +* [Quickstart example](#quickstart-example) +* [Process](#process) + * [Stream Properties](#stream-properties) + * [Command](#command) + * [Termination](#termination) + * [Custom pipes](#custom-pipes) + * [Sigchild Compatibility](#sigchild-compatibility) + * [Windows Compatibility](#windows-compatibility) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Quickstart example + +```php +$process = new React\ChildProcess\Process('echo foo'); +$process->start(); + +$process->stdout->on('data', function ($chunk) { + echo $chunk; +}); + +$process->on('exit', function($exitCode, $termSignal) { + echo 'Process exited with code ' . $exitCode . PHP_EOL; +}); +``` + +See also the [examples](examples). + +## Process + +### Stream Properties + +Once a process is started, its I/O streams will be constructed as instances of +`React\Stream\ReadableStreamInterface` and `React\Stream\WritableStreamInterface`. +Before `start()` is called, these properties are not set. Once a process terminates, +the streams will become closed but not unset. + +Following common Unix conventions, this library will start each child process +with the three pipes matching the standard I/O streams as given below by default. +You can use the named references for common use cases or access these as an +array with all three pipes. + +* `$stdin` or `$pipes[0]` is a `WritableStreamInterface` +* `$stdout` or `$pipes[1]` is a `ReadableStreamInterface` +* `$stderr` or `$pipes[2]` is a `ReadableStreamInterface` + +Note that this default configuration may be overridden by explicitly passing +[custom pipes](#custom-pipes), in which case they may not be set or be assigned +different values. In particular, note that [Windows support](#windows-compatibility) +is limited in that it doesn't support non-blocking STDIO pipes. The `$pipes` +array will always contain references to all pipes as configured and the standard +I/O references will always be set to reference the pipes matching the above +conventions. See [custom pipes](#custom-pipes) for more details. + +Because each of these implement the underlying +[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) or +[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface), +you can use any of their events and methods as usual: + +```php +$process = new Process($command); +$process->start(); + +$process->stdout->on('data', function ($chunk) { + echo $chunk; +}); + +$process->stdout->on('end', function () { + echo 'ended'; +}); + +$process->stdout->on('error', function (Exception $e) { + echo 'error: ' . $e->getMessage(); +}); + +$process->stdout->on('close', function () { + echo 'closed'; +}); + +$process->stdin->write($data); +$process->stdin->end($data = null); +// … +``` + +For more details, see the +[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) and +[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface). + +### Command + +The `Process` class allows you to pass any kind of command line string: + +```php +$process = new Process('echo test'); +$process->start(); +``` + +The command line string usually consists of a whitespace-separated list with +your main executable bin and any number of arguments. Special care should be +taken to escape or quote any arguments, escpecially if you pass any user input +along. Likewise, keep in mind that especially on Windows, it is rather common to +have path names containing spaces and other special characters. If you want to +run a binary like this, you will have to ensure this is quoted as a single +argument using `escapeshellarg()` like this: + +```php +$bin = 'C:\\Program files (x86)\\PHP\\php.exe'; +$file = 'C:\\Users\\me\\Desktop\\Application\\main.php'; + +$process = new Process(escapeshellarg($bin) . ' ' . escapeshellarg($file)); +$process->start(); +``` + +By default, PHP will launch processes by wrapping the given command line string +in a `sh` command on Unix, so that the first example will actually execute +`sh -c echo test` under the hood on Unix. On Windows, it will not launch +processes by wrapping them in a shell. + +This is a very useful feature because it does not only allow you to pass single +commands, but actually allows you to pass any kind of shell command line and +launch multiple sub-commands using command chains (with `&&`, `||`, `;` and +others) and allows you to redirect STDIO streams (with `2>&1` and family). +This can be used to pass complete command lines and receive the resulting STDIO +streams from the wrapping shell command like this: + +```php +$process = new Process('echo run && demo || echo failed'); +$process->start(); +``` + +> Note that [Windows support](#windows-compatibility) is limited in that it + doesn't support STDIO streams at all and also that processes will not be run + in a wrapping shell by default. If you want to run a shell built-in function + such as `echo hello` or `sleep 10`, you may have to prefix your command line + with an explicit shell like `cmd /c echo hello`. + +In other words, the underlying shell is responsible for managing this command +line and launching the individual sub-commands and connecting their STDIO +streams as appropriate. +This implies that the `Process` class will only receive the resulting STDIO +streams from the wrapping shell, which will thus contain the complete +input/output with no way to discern the input/output of single sub-commands. + +If you want to discern the output of single sub-commands, you may want to +implement some higher-level protocol logic, such as printing an explicit +boundary between each sub-command like this: + +```php +$process = new Process('cat first && echo --- && cat second'); +$process->start(); +``` + +As an alternative, considering launching one process at a time and listening on +its `exit` event to conditionally start the next process in the chain. +This will give you an opportunity to configure the subsequent process I/O streams: + +```php +$first = new Process('cat first'); +$first->start(); + +$first->on('exit', function () { + $second = new Process('cat second'); + $second->start(); +}); +``` + +Keep in mind that PHP uses the shell wrapper for ALL command lines on Unix. +While this may seem reasonable for more complex command lines, this actually +also applies to running the most simple single command: + +```php +$process = new Process('yes'); +$process->start(); +``` + +This will actually spawn a command hierarchy similar to this on Unix: + +``` +5480 … \_ php example.php +5481 … \_ sh -c yes +5482 … \_ yes +``` + +This means that trying to get the underlying process PID or sending signals +will actually target the wrapping shell, which may not be the desired result +in many cases. + +If you do not want this wrapping shell process to show up, you can simply +prepend the command string with `exec` on Unix platforms, which will cause the +wrapping shell process to be replaced by our process: + +```php +$process = new Process('exec yes'); +$process->start(); +``` + +This will show a resulting command hierarchy similar to this: + +``` +5480 … \_ php example.php +5481 … \_ yes +``` + +This means that trying to get the underlying process PID and sending signals +will now target the actual command as expected. + +Note that in this case, the command line will not be run in a wrapping shell. +This implies that when using `exec`, there's no way to pass command lines such +as those containing command chains or redirected STDIO streams. + +As a rule of thumb, most commands will likely run just fine with the wrapping +shell. +If you pass a complete command line (or are unsure), you SHOULD most likely keep +the wrapping shell. +If you're running on Unix and you want to pass an invidual command only, you MAY +want to consider prepending the command string with `exec` to avoid the wrapping shell. + +### Termination + +The `exit` event will be emitted whenever the process is no longer running. +Event listeners will receive the exit code and termination signal as two +arguments: + +```php +$process = new Process('sleep 10'); +$process->start(); + +$process->on('exit', function ($code, $term) { + if ($term === null) { + echo 'exit with code ' . $code . PHP_EOL; + } else { + echo 'terminated with signal ' . $term . PHP_EOL; + } +}); +``` + +Note that `$code` is `null` if the process has terminated, but the exit +code could not be determined (for example +[sigchild compatibility](#sigchild-compatibility) was disabled). +Similarly, `$term` is `null` unless the process has terminated in response to +an uncaught signal sent to it. +This is not a limitation of this project, but actual how exit codes and signals +are exposed on POSIX systems, for more details see also +[here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated). + +It's also worth noting that process termination depends on all file descriptors +being closed beforehand. +This means that all [process pipes](#stream-properties) will emit a `close` +event before the `exit` event and that no more `data` events will arrive after +the `exit` event. +Accordingly, if either of these pipes is in a paused state (`pause()` method +or internally due to a `pipe()` call), this detection may not trigger. + +The `terminate(?int $signal = null): bool` method can be used to send the +process a signal (SIGTERM by default). +Depending on which signal you send to the process and whether it has a signal +handler registered, this can be used to either merely signal a process or even +forcefully terminate it. + +```php +$process->terminate(SIGUSR1); +``` + +Keep the above section in mind if you want to forcefully terminate a process. +If your process spawn sub-processes or implicitly uses the +[wrapping shell mentioned above](#command), its file descriptors may be +inherited to child processes and terminating the main process may not +necessarily terminate the whole process tree. +It is highly suggested that you explicitly `close()` all process pipes +accordingly when terminating a process: + +```php +$process = new Process('sleep 10'); +$process->start(); + +Loop::addTimer(2.0, function () use ($process) { + foreach ($process->pipes as $pipe) { + $pipe->close(); + } + $process->terminate(); +}); +``` + +For many simple programs these seamingly complicated steps can also be avoided +by prefixing the command line with `exec` to avoid the wrapping shell and its +inherited process pipes as [mentioned above](#command). + +```php +$process = new Process('exec sleep 10'); +$process->start(); + +Loop::addTimer(2.0, function () use ($process) { + $process->terminate(); +}); +``` + +Many command line programs also wait for data on `STDIN` and terminate cleanly +when this pipe is closed. +For example, the following can be used to "soft-close" a `cat` process: + +```php +$process = new Process('cat'); +$process->start(); + +Loop::addTimer(2.0, function () use ($process) { + $process->stdin->end(); +}); +``` + +While process pipes and termination may seem confusing to newcomers, the above +properties actually allow some fine grained control over process termination, +such as first trying a soft-close and then applying a force-close after a +timeout. + +### Custom pipes + +Following common Unix conventions, this library will start each child process +with the three pipes matching the standard I/O streams by default. For more +advanced use cases it may be useful to pass in custom pipes, such as explicitly +passing additional file descriptors (FDs) or overriding default process pipes. + +Note that passing custom pipes is considered advanced usage and requires a +more in-depth understanding of Unix file descriptors and how they are inherited +to child processes and shared in multi-processing applications. + +If you do not want to use the default standard I/O pipes, you can explicitly +pass an array containing the file descriptor specification to the constructor +like this: + +```php +$fds = array( + // standard I/O pipes for stdin/stdout/stderr + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + + // example FDs for files or open resources + 4 => array('file', '/dev/null', 'r'), + 6 => fopen('log.txt','a'), + 8 => STDERR, + + // example FDs for sockets + 10 => fsockopen('localhost', 8080), + 12 => stream_socket_server('tcp://0.0.0.0:4711') +); + +$process = new Process($cmd, null, null, $fds); +$process->start(); +``` + +Unless your use case has special requirements that demand otherwise, you're +highly recommended to (at least) pass in the standard I/O pipes as given above. +The file descriptor specification accepts arguments in the exact same format +as the underlying [`proc_open()`](http://php.net/proc_open) function. + +Once the process is started, the `$pipes` array will always contain references to +all pipes as configured and the standard I/O references will always be set to +reference the pipes matching common Unix conventions. This library supports any +number of pipes and additional file descriptors, but many common applications +being run as a child process will expect that the parent process properly +assigns these file descriptors. + +### Sigchild Compatibility + +Internally, this project uses a work-around to improve compatibility when PHP +has been compiled with the `--enable-sigchild` option. This should not affect most +installations as this configure option is not used by default and many +distributions (such as Debian and Ubuntu) are known to not use this by default. +Some installations that use [Oracle OCI8](http://php.net/manual/en/book.oci8.php) +may use this configure option to circumvent `defunct` processes. + +When PHP has been compiled with the `--enable-sigchild` option, a child process' +exit code cannot be reliably determined via `proc_close()` or `proc_get_status()`. +To work around this, we execute the child process with an additional pipe and +use that to retrieve its exit code. + +This work-around incurs some overhead, so we only trigger this when necessary +and when we detect that PHP has been compiled with the `--enable-sigchild` option. +Because PHP does not provide a way to reliably detect this option, we try to +inspect output of PHP's configure options from the `phpinfo()` function. + +The static `setSigchildEnabled(bool $sigchild): void` method can be used to +explicitly enable or disable this behavior like this: + +```php +// advanced: not recommended by default +Process::setSigchildEnabled(true); +``` + +Note that all processes instantiated after this method call will be affected. +If this work-around is disabled on an affected PHP installation, the `exit` +event may receive `null` instead of the actual exit code as described above. +Similarly, some distributions are known to omit the configure options from +`phpinfo()`, so automatic detection may fail to enable this work-around in some +cases. You may then enable this explicitly as given above. + +**Note:** The original functionality was taken from Symfony's +[Process](https://github.com/symfony/process) compoment. + +### Windows Compatibility + +Due to platform constraints, this library provides only limited support for +spawning child processes on Windows. In particular, PHP does not allow accessing +standard I/O pipes on Windows without blocking. As such, this project will not +allow constructing a child process with the default process pipes and will +instead throw a `LogicException` on Windows by default: + +```php +// throws LogicException on Windows +$process = new Process('ping example.com'); +$process->start(); +``` + +There are a number of alternatives and workarounds as detailed below if you want +to run a child process on Windows, each with its own set of pros and cons: + +* As of PHP 8, you can start the child process with `socket` pair descriptors + in place of normal standard I/O pipes like this: + + ```php + $process = new Process( + 'ping example.com', + null, + null, + [ + ['socket'], + ['socket'], + ['socket'] + ] + ); + $process->start(); + + $process->stdout->on('data', function ($chunk) { + echo $chunk; + }); + ``` + + These `socket` pairs support non-blocking process I/O on any platform, + including Windows. However, not all programs accept stdio sockets. + +* This package does work on + [`Windows Subsystem for Linux`](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux) + (or WSL) without issues. When you are in control over how your application is + deployed, we recommend [installing WSL](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide) + when you want to run this package on Windows. + +* If you only care about the exit code of a child process to check if its + execution was successful, you can use [custom pipes](#custom-pipes) to omit + any standard I/O pipes like this: + + ```php + $process = new Process('ping example.com', null, null, array()); + $process->start(); + + $process->on('exit', function ($exitcode) { + echo 'exit with ' . $exitcode . PHP_EOL; + }); + ``` + + Similarly, this is also useful if your child process communicates over + sockets with remote servers or even your parent process using the + [Socket component](https://github.com/reactphp/socket). This is usually + considered the best alternative if you have control over how your child + process communicates with the parent process. + +* If you only care about command output after the child process has been + executed, you can use [custom pipes](#custom-pipes) to configure file + handles to be passed to the child process instead of pipes like this: + + ```php + $process = new Process('ping example.com', null, null, array( + array('file', 'nul', 'r'), + $stdout = tmpfile(), + array('file', 'nul', 'w') + )); + $process->start(); + + $process->on('exit', function ($exitcode) use ($stdout) { + echo 'exit with ' . $exitcode . PHP_EOL; + + // rewind to start and then read full file (demo only, this is blocking). + // reading from shared file is only safe if you have some synchronization in place + // or after the child process has terminated. + rewind($stdout); + echo stream_get_contents($stdout); + fclose($stdout); + }); + ``` + + Note that this example uses `tmpfile()`/`fopen()` for illustration purposes only. + This should not be used in a truly async program because the filesystem is + inherently blocking and each call could potentially take several seconds. + See also the [Filesystem component](https://github.com/reactphp/filesystem) as an + alternative. + +* If you want to access command output as it happens in a streaming fashion, + you can use redirection to spawn an additional process to forward your + standard I/O streams to a socket and use [custom pipes](#custom-pipes) to + omit any actual standard I/O pipes like this: + + ```php + $server = new React\Socket\Server('127.0.0.1:0'); + $server->on('connection', function (React\Socket\ConnectionInterface $connection) { + $connection->on('data', function ($chunk) { + echo $chunk; + }); + }); + + $command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress()); + $process = new Process($command, null, null, array()); + $process->start(); + + $process->on('exit', function ($exitcode) use ($server) { + $server->close(); + echo 'exit with ' . $exitcode . PHP_EOL; + }); + ``` + + Note how this will spawn another fictional `foobar` helper program to consume + the standard output from the actual child process. This is in fact similar + to the above recommendation of using socket connections in the child process, + but in this case does not require modification of the actual child process. + + In this example, the fictional `foobar` helper program can be implemented by + simply consuming all data from standard input and forwarding it to a socket + connection like this: + + ```php + $socket = stream_socket_client($argv[1]); + do { + fwrite($socket, $data = fread(STDIN, 8192)); + } while (isset($data[0])); + ``` + + Accordingly, this example can also be run with plain PHP without having to + rely on any external helper program like this: + + ```php + $code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));'; + $command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress()); + $process = new Process($command, null, null, array()); + $process->start(); + ``` + + See also [example #23](examples/23-forward-socket.php). + + Note that this is for illustration purposes only and you may want to implement + some proper error checks and/or socket verification in actual production use + if you do not want to risk other processes connecting to the server socket. + In this case, we suggest looking at the excellent + [createprocess-windows](https://github.com/cubiclesoft/createprocess-windows). + +Additionally, note that the [command](#command) given to the `Process` will be +passed to the underlying Windows-API +([`CreateProcess`](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa)) +as-is and the process will not be launched in a wrapping shell by default. In +particular, this means that shell built-in functions such as `echo hello` or +`sleep 10` may have to be prefixed with an explicit shell command like this: + +```php +$process = new Process('cmd /c echo hello', null, null, $pipes); +$process->start(); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +composer require react/child-process:^0.6.5 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. +It's *highly recommended to use the latest supported PHP version* for this project. + +See above note for limited [Windows Compatibility](#windows-compatibility). + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). diff --git a/vendor/react/child-process/composer.json b/vendor/react/child-process/composer.json new file mode 100644 index 0000000000..32aa7139cd --- /dev/null +++ b/vendor/react/child-process/composer.json @@ -0,0 +1,45 @@ +{ + "name": "react/child-process", + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": ["process", "event-driven", "ReactPHP"], + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "react/event-loop": "^1.2", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "autoload": { + "psr-4": { "React\\ChildProcess\\": "src" } + }, + "autoload-dev": { + "psr-4": { "React\\Tests\\ChildProcess\\": "tests" } + } +} diff --git a/vendor/react/child-process/src/Process.php b/vendor/react/child-process/src/Process.php new file mode 100644 index 0000000000..d06b303126 --- /dev/null +++ b/vendor/react/child-process/src/Process.php @@ -0,0 +1,576 @@ +start(); + * + * $process->on('exit', function ($code, $term) { + * if ($term === null) { + * echo 'exit with code ' . $code . PHP_EOL; + * } else { + * echo 'terminated with signal ' . $term . PHP_EOL; + * } + * }); + * ``` + * + * Note that `$code` is `null` if the process has terminated, but the exit + * code could not be determined (for example + * [sigchild compatibility](#sigchild-compatibility) was disabled). + * Similarly, `$term` is `null` unless the process has terminated in response to + * an uncaught signal sent to it. + * This is not a limitation of this project, but actual how exit codes and signals + * are exposed on POSIX systems, for more details see also + * [here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated). + * + * It's also worth noting that process termination depends on all file descriptors + * being closed beforehand. + * This means that all [process pipes](#stream-properties) will emit a `close` + * event before the `exit` event and that no more `data` events will arrive after + * the `exit` event. + * Accordingly, if either of these pipes is in a paused state (`pause()` method + * or internally due to a `pipe()` call), this detection may not trigger. + */ +class Process extends EventEmitter +{ + /** + * @var WritableStreamInterface|null|DuplexStreamInterface|ReadableStreamInterface + */ + public $stdin; + + /** + * @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface + */ + public $stdout; + + /** + * @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface + */ + public $stderr; + + /** + * Array with all process pipes (once started) + * + * Unless explicitly configured otherwise during construction, the following + * standard I/O pipes will be assigned by default: + * - 0: STDIN (`WritableStreamInterface`) + * - 1: STDOUT (`ReadableStreamInterface`) + * - 2: STDERR (`ReadableStreamInterface`) + * + * @var array + */ + public $pipes = array(); + + private $cmd; + private $cwd; + private $env; + private $fds; + + private $enhanceSigchildCompatibility; + private $sigchildPipe; + + private $process; + private $status; + private $exitCode; + private $fallbackExitCode; + private $stopSignal; + private $termSignal; + + private static $sigchild; + + /** + * Constructor. + * + * @param string $cmd Command line to run + * @param null|string $cwd Current working directory or null to inherit + * @param null|array $env Environment variables or null to inherit + * @param null|array $fds File descriptors to allocate for this process (or null = default STDIO streams) + * @throws \LogicException On windows or when proc_open() is not installed + */ + public function __construct($cmd, $cwd = null, array $env = null, array $fds = null) + { + if (!\function_exists('proc_open')) { + throw new \LogicException('The Process class relies on proc_open(), which is not available on your PHP installation.'); + } + + $this->cmd = $cmd; + $this->cwd = $cwd; + + if (null !== $env) { + $this->env = array(); + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + } + + if ($fds === null) { + $fds = array( + array('pipe', 'r'), // stdin + array('pipe', 'w'), // stdout + array('pipe', 'w'), // stderr + ); + } + + if (\DIRECTORY_SEPARATOR === '\\') { + foreach ($fds as $fd) { + if (isset($fd[0]) && $fd[0] === 'pipe') { + throw new \LogicException('Process pipes are not supported on Windows due to their blocking nature on Windows'); + } + } + } + + $this->fds = $fds; + $this->enhanceSigchildCompatibility = self::isSigchildEnabled(); + } + + /** + * Start the process. + * + * After the process is started, the standard I/O streams will be constructed + * and available via public properties. + * + * This method takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this process. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * + * @param ?LoopInterface $loop Loop interface for stream construction + * @param float $interval Interval to periodically monitor process state (seconds) + * @throws \RuntimeException If the process is already running or fails to start + */ + public function start(LoopInterface $loop = null, $interval = 0.1) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $loop = $loop ?: Loop::get(); + $cmd = $this->cmd; + $fdSpec = $this->fds; + $sigchild = null; + + // Read exit code through fourth pipe to work around --enable-sigchild + if ($this->enhanceSigchildCompatibility) { + $fdSpec[] = array('pipe', 'w'); + \end($fdSpec); + $sigchild = \key($fdSpec); + + // make sure this is fourth or higher (do not mess with STDIO) + if ($sigchild < 3) { + $fdSpec[3] = $fdSpec[$sigchild]; + unset($fdSpec[$sigchild]); + $sigchild = 3; + } + + $cmd = \sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd); + } + + // on Windows, we do not launch the given command line in a shell (cmd.exe) by default and omit any error dialogs + // the cmd.exe shell can explicitly be given as part of the command as detailed in both documentation and tests + $options = array(); + if (\DIRECTORY_SEPARATOR === '\\') { + $options['bypass_shell'] = true; + $options['suppress_errors'] = true; + } + + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // proc_open(/dev/does-not-exist): Failed to open stream: No such file or directory + $errstr = $error; + }); + + $pipes = array(); + $this->process = @\proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env, $options); + + \restore_error_handler(); + + if (!\is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process: ' . $errstr); + } + + // count open process pipes and await close event for each to drain buffers before detecting exit + $that = $this; + $closeCount = 0; + $streamCloseHandler = function () use (&$closeCount, $loop, $interval, $that) { + $closeCount--; + + if ($closeCount > 0) { + return; + } + + // process already closed => report immediately + if (!$that->isRunning()) { + $that->close(); + $that->emit('exit', array($that->getExitCode(), $that->getTermSignal())); + return; + } + + // close not detected immediately => check regularly + $loop->addPeriodicTimer($interval, function ($timer) use ($that, $loop) { + if (!$that->isRunning()) { + $loop->cancelTimer($timer); + $that->close(); + $that->emit('exit', array($that->getExitCode(), $that->getTermSignal())); + } + }); + }; + + if ($sigchild !== null) { + $this->sigchildPipe = $pipes[$sigchild]; + unset($pipes[$sigchild]); + } + + foreach ($pipes as $n => $fd) { + // use open mode from stream meta data or fall back to pipe open mode for legacy HHVM + $meta = \stream_get_meta_data($fd); + $mode = $meta['mode'] === '' ? ($this->fds[$n][1] === 'r' ? 'w' : 'r') : $meta['mode']; + + if ($mode === 'r+') { + $stream = new DuplexResourceStream($fd, $loop); + $stream->on('close', $streamCloseHandler); + $closeCount++; + } elseif ($mode === 'w') { + $stream = new WritableResourceStream($fd, $loop); + } else { + $stream = new ReadableResourceStream($fd, $loop); + $stream->on('close', $streamCloseHandler); + $closeCount++; + } + $this->pipes[$n] = $stream; + } + + $this->stdin = isset($this->pipes[0]) ? $this->pipes[0] : null; + $this->stdout = isset($this->pipes[1]) ? $this->pipes[1] : null; + $this->stderr = isset($this->pipes[2]) ? $this->pipes[2] : null; + + // immediately start checking for process exit when started without any I/O pipes + if (!$closeCount) { + $streamCloseHandler(); + } + } + + /** + * Close the process. + * + * This method should only be invoked via the periodic timer that monitors + * the process state. + */ + public function close() + { + if ($this->process === null) { + return; + } + + foreach ($this->pipes as $pipe) { + $pipe->close(); + } + + if ($this->enhanceSigchildCompatibility) { + $this->pollExitCodePipe(); + $this->closeExitCodePipe(); + } + + $exitCode = \proc_close($this->process); + $this->process = null; + + if ($this->exitCode === null && $exitCode !== -1) { + $this->exitCode = $exitCode; + } + + if ($this->exitCode === null && $this->status['exitcode'] !== -1) { + $this->exitCode = $this->status['exitcode']; + } + + if ($this->exitCode === null && $this->fallbackExitCode !== null) { + $this->exitCode = $this->fallbackExitCode; + $this->fallbackExitCode = null; + } + } + + /** + * Terminate the process with an optional signal. + * + * @param int $signal Optional signal (default: SIGTERM) + * @return bool Whether the signal was sent successfully + */ + public function terminate($signal = null) + { + if ($this->process === null) { + return false; + } + + if ($signal !== null) { + return \proc_terminate($this->process, $signal); + } + + return \proc_terminate($this->process); + } + + /** + * Get the command string used to launch the process. + * + * @return string + */ + public function getCommand() + { + return $this->cmd; + } + + /** + * Get the exit code returned by the process. + * + * This value is only meaningful if isRunning() has returned false. Null + * will be returned if the process is still running. + * + * Null may also be returned if the process has terminated, but the exit + * code could not be determined (e.g. sigchild compatibility was disabled). + * + * @return int|null + */ + public function getExitCode() + { + return $this->exitCode; + } + + /** + * Get the process ID. + * + * @return int|null + */ + public function getPid() + { + $status = $this->getCachedStatus(); + + return $status !== null ? $status['pid'] : null; + } + + /** + * Get the signal that caused the process to stop its execution. + * + * This value is only meaningful if isStopped() has returned true. Null will + * be returned if the process was never stopped. + * + * @return int|null + */ + public function getStopSignal() + { + return $this->stopSignal; + } + + /** + * Get the signal that caused the process to terminate its execution. + * + * This value is only meaningful if isTerminated() has returned true. Null + * will be returned if the process was never terminated. + * + * @return int|null + */ + public function getTermSignal() + { + return $this->termSignal; + } + + /** + * Return whether the process is still running. + * + * @return bool + */ + public function isRunning() + { + if ($this->process === null) { + return false; + } + + $status = $this->getFreshStatus(); + + return $status !== null ? $status['running'] : false; + } + + /** + * Return whether the process has been stopped by a signal. + * + * @return bool + */ + public function isStopped() + { + $status = $this->getFreshStatus(); + + return $status !== null ? $status['stopped'] : false; + } + + /** + * Return whether the process has been terminated by an uncaught signal. + * + * @return bool + */ + public function isTerminated() + { + $status = $this->getFreshStatus(); + + return $status !== null ? $status['signaled'] : false; + } + + /** + * Return whether PHP has been compiled with the '--enable-sigchild' option. + * + * @see \Symfony\Component\Process\Process::isSigchildEnabled() + * @return bool + */ + public final static function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; // @codeCoverageIgnore + } + + \ob_start(); + \phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== \strpos(\ob_get_clean(), '--enable-sigchild'); + } + + /** + * Enable or disable sigchild compatibility mode. + * + * Sigchild compatibility mode is required to get the exit code and + * determine the success of a process when PHP has been compiled with + * the --enable-sigchild option. + * + * @param bool $sigchild + * @return void + */ + public final static function setSigchildEnabled($sigchild) + { + self::$sigchild = (bool) $sigchild; + } + + /** + * Check the fourth pipe for an exit code. + * + * This should only be used if --enable-sigchild compatibility was enabled. + */ + private function pollExitCodePipe() + { + if ($this->sigchildPipe === null) { + return; + } + + $r = array($this->sigchildPipe); + $w = $e = null; + + $n = @\stream_select($r, $w, $e, 0); + + if (1 !== $n) { + return; + } + + $data = \fread($r[0], 8192); + + if (\strlen($data) > 0) { + $this->fallbackExitCode = (int) $data; + } + } + + /** + * Close the fourth pipe used to relay an exit code. + * + * This should only be used if --enable-sigchild compatibility was enabled. + */ + private function closeExitCodePipe() + { + if ($this->sigchildPipe === null) { + return; + } + + \fclose($this->sigchildPipe); + $this->sigchildPipe = null; + } + + /** + * Return the cached process status. + * + * @return array + */ + private function getCachedStatus() + { + if ($this->status === null) { + $this->updateStatus(); + } + + return $this->status; + } + + /** + * Return the updated process status. + * + * @return array + */ + private function getFreshStatus() + { + $this->updateStatus(); + + return $this->status; + } + + /** + * Update the process status, stop/term signals, and exit code. + * + * Stop/term signals are only updated if the process is currently stopped or + * signaled, respectively. Otherwise, signal values will remain as-is so the + * corresponding getter methods may be used at a later point in time. + */ + private function updateStatus() + { + if ($this->process === null) { + return; + } + + $this->status = \proc_get_status($this->process); + + if ($this->status === false) { + throw new \UnexpectedValueException('proc_get_status() failed'); + } + + if ($this->status['stopped']) { + $this->stopSignal = $this->status['stopsig']; + } + + if ($this->status['signaled']) { + $this->termSignal = $this->status['termsig']; + } + + if (!$this->status['running'] && -1 !== $this->status['exitcode']) { + $this->exitCode = $this->status['exitcode']; + } + } +} diff --git a/vendor/react/dns/CHANGELOG.md b/vendor/react/dns/CHANGELOG.md new file mode 100644 index 0000000000..bc1055fc04 --- /dev/null +++ b/vendor/react/dns/CHANGELOG.md @@ -0,0 +1,452 @@ +# Changelog + +## 1.13.0 (2024-06-13) + +* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations. + (#224 by @WyriHaximus) + +## 1.12.0 (2023-11-29) + +* Feature: Full PHP 8.3 compatibility. + (#217 by @sergiy-petrov) + +* Update test environment and avoid unhandled promise rejections. + (#215, #216 and #218 by @clue) + +## 1.11.0 (2023-06-02) + +* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer. + (#213 by @clue) + +* Improve test suite and project setup and report failed assertions. + (#210 by @clue, #212 by @WyriHaximus and #209 and #211 by @SimonFrings) + +## 1.10.0 (2022-09-08) + +* Feature: Full support for PHP 8.2 release. + (#201 by @clue and #207 by @WyriHaximus) + +* Feature: Optimize forward compatibility with Promise v3, avoid hitting autoloader. + (#202 by @clue) + +* Feature / Fix: Improve error reporting when custom error handler is used. + (#197 by @clue) + +* Fix: Fix invalid references in exception stack trace. + (#191 by @clue) + +* Minor documentation improvements. + (#195 by @SimonFrings and #203 by @nhedger) + +* Improve test suite, update to use default loop and new reactphp/async package. + (#204, #205 and #206 by @clue and #196 by @SimonFrings) + +## 1.9.0 (2021-12-20) + +* Feature: Full support for PHP 8.1 release and prepare PHP 8.2 compatibility + by refactoring `Parser` to avoid assigning dynamic properties. + (#188 and #186 by @clue and #184 by @SimonFrings) + +* Feature: Avoid dependency on `ext-filter`. + (#185 by @clue) + +* Feature / Fix: Skip invalid nameserver entries from `resolv.conf` and ignore IPv6 zone IDs. + (#187 by @clue) + +* Feature / Fix: Reduce socket read chunk size for queries over TCP/IP. + (#189 by @clue) + +## 1.8.0 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop). + (#182 by @clue) + + ```php + // old (still supported) + $factory = new React\Dns\Resolver\Factory(); + $resolver = $factory->create($config, $loop); + + // new (using default loop) + $factory = new React\Dns\Resolver\Factory(); + $resolver = $factory->create($config); + ``` + +## 1.7.0 (2021-06-25) + +* Feature: Update DNS `Factory` to accept complete `Config` object. + Add new `FallbackExecutor` and use fallback DNS servers when `Config` lists multiple servers. + (#179 and #180 by @clue) + + ```php + // old (still supported) + $config = React\Dns\Config\Config::loadSystemConfigBlocking(); + $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + $resolver = $factory->create($server, $loop); + + // new + $config = React\Dns\Config\Config::loadSystemConfigBlocking(); + if (!$config->nameservers) { + $config->nameservers[] = '8.8.8.8'; + } + $resolver = $factory->create($config, $loop); + ``` + +## 1.6.0 (2021-06-21) + +* Feature: Add support for legacy `SPF` record type. + (#178 by @akondas and @clue) + +* Fix: Fix integer overflow for TCP/IP chunk size on 32 bit platforms. + (#177 by @clue) + +## 1.5.0 (2021-03-05) + +* Feature: Improve error reporting when query fails, include domain and query type and DNS server address where applicable. + (#174 by @clue) + +* Feature: Improve error handling when sending data to DNS server fails (macOS). + (#171 and #172 by @clue) + +* Fix: Improve DNS response parser to limit recursion for compressed labels. + (#169 by @clue) + +* Improve test suite, use GitHub actions for continuous integration (CI). + (#170 by @SimonFrings) + +## 1.4.0 (2020-09-18) + +* Feature: Support upcoming PHP 8. + (#168 by @clue) + +* Improve test suite and update to PHPUnit 9.3. + (#164 by @clue, #165 and #166 by @SimonFrings and #167 by @WyriHaximus) + +## 1.3.0 (2020-07-10) + +* Feature: Forward compatibility with react/promise v3. + (#153 by @WyriHaximus) + +* Feature: Support parsing `OPT` records (EDNS0). + (#157 by @clue) + +* Fix: Avoid PHP warnings due to lack of args in exception trace on PHP 7.4. + (#160 by @clue) + +* Improve test suite and add `.gitattributes` to exclude dev files from exports. + Run tests on PHPUnit 9 and PHP 7.4 and clean up test suite. + (#154 by @reedy, #156 by @clue and #163 by @SimonFrings) + +## 1.2.0 (2019-08-15) + +* Feature: Add `TcpTransportExecutor` to send DNS queries over TCP/IP connection, + add `SelectiveTransportExecutor` to retry with TCP if UDP is truncated and + automatically select transport protocol when no explicit `udp://` or `tcp://` scheme is given in `Factory`. + (#145, #146, #147 and #148 by @clue) + +* Feature: Support escaping literal dots and special characters in domain names. + (#144 by @clue) + +## 1.1.0 (2019-07-18) + +* Feature: Support parsing `CAA` and `SSHFP` records. + (#141 and #142 by @clue) + +* Feature: Add `ResolverInterface` as common interface for `Resolver` class. + (#139 by @clue) + +* Fix: Add missing private property definitions and + remove unneeded dependency on `react/stream`. + (#140 and #143 by @clue) + +## 1.0.0 (2019-07-11) + +* First stable LTS release, now following [SemVer](https://semver.org/). + We'd like to emphasize that this component is production ready and battle-tested. + We plan to support all long-term support (LTS) releases for at least 24 months, + so you have a rock-solid foundation to build on top of. + +This update involves a number of BC breaks due to dropped support for +deprecated functionality and some internal API cleanup. We've tried hard to +avoid BC breaks where possible and minimize impact otherwise. We expect that +most consumers of this package will actually not be affected by any BC +breaks, see below for more details: + +* BC break: Delete all deprecated APIs, use `Query` objects for `Message` questions + instead of nested arrays and increase code coverage to 100%. + (#130 by @clue) + +* BC break: Move `$nameserver` from `ExecutorInterface` to `UdpTransportExecutor`, + remove advanced/internal `UdpTransportExecutor` args for `Parser`/`BinaryDumper` and + add API documentation for `ExecutorInterface`. + (#135, #137 and #138 by @clue) + +* BC break: Replace `HeaderBag` attributes with simple `Message` properties. + (#132 by @clue) + +* BC break: Mark all `Record` attributes as required, add documentation vs `Query`. + (#136 by @clue) + +* BC break: Mark all classes as final to discourage inheritance + (#134 by @WyriHaximus) + +## 0.4.19 (2019-07-10) + +* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6. + (#133 by @clue) + +## 0.4.18 (2019-09-07) + +* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`, + respect TTL from response records when caching and do not cache truncated responses. + (#129 by @clue) + +* Feature: Limit cache size to 256 last responses by default. + (#127 by @clue) + +* Feature: Cooperatively resolve hosts to avoid running same query concurrently. + (#125 by @clue) + +## 0.4.17 (2019-04-01) + +* Feature: Support parsing `authority` and `additional` records from DNS response. + (#123 by @clue) + +* Feature: Support dumping records as part of outgoing binary DNS message. + (#124 by @clue) + +* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0 + (#121 by @clue) + +* Improve test suite to add forward compatibility with PHPUnit 7, + test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM. + (#122 by @clue) + +## 0.4.16 (2018-11-11) + +* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references. + (#118 by @clue) + +* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages, + malformed record data or malformed compressed domain name labels. + (#115 and #117 by @clue) + +* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset. + (#116 by @clue) + +* Fix: Fix caching advanced MX/SRV/TXT/SOA structures. + (#112 by @clue) + +## 0.4.15 (2018-07-02) + +* Feature: Add `resolveAll()` method to support custom query types in `Resolver`. + (#110 by @clue and @WyriHaximus) + + ```php + $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) { + echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; + }); + ``` + +* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records. + (#104, #105, #106, #107 and #108 by @clue) + +* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data. + (#104 by @clue) + +* Feature: Improve error messages for failed queries and improve documentation. + (#109 by @clue) + +* Feature: Add reverse DNS lookup example. + (#111 by @clue) + +## 0.4.14 (2018-06-26) + +* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages + to avoid cache poisoning attacks and deprecate legacy `Executor`. + (#101 and #103 by @clue) + +* Feature: Forward compatibility with Cache 0.5 + (#102 by @clue) + +* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API. + (#99 by @clue) + +## 0.4.13 (2018-02-27) + +* Add `Config::loadSystemConfigBlocking()` to load default system config + and support parsing DNS config on all supported platforms + (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows) + (#92, #93, #94 and #95 by @clue) + + ```php + $config = Config::loadSystemConfigBlocking(); + $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8'; + ``` + +* Remove unneeded cyclic dependency on react/socket + (#96 by @clue) + +## 0.4.12 (2018-01-14) + +* Improve test suite by adding forward compatibility with PHPUnit 6, + test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases, + add test group to skip integration tests relying on internet connection + and add minor documentation improvements. + (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor) + +## 0.4.11 (2017-08-25) + +* Feature: Support resolving from default hosts file + (#75, #76 and #77 by @clue) + + This means that resolving hosts such as `localhost` will now work as + expected across all platforms with no changes required: + + ```php + $resolver->resolve('localhost')->then(function ($ip) { + echo 'IP: ' . $ip; + }); + ``` + + The new `HostsExecutor` exists for advanced usage and is otherwise used + internally for this feature. + +## 0.4.10 (2017-08-10) + +* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and + lock minimum dependencies and work around circular dependency for tests + (#70 and #71 by @clue) + +* Fix: Work around DNS timeout issues for Windows users + (#74 by @clue) + +* Documentation and examples for advanced usage + (#66 by @WyriHaximus) + +* Remove broken TCP code, do not retry with invalid TCP query + (#73 by @clue) + +* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and + lock Travis distro so new defaults will not break the build and + fix failing tests for PHP 7.1 + (#68 by @WyriHaximus and #69 and #72 by @clue) + +## 0.4.9 (2017-05-01) + +* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 + (#61 by @clue) + +## 0.4.8 (2017-04-16) + +* Feature: Add support for the AAAA record type to the protocol parser + (#58 by @othillo) + +* Feature: Add support for the PTR record type to the protocol parser + (#59 by @othillo) + +## 0.4.7 (2017-03-31) + +* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component + (#57 by @clue) + +## 0.4.6 (2017-03-11) + +* Fix: Fix DNS timeout issues for Windows users and add forward compatibility + with Stream v0.5 and upcoming v0.6 + (#53 by @clue) + +* Improve test suite by adding PHPUnit to `require-dev` + (#54 by @clue) + +## 0.4.5 (2017-03-02) + +* Fix: Ensure we ignore the case of the answer + (#51 by @WyriHaximus) + +* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal + code re-use for upcoming versions. + (#48 and #49 by @clue) + +## 0.4.4 (2017-02-13) + +* Fix: Fix handling connection and stream errors + (#45 by @clue) + +* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component + (#46 and #47 by @clue) + +## 0.4.3 (2016-07-31) + +* Feature: Allow for cache adapter injection (#38 by @WyriHaximus) + + ```php + $factory = new React\Dns\Resolver\Factory(); + + $cache = new MyCustomCacheInstance(); + $resolver = $factory->createCached('8.8.8.8', $loop, $cache); + ``` + +* Feature: Support Promise cancellation (#35 by @clue) + + ```php + $promise = $resolver->resolve('reactphp.org'); + + $promise->cancel(); + ``` + +## 0.4.2 (2016-02-24) + +* Repository maintenance, split off from main repo, improve test suite and documentation +* First class support for PHP7 and HHVM (#34 by @clue) +* Adjust compatibility to 5.3 (#30 by @clue) + +## 0.4.1 (2014-04-13) + +* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* Bug fix: Properly resolve CNAME aliases +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 +* Bump React dependencies to v0.4 + +## 0.3.2 (2013-05-10) + +* Feature: Support default port for IPv6 addresses (@clue) + +## 0.3.0 (2013-04-14) + +* Bump React dependencies to v0.3 + +## 0.2.6 (2012-12-26) + +* Feature: New cache component, used by DNS + +## 0.2.5 (2012-11-26) + +* Version bump + +## 0.2.4 (2012-11-18) + +* Feature: Change to promise-based API (@jsor) + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.2 (2012-10-28) + +* Feature: DNS executor timeout handling (@arnaud-lb) +* Feature: DNS retry executor (@arnaud-lb) + +## 0.2.1 (2012-10-14) + +* Minor adjustments to DNS parser + +## 0.2.0 (2012-09-10) + +* Feature: DNS resolver diff --git a/vendor/react/dns/LICENSE b/vendor/react/dns/LICENSE new file mode 100644 index 0000000000..d6f8901f9a --- /dev/null +++ b/vendor/react/dns/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/react/dns/README.md b/vendor/react/dns/README.md new file mode 100644 index 0000000000..9f83a944dc --- /dev/null +++ b/vendor/react/dns/README.md @@ -0,0 +1,453 @@ +# DNS + +[![CI status](https://github.com/reactphp/dns/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/dns/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/dns?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/dns) + +Async DNS resolver for [ReactPHP](https://reactphp.org/). + +The main point of the DNS component is to provide async DNS resolution. +However, it is really a toolkit for working with DNS messages, and could +easily be used to create a DNS server. + +**Table of contents** + +* [Basic usage](#basic-usage) +* [Caching](#caching) + * [Custom cache adapter](#custom-cache-adapter) +* [ResolverInterface](#resolverinterface) + * [resolve()](#resolve) + * [resolveAll()](#resolveall) +* [Advanced usage](#advanced-usage) + * [UdpTransportExecutor](#udptransportexecutor) + * [TcpTransportExecutor](#tcptransportexecutor) + * [SelectiveTransportExecutor](#selectivetransportexecutor) + * [HostsFileExecutor](#hostsfileexecutor) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [References](#references) + +## Basic usage + +The most basic usage is to just create a resolver through the resolver +factory. All you need to give it is a nameserver, then you can start resolving +names, baby! + +```php +$config = React\Dns\Config\Config::loadSystemConfigBlocking(); +if (!$config->nameservers) { + $config->nameservers[] = '8.8.8.8'; +} + +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->create($config); + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); +``` + +See also the [first example](examples). + +The `Config` class can be used to load the system default config. This is an +operation that may access the filesystem and block. Ideally, this method should +thus be executed only once before the loop starts and not repeatedly while it is +running. +Note that this class may return an *empty* configuration if the system config +can not be loaded. As such, you'll likely want to apply a default nameserver +as above if none can be found. + +> Note that the factory loads the hosts file from the filesystem once when + creating the resolver instance. + Ideally, this method should thus be executed only once before the loop starts + and not repeatedly while it is running. + +But there's more. + +## Caching + +You can cache results by configuring the resolver to use a `CachedExecutor`: + +```php +$config = React\Dns\Config\Config::loadSystemConfigBlocking(); +if (!$config->nameservers) { + $config->nameservers[] = '8.8.8.8'; +} + +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->createCached($config); + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); + +... + +$dns->resolve('igor.io')->then(function ($ip) { + echo "Host: $ip\n"; +}); +``` + +If the first call returns before the second, only one query will be executed. +The second result will be served from an in memory cache. +This is particularly useful for long running scripts where the same hostnames +have to be looked up multiple times. + +See also the [third example](examples). + +### Custom cache adapter + +By default, the above will use an in memory cache. + +You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead: + +```php +$cache = new React\Cache\ArrayCache(); +$factory = new React\Dns\Resolver\Factory(); +$dns = $factory->createCached('8.8.8.8', null, $cache); +``` + +See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations). + +## ResolverInterface + + + +### resolve() + +The `resolve(string $domain): PromiseInterface` method can be used to +resolve the given $domain name to a single IPv4 address (type `A` query). + +```php +$resolver->resolve('reactphp.org')->then(function ($ip) { + echo 'IP for reactphp.org is ' . $ip . PHP_EOL; +}); +``` + +This is one of the main methods in this package. It sends a DNS query +for the given $domain name to your DNS server and returns a single IP +address on success. + +If the DNS server sends a DNS response message that contains more than +one IP address for this query, it will randomly pick one of the IP +addresses from the response. If you want the full list of IP addresses +or want to send a different type of query, you should use the +[`resolveAll()`](#resolveall) method instead. + +If the DNS server sends a DNS response message that indicates an error +code, this method will reject with a `RecordNotFoundException`. Its +message and code can be used to check for the response code. + +If the DNS communication fails and the server does not respond with a +valid response message, this message will reject with an `Exception`. + +Pending DNS queries can be cancelled by cancelling its pending promise like so: + +```php +$promise = $resolver->resolve('reactphp.org'); + +$promise->cancel(); +``` + +### resolveAll() + +The `resolveAll(string $host, int $type): PromiseInterface` method can be used to +resolve all record values for the given $domain name and query $type. + +```php +$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) { + echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; +}); + +$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) { + echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; +}); +``` + +This is one of the main methods in this package. It sends a DNS query +for the given $domain name to your DNS server and returns a list with all +record values on success. + +If the DNS server sends a DNS response message that contains one or more +records for this query, it will return a list with all record values +from the response. You can use the `Message::TYPE_*` constants to control +which type of query will be sent. Note that this method always returns a +list of record values, but each record value type depends on the query +type. For example, it returns the IPv4 addresses for type `A` queries, +the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`, +`CNAME` and `PTR` queries and structured data for other queries. See also +the `Record` documentation for more details. + +If the DNS server sends a DNS response message that indicates an error +code, this method will reject with a `RecordNotFoundException`. Its +message and code can be used to check for the response code. + +If the DNS communication fails and the server does not respond with a +valid response message, this message will reject with an `Exception`. + +Pending DNS queries can be cancelled by cancelling its pending promise like so: + +```php +$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA); + +$promise->cancel(); +``` + +## Advanced Usage + +### UdpTransportExecutor + +The `UdpTransportExecutor` can be used to +send DNS queries over a UDP transport. + +This is the main class that sends a DNS query to your DNS server and is used +internally by the `Resolver` for the actual message transport. + +For more advanced usages one can utilize this class directly. +The following example looks up the `IPv6` address for `igor.io`. + +```php +$executor = new UdpTransportExecutor('8.8.8.8:53'); + +$executor->query( + new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) +)->then(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); +``` + +See also the [fourth example](examples). + +Note that this executor does not implement a timeout, so you will very likely +want to use this in combination with a `TimeoutExecutor` like this: + +```php +$executor = new TimeoutExecutor( + new UdpTransportExecutor($nameserver), + 3.0 +); +``` + +Also note that this executor uses an unreliable UDP transport and that it +does not implement any retry logic, so you will likely want to use this in +combination with a `RetryExecutor` like this: + +```php +$executor = new RetryExecutor( + new TimeoutExecutor( + new UdpTransportExecutor($nameserver), + 3.0 + ) +); +``` + +Note that this executor is entirely async and as such allows you to execute +any number of queries concurrently. You should probably limit the number of +concurrent queries in your application or you're very likely going to face +rate limitations and bans on the resolver end. For many common applications, +you may want to avoid sending the same query multiple times when the first +one is still pending, so you will likely want to use this in combination with +a `CoopExecutor` like this: + +```php +$executor = new CoopExecutor( + new RetryExecutor( + new TimeoutExecutor( + new UdpTransportExecutor($nameserver), + 3.0 + ) + ) +); +``` + +> Internally, this class uses PHP's UDP sockets and does not take advantage + of [react/datagram](https://github.com/reactphp/datagram) purely for + organizational reasons to avoid a cyclic dependency between the two + packages. Higher-level components should take advantage of the Datagram + component instead of reimplementing this socket logic from scratch. + +### TcpTransportExecutor + +The `TcpTransportExecutor` class can be used to +send DNS queries over a TCP/IP stream transport. + +This is one of the main classes that send a DNS query to your DNS server. + +For more advanced usages one can utilize this class directly. +The following example looks up the `IPv6` address for `reactphp.org`. + +```php +$executor = new TcpTransportExecutor('8.8.8.8:53'); + +$executor->query( + new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) +)->then(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); +``` + +See also [example #92](examples). + +Note that this executor does not implement a timeout, so you will very likely +want to use this in combination with a `TimeoutExecutor` like this: + +```php +$executor = new TimeoutExecutor( + new TcpTransportExecutor($nameserver), + 3.0 +); +``` + +Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP +transport, so you do not necessarily have to implement any retry logic. + +Note that this executor is entirely async and as such allows you to execute +queries concurrently. The first query will establish a TCP/IP socket +connection to the DNS server which will be kept open for a short period. +Additional queries will automatically reuse this existing socket connection +to the DNS server, will pipeline multiple requests over this single +connection and will keep an idle connection open for a short period. The +initial TCP/IP connection overhead may incur a slight delay if you only send +occasional queries – when sending a larger number of concurrent queries over +an existing connection, it becomes increasingly more efficient and avoids +creating many concurrent sockets like the UDP-based executor. You may still +want to limit the number of (concurrent) queries in your application or you +may be facing rate limitations and bans on the resolver end. For many common +applications, you may want to avoid sending the same query multiple times +when the first one is still pending, so you will likely want to use this in +combination with a `CoopExecutor` like this: + +```php +$executor = new CoopExecutor( + new TimeoutExecutor( + new TcpTransportExecutor($nameserver), + 3.0 + ) +); +``` + +> Internally, this class uses PHP's TCP/IP sockets and does not take advantage + of [react/socket](https://github.com/reactphp/socket) purely for + organizational reasons to avoid a cyclic dependency between the two + packages. Higher-level components should take advantage of the Socket + component instead of reimplementing this socket logic from scratch. + +### SelectiveTransportExecutor + +The `SelectiveTransportExecutor` class can be used to +Send DNS queries over a UDP or TCP/IP stream transport. + +This class will automatically choose the correct transport protocol to send +a DNS query to your DNS server. It will always try to send it over the more +efficient UDP transport first. If this query yields a size related issue +(truncated messages), it will retry over a streaming TCP/IP transport. + +For more advanced usages one can utilize this class directly. +The following example looks up the `IPv6` address for `reactphp.org`. + +```php +$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor); + +$executor->query( + new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) +)->then(function (Message $message) { + foreach ($message->answers as $answer) { + echo 'IPv6: ' . $answer->data . PHP_EOL; + } +}, 'printf'); +``` + +Note that this executor only implements the logic to select the correct +transport for the given DNS query. Implementing the correct transport logic, +implementing timeouts and any retry logic is left up to the given executors, +see also [`UdpTransportExecutor`](#udptransportexecutor) and +[`TcpTransportExecutor`](#tcptransportexecutor) for more details. + +Note that this executor is entirely async and as such allows you to execute +any number of queries concurrently. You should probably limit the number of +concurrent queries in your application or you're very likely going to face +rate limitations and bans on the resolver end. For many common applications, +you may want to avoid sending the same query multiple times when the first +one is still pending, so you will likely want to use this in combination with +a `CoopExecutor` like this: + +```php +$executor = new CoopExecutor( + new SelectiveTransportExecutor( + $datagramExecutor, + $streamExecutor + ) +); +``` + +### HostsFileExecutor + +Note that the above `UdpTransportExecutor` class always performs an actual DNS query. +If you also want to take entries from your hosts file into account, you may +use this code: + +```php +$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking(); + +$executor = new UdpTransportExecutor('8.8.8.8:53'); +$executor = new HostsFileExecutor($hosts, $executor); + +$executor->query( + new Query('localhost', Message::TYPE_A, Message::CLASS_IN) +); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require react/dns:^1.13 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and +HHVM. +It's *highly recommended to use the latest supported PHP version* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## References + +* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities +* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification diff --git a/vendor/react/dns/composer.json b/vendor/react/dns/composer.json new file mode 100644 index 0000000000..4fe5c0da24 --- /dev/null +++ b/vendor/react/dns/composer.json @@ -0,0 +1,49 @@ +{ + "name": "react/dns", + "description": "Async DNS resolver for ReactPHP", + "keywords": ["dns", "dns-resolver", "ReactPHP", "async"], + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Dns\\": "tests/" + } + } +} diff --git a/vendor/react/dns/src/BadServerException.php b/vendor/react/dns/src/BadServerException.php new file mode 100644 index 0000000000..3d95213f48 --- /dev/null +++ b/vendor/react/dns/src/BadServerException.php @@ -0,0 +1,7 @@ + `fe80:1`) + if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) { + $ip = substr($ip, 0, $pos); + } + + if (@inet_pton($ip) !== false) { + $config->nameservers[] = $ip; + } + } + + return $config; + } + + /** + * Loads the DNS configurations from Windows's WMIC (from the given command or default command) + * + * Note that this method blocks while loading the given command and should + * thus be used with care! While this should be relatively fast for normal + * WMIC commands, it remains unknown if this may block under certain + * circumstances. In particular, this method should only be executed before + * the loop starts, not while it is running. + * + * Note that this method will only try to execute the given command try to + * parse its output, irrespective of whether this command exists. In + * particular, this command is only available on Windows. Currently, this + * will only parse valid nameserver entries from the command output and will + * ignore all other output without complaining. + * + * Note that the previous section implies that this may return an empty + * `Config` object if no valid nameserver entries can be found. + * + * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing + * @return self + * @link https://ss64.com/nt/wmic.html + */ + public static function loadWmicBlocking($command = null) + { + $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command); + preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches); + + $config = new self(); + $config->nameservers = $matches[1]; + + return $config; + } + + public $nameservers = array(); +} diff --git a/vendor/react/dns/src/Config/HostsFile.php b/vendor/react/dns/src/Config/HostsFile.php new file mode 100644 index 0000000000..1060231a20 --- /dev/null +++ b/vendor/react/dns/src/Config/HostsFile.php @@ -0,0 +1,153 @@ +contents = $contents; + } + + /** + * Returns all IPs for the given hostname + * + * @param string $name + * @return string[] + */ + public function getIpsForHost($name) + { + $name = strtolower($name); + + $ips = array(); + foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $parts = preg_split('/\s+/', $line); + $ip = array_shift($parts); + if ($parts && array_search($name, $parts) !== false) { + // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) + if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) { + $ip = substr($ip, 0, $pos); + } + + if (@inet_pton($ip) !== false) { + $ips[] = $ip; + } + } + } + + return $ips; + } + + /** + * Returns all hostnames for the given IPv4 or IPv6 address + * + * @param string $ip + * @return string[] + */ + public function getHostsForIp($ip) + { + // check binary representation of IP to avoid string case and short notation + $ip = @inet_pton($ip); + if ($ip === false) { + return array(); + } + + $names = array(); + foreach (preg_split('/\r?\n/', $this->contents) as $line) { + $parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY); + $addr = (string) array_shift($parts); + + // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`) + if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) { + $addr = substr($addr, 0, $pos); + } + + if (@inet_pton($addr) === $ip) { + foreach ($parts as $part) { + $names[] = $part; + } + } + } + + return $names; + } +} diff --git a/vendor/react/dns/src/Model/Message.php b/vendor/react/dns/src/Model/Message.php new file mode 100644 index 0000000000..bac2b10d0e --- /dev/null +++ b/vendor/react/dns/src/Model/Message.php @@ -0,0 +1,230 @@ +id = self::generateId(); + $request->rd = true; + $request->questions[] = $query; + + return $request; + } + + /** + * Creates a new response message for the given query with the given answer records + * + * @param Query $query + * @param Record[] $answers + * @return self + */ + public static function createResponseWithAnswersForQuery(Query $query, array $answers) + { + $response = new Message(); + $response->id = self::generateId(); + $response->qr = true; + $response->rd = true; + + $response->questions[] = $query; + + foreach ($answers as $record) { + $response->answers[] = $record; + } + + return $response; + } + + /** + * generates a random 16 bit message ID + * + * This uses a CSPRNG so that an outside attacker that is sending spoofed + * DNS response messages can not guess the message ID to avoid possible + * cache poisoning attacks. + * + * The `random_int()` function is only available on PHP 7+ or when + * https://github.com/paragonie/random_compat is installed. As such, using + * the latest supported PHP version is highly recommended. This currently + * falls back to a less secure random number generator on older PHP versions + * in the hope that this system is properly protected against outside + * attackers, for example by using one of the common local DNS proxy stubs. + * + * @return int + * @see self::getId() + * @codeCoverageIgnore + */ + private static function generateId() + { + if (function_exists('random_int')) { + return random_int(0, 0xffff); + } + return mt_rand(0, 0xffff); + } + + /** + * The 16 bit message ID + * + * The response message ID has to match the request message ID. This allows + * the receiver to verify this is the correct response message. An outside + * attacker may try to inject fake responses by "guessing" the message ID, + * so this should use a proper CSPRNG to avoid possible cache poisoning. + * + * @var int 16 bit message ID + * @see self::generateId() + */ + public $id = 0; + + /** + * @var bool Query/Response flag, query=false or response=true + */ + public $qr = false; + + /** + * @var int specifies the kind of query (4 bit), see self::OPCODE_* constants + * @see self::OPCODE_QUERY + */ + public $opcode = self::OPCODE_QUERY; + + /** + * + * @var bool Authoritative Answer + */ + public $aa = false; + + /** + * @var bool TrunCation + */ + public $tc = false; + + /** + * @var bool Recursion Desired + */ + public $rd = false; + + /** + * @var bool Recursion Available + */ + public $ra = false; + + /** + * @var int response code (4 bit), see self::RCODE_* constants + * @see self::RCODE_OK + */ + public $rcode = Message::RCODE_OK; + + /** + * An array of Query objects + * + * ```php + * $questions = array( + * new Query( + * 'reactphp.org', + * Message::TYPE_A, + * Message::CLASS_IN + * ) + * ); + * ``` + * + * @var Query[] + */ + public $questions = array(); + + /** + * @var Record[] + */ + public $answers = array(); + + /** + * @var Record[] + */ + public $authority = array(); + + /** + * @var Record[] + */ + public $additional = array(); +} diff --git a/vendor/react/dns/src/Model/Record.php b/vendor/react/dns/src/Model/Record.php new file mode 100644 index 0000000000..c20403f52c --- /dev/null +++ b/vendor/react/dns/src/Model/Record.php @@ -0,0 +1,153 @@ +name = $name; + $this->type = $type; + $this->class = $class; + $this->ttl = $ttl; + $this->data = $data; + } +} diff --git a/vendor/react/dns/src/Protocol/BinaryDumper.php b/vendor/react/dns/src/Protocol/BinaryDumper.php new file mode 100644 index 0000000000..6f4030f63c --- /dev/null +++ b/vendor/react/dns/src/Protocol/BinaryDumper.php @@ -0,0 +1,199 @@ +headerToBinary($message); + $data .= $this->questionToBinary($message->questions); + $data .= $this->recordsToBinary($message->answers); + $data .= $this->recordsToBinary($message->authority); + $data .= $this->recordsToBinary($message->additional); + + return $data; + } + + /** + * @param Message $message + * @return string + */ + private function headerToBinary(Message $message) + { + $data = ''; + + $data .= pack('n', $message->id); + + $flags = 0x00; + $flags = ($flags << 1) | ($message->qr ? 1 : 0); + $flags = ($flags << 4) | $message->opcode; + $flags = ($flags << 1) | ($message->aa ? 1 : 0); + $flags = ($flags << 1) | ($message->tc ? 1 : 0); + $flags = ($flags << 1) | ($message->rd ? 1 : 0); + $flags = ($flags << 1) | ($message->ra ? 1 : 0); + $flags = ($flags << 3) | 0; // skip unused zero bit + $flags = ($flags << 4) | $message->rcode; + + $data .= pack('n', $flags); + + $data .= pack('n', count($message->questions)); + $data .= pack('n', count($message->answers)); + $data .= pack('n', count($message->authority)); + $data .= pack('n', count($message->additional)); + + return $data; + } + + /** + * @param Query[] $questions + * @return string + */ + private function questionToBinary(array $questions) + { + $data = ''; + + foreach ($questions as $question) { + $data .= $this->domainNameToBinary($question->name); + $data .= pack('n*', $question->type, $question->class); + } + + return $data; + } + + /** + * @param Record[] $records + * @return string + */ + private function recordsToBinary(array $records) + { + $data = ''; + + foreach ($records as $record) { + /* @var $record Record */ + switch ($record->type) { + case Message::TYPE_A: + case Message::TYPE_AAAA: + $binary = \inet_pton($record->data); + break; + case Message::TYPE_CNAME: + case Message::TYPE_NS: + case Message::TYPE_PTR: + $binary = $this->domainNameToBinary($record->data); + break; + case Message::TYPE_TXT: + case Message::TYPE_SPF: + $binary = $this->textsToBinary($record->data); + break; + case Message::TYPE_MX: + $binary = \pack( + 'n', + $record->data['priority'] + ); + $binary .= $this->domainNameToBinary($record->data['target']); + break; + case Message::TYPE_SRV: + $binary = \pack( + 'n*', + $record->data['priority'], + $record->data['weight'], + $record->data['port'] + ); + $binary .= $this->domainNameToBinary($record->data['target']); + break; + case Message::TYPE_SOA: + $binary = $this->domainNameToBinary($record->data['mname']); + $binary .= $this->domainNameToBinary($record->data['rname']); + $binary .= \pack( + 'N*', + $record->data['serial'], + $record->data['refresh'], + $record->data['retry'], + $record->data['expire'], + $record->data['minimum'] + ); + break; + case Message::TYPE_CAA: + $binary = \pack( + 'C*', + $record->data['flag'], + \strlen($record->data['tag']) + ); + $binary .= $record->data['tag']; + $binary .= $record->data['value']; + break; + case Message::TYPE_SSHFP: + $binary = \pack( + 'CCH*', + $record->data['algorithm'], + $record->data['type'], + $record->data['fingerprint'] + ); + break; + case Message::TYPE_OPT: + $binary = ''; + foreach ($record->data as $opt => $value) { + if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) { + $value = \pack('n', round($value * 10)); + } + $binary .= \pack('n*', $opt, \strlen((string) $value)) . $value; + } + break; + default: + // RDATA is already stored as binary value for unknown record types + $binary = $record->data; + } + + $data .= $this->domainNameToBinary($record->name); + $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary)); + $data .= $binary; + } + + return $data; + } + + /** + * @param string[] $texts + * @return string + */ + private function textsToBinary(array $texts) + { + $data = ''; + foreach ($texts as $text) { + $data .= \chr(\strlen($text)) . $text; + } + return $data; + } + + /** + * @param string $host + * @return string + */ + private function domainNameToBinary($host) + { + if ($host === '') { + return "\0"; + } + + // break up domain name at each dot that is not preceeded by a backslash (escaped notation) + return $this->textsToBinary( + \array_map( + 'stripcslashes', + \preg_split( + '/(?parse($data, 0); + if ($message === null) { + throw new InvalidArgumentException('Unable to parse binary message'); + } + + return $message; + } + + /** + * @param string $data + * @param int $consumed + * @return ?Message + */ + private function parse($data, $consumed) + { + if (!isset($data[12 - 1])) { + return null; + } + + list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12))); + + $message = new Message(); + $message->id = $id; + $message->rcode = $fields & 0xf; + $message->ra = (($fields >> 7) & 1) === 1; + $message->rd = (($fields >> 8) & 1) === 1; + $message->tc = (($fields >> 9) & 1) === 1; + $message->aa = (($fields >> 10) & 1) === 1; + $message->opcode = ($fields >> 11) & 0xf; + $message->qr = (($fields >> 15) & 1) === 1; + $consumed += 12; + + // parse all questions + for ($i = $qdCount; $i > 0; --$i) { + list($question, $consumed) = $this->parseQuestion($data, $consumed); + if ($question === null) { + return null; + } else { + $message->questions[] = $question; + } + } + + // parse all answer records + for ($i = $anCount; $i > 0; --$i) { + list($record, $consumed) = $this->parseRecord($data, $consumed); + if ($record === null) { + return null; + } else { + $message->answers[] = $record; + } + } + + // parse all authority records + for ($i = $nsCount; $i > 0; --$i) { + list($record, $consumed) = $this->parseRecord($data, $consumed); + if ($record === null) { + return null; + } else { + $message->authority[] = $record; + } + } + + // parse all additional records + for ($i = $arCount; $i > 0; --$i) { + list($record, $consumed) = $this->parseRecord($data, $consumed); + if ($record === null) { + return null; + } else { + $message->additional[] = $record; + } + } + + return $message; + } + + /** + * @param string $data + * @param int $consumed + * @return array + */ + private function parseQuestion($data, $consumed) + { + list($labels, $consumed) = $this->readLabels($data, $consumed); + + if ($labels === null || !isset($data[$consumed + 4 - 1])) { + return array(null, null); + } + + list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4))); + $consumed += 4; + + return array( + new Query( + implode('.', $labels), + $type, + $class + ), + $consumed + ); + } + + /** + * @param string $data + * @param int $consumed + * @return array An array with a parsed Record on success or array with null if data is invalid/incomplete + */ + private function parseRecord($data, $consumed) + { + list($name, $consumed) = $this->readDomain($data, $consumed); + + if ($name === null || !isset($data[$consumed + 10 - 1])) { + return array(null, null); + } + + list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4))); + $consumed += 4; + + list($ttl) = array_values(unpack('N', substr($data, $consumed, 4))); + $consumed += 4; + + // TTL is a UINT32 that must not have most significant bit set for BC reasons + if ($ttl < 0 || $ttl >= 1 << 31) { + $ttl = 0; + } + + list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2))); + $consumed += 2; + + if (!isset($data[$consumed + $rdLength - 1])) { + return array(null, null); + } + + $rdata = null; + $expected = $consumed + $rdLength; + + if (Message::TYPE_A === $type) { + if ($rdLength === 4) { + $rdata = inet_ntop(substr($data, $consumed, $rdLength)); + $consumed += $rdLength; + } + } elseif (Message::TYPE_AAAA === $type) { + if ($rdLength === 16) { + $rdata = inet_ntop(substr($data, $consumed, $rdLength)); + $consumed += $rdLength; + } + } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) { + list($rdata, $consumed) = $this->readDomain($data, $consumed); + } elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) { + $rdata = array(); + while ($consumed < $expected) { + $len = ord($data[$consumed]); + $rdata[] = (string)substr($data, $consumed + 1, $len); + $consumed += $len + 1; + } + } elseif (Message::TYPE_MX === $type) { + if ($rdLength > 2) { + list($priority) = array_values(unpack('n', substr($data, $consumed, 2))); + list($target, $consumed) = $this->readDomain($data, $consumed + 2); + + $rdata = array( + 'priority' => $priority, + 'target' => $target + ); + } + } elseif (Message::TYPE_SRV === $type) { + if ($rdLength > 6) { + list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6))); + list($target, $consumed) = $this->readDomain($data, $consumed + 6); + + $rdata = array( + 'priority' => $priority, + 'weight' => $weight, + 'port' => $port, + 'target' => $target + ); + } + } elseif (Message::TYPE_SSHFP === $type) { + if ($rdLength > 2) { + list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2))); + $fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2)); + $consumed += $rdLength; + + $rdata = array( + 'algorithm' => $algorithm, + 'type' => $hash, + 'fingerprint' => $fingerprint + ); + } + } elseif (Message::TYPE_SOA === $type) { + list($mname, $consumed) = $this->readDomain($data, $consumed); + list($rname, $consumed) = $this->readDomain($data, $consumed); + + if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) { + list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20))); + $consumed += 20; + + $rdata = array( + 'mname' => $mname, + 'rname' => $rname, + 'serial' => $serial, + 'refresh' => $refresh, + 'retry' => $retry, + 'expire' => $expire, + 'minimum' => $minimum + ); + } + } elseif (Message::TYPE_OPT === $type) { + $rdata = array(); + while (isset($data[$consumed + 4 - 1])) { + list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4))); + $value = (string) substr($data, $consumed + 4, $length); + if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') { + $value = null; + } elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) { + list($value) = array_values(unpack('n', $value)); + $value = round($value * 0.1, 1); + } elseif ($code === Message::OPT_TCP_KEEPALIVE) { + break; + } + $rdata[$code] = $value; + $consumed += 4 + $length; + } + } elseif (Message::TYPE_CAA === $type) { + if ($rdLength > 3) { + list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2))); + + if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) { + $tag = substr($data, $consumed + 2, $tagLength); + $value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength); + $consumed += $rdLength; + + $rdata = array( + 'flag' => $flag, + 'tag' => $tag, + 'value' => $value + ); + } + } + } else { + // unknown types simply parse rdata as an opaque binary string + $rdata = substr($data, $consumed, $rdLength); + $consumed += $rdLength; + } + + // ensure parsing record data consumes expact number of bytes indicated in record length + if ($consumed !== $expected || $rdata === null) { + return array(null, null); + } + + return array( + new Record($name, $type, $class, $ttl, $rdata), + $consumed + ); + } + + private function readDomain($data, $consumed) + { + list ($labels, $consumed) = $this->readLabels($data, $consumed); + + if ($labels === null) { + return array(null, null); + } + + // use escaped notation for each label part, then join using dots + return array( + \implode( + '.', + \array_map( + function ($label) { + return \addcslashes($label, "\0..\40.\177"); + }, + $labels + ) + ), + $consumed + ); + } + + /** + * @param string $data + * @param int $consumed + * @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion + * @return array + */ + private function readLabels($data, $consumed, $compressionDepth = 127) + { + $labels = array(); + + while (true) { + if (!isset($data[$consumed])) { + return array(null, null); + } + + $length = \ord($data[$consumed]); + + // end of labels reached + if ($length === 0) { + $consumed += 1; + break; + } + + // first two bits set? this is a compressed label (14 bit pointer offset) + if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1]) && $compressionDepth) { + $offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]); + if ($offset >= $consumed) { + return array(null, null); + } + + $consumed += 2; + list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1); + + if ($newLabels === null) { + return array(null, null); + } + + $labels = array_merge($labels, $newLabels); + break; + } + + // length MUST be 0-63 (6 bits only) and data has to be large enough + if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) { + return array(null, null); + } + + $labels[] = substr($data, $consumed + 1, $length); + $consumed += $length + 1; + } + + return array($labels, $consumed); + } +} diff --git a/vendor/react/dns/src/Query/CachingExecutor.php b/vendor/react/dns/src/Query/CachingExecutor.php new file mode 100644 index 0000000000..e530b24c4d --- /dev/null +++ b/vendor/react/dns/src/Query/CachingExecutor.php @@ -0,0 +1,88 @@ +executor = $executor; + $this->cache = $cache; + } + + public function query(Query $query) + { + $id = $query->name . ':' . $query->type . ':' . $query->class; + $cache = $this->cache; + $that = $this; + $executor = $this->executor; + + $pending = $cache->get($id); + return new Promise(function ($resolve, $reject) use ($query, $id, $cache, $executor, &$pending, $that) { + $pending->then( + function ($message) use ($query, $id, $cache, $executor, &$pending, $that) { + // return cached response message on cache hit + if ($message !== null) { + return $message; + } + + // perform DNS lookup if not already cached + return $pending = $executor->query($query)->then( + function (Message $message) use ($cache, $id, $that) { + // DNS response message received => store in cache when not truncated and return + if (!$message->tc) { + $cache->set($id, $message, $that->ttl($message)); + } + + return $message; + } + ); + } + )->then($resolve, function ($e) use ($reject, &$pending) { + $reject($e); + $pending = null; + }); + }, function ($_, $reject) use (&$pending, $query) { + $reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled')); + $pending->cancel(); + $pending = null; + }); + } + + /** + * @param Message $message + * @return int + * @internal + */ + public function ttl(Message $message) + { + // select TTL from answers (should all be the same), use smallest value if available + // @link https://tools.ietf.org/html/rfc2181#section-5.2 + $ttl = null; + foreach ($message->answers as $answer) { + if ($ttl === null || $answer->ttl < $ttl) { + $ttl = $answer->ttl; + } + } + + if ($ttl === null) { + $ttl = self::TTL; + } + + return $ttl; + } +} diff --git a/vendor/react/dns/src/Query/CancellationException.php b/vendor/react/dns/src/Query/CancellationException.php new file mode 100644 index 0000000000..5432b36fe7 --- /dev/null +++ b/vendor/react/dns/src/Query/CancellationException.php @@ -0,0 +1,7 @@ +executor = $base; + } + + public function query(Query $query) + { + $key = $this->serializeQueryToIdentity($query); + if (isset($this->pending[$key])) { + // same query is already pending, so use shared reference to pending query + $promise = $this->pending[$key]; + ++$this->counts[$key]; + } else { + // no such query pending, so start new query and keep reference until it's fulfilled or rejected + $promise = $this->executor->query($query); + $this->pending[$key] = $promise; + $this->counts[$key] = 1; + + $pending =& $this->pending; + $counts =& $this->counts; + $promise->then(function () use ($key, &$pending, &$counts) { + unset($pending[$key], $counts[$key]); + }, function () use ($key, &$pending, &$counts) { + unset($pending[$key], $counts[$key]); + }); + } + + // Return a child promise awaiting the pending query. + // Cancelling this child promise should only cancel the pending query + // when no other child promise is awaiting the same query. + $pending =& $this->pending; + $counts =& $this->counts; + return new Promise(function ($resolve, $reject) use ($promise) { + $promise->then($resolve, $reject); + }, function () use (&$promise, $key, $query, &$pending, &$counts) { + if (--$counts[$key] < 1) { + unset($pending[$key], $counts[$key]); + $promise->cancel(); + $promise = null; + } + throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'); + }); + } + + private function serializeQueryToIdentity(Query $query) + { + return sprintf('%s:%s:%s', $query->name, $query->type, $query->class); + } +} diff --git a/vendor/react/dns/src/Query/ExecutorInterface.php b/vendor/react/dns/src/Query/ExecutorInterface.php new file mode 100644 index 0000000000..0bc3945f1b --- /dev/null +++ b/vendor/react/dns/src/Query/ExecutorInterface.php @@ -0,0 +1,43 @@ +query($query)->then( + * function (React\Dns\Model\Message $response) { + * // response message successfully received + * var_dump($response->rcode, $response->answers); + * }, + * function (Exception $error) { + * // failed to query due to $error + * } + * ); + * ``` + * + * The returned Promise MUST be implemented in such a way that it can be + * cancelled when it is still pending. Cancelling a pending promise MUST + * reject its value with an Exception. It SHOULD clean up any underlying + * resources and references as applicable. + * + * ```php + * $promise = $executor->query($query); + * + * $promise->cancel(); + * ``` + * + * @param Query $query + * @return \React\Promise\PromiseInterface<\React\Dns\Model\Message> + * resolves with response message on success or rejects with an Exception on error + */ + public function query(Query $query); +} diff --git a/vendor/react/dns/src/Query/FallbackExecutor.php b/vendor/react/dns/src/Query/FallbackExecutor.php new file mode 100644 index 0000000000..83bd360bd3 --- /dev/null +++ b/vendor/react/dns/src/Query/FallbackExecutor.php @@ -0,0 +1,49 @@ +executor = $executor; + $this->fallback = $fallback; + } + + public function query(Query $query) + { + $cancelled = false; + $fallback = $this->fallback; + $promise = $this->executor->query($query); + + return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) { + $promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) { + // reject if primary resolution rejected due to cancellation + if ($cancelled) { + $reject($e1); + return; + } + + // start fallback query if primary query rejected + $promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) { + $append = $e2->getMessage(); + if (($pos = strpos($append, ':')) !== false) { + $append = substr($append, $pos + 2); + } + + // reject with combined error message if both queries fail + $reject(new \RuntimeException($e1->getMessage() . '. ' . $append)); + }); + }); + }, function () use (&$promise, &$cancelled) { + // cancel pending query (primary or fallback) + $cancelled = true; + $promise->cancel(); + }); + } +} diff --git a/vendor/react/dns/src/Query/HostsFileExecutor.php b/vendor/react/dns/src/Query/HostsFileExecutor.php new file mode 100644 index 0000000000..d6e2d9347f --- /dev/null +++ b/vendor/react/dns/src/Query/HostsFileExecutor.php @@ -0,0 +1,89 @@ +hosts = $hosts; + $this->fallback = $fallback; + } + + public function query(Query $query) + { + if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) { + // forward lookup for type A or AAAA + $records = array(); + $expectsColon = $query->type === Message::TYPE_AAAA; + foreach ($this->hosts->getIpsForHost($query->name) as $ip) { + // ensure this is an IPv4/IPV6 address according to query type + if ((strpos($ip, ':') !== false) === $expectsColon) { + $records[] = new Record($query->name, $query->type, $query->class, 0, $ip); + } + } + + if ($records) { + return Promise\resolve( + Message::createResponseWithAnswersForQuery($query, $records) + ); + } + } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) { + // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain + $ip = $this->getIpFromHost($query->name); + + if ($ip !== null) { + $records = array(); + foreach ($this->hosts->getHostsForIp($ip) as $host) { + $records[] = new Record($query->name, $query->type, $query->class, 0, $host); + } + + if ($records) { + return Promise\resolve( + Message::createResponseWithAnswersForQuery($query, $records) + ); + } + } + } + + return $this->fallback->query($query); + } + + private function getIpFromHost($host) + { + if (substr($host, -13) === '.in-addr.arpa') { + // IPv4: read as IP and reverse bytes + $ip = @inet_pton(substr($host, 0, -13)); + if ($ip === false || isset($ip[4])) { + return null; + } + + return inet_ntop(strrev($ip)); + } elseif (substr($host, -9) === '.ip6.arpa') { + // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string + $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9))))); + if ($ip === false) { + return null; + } + + return $ip; + } else { + return null; + } + } +} diff --git a/vendor/react/dns/src/Query/Query.php b/vendor/react/dns/src/Query/Query.php new file mode 100644 index 0000000000..a3dcfb5828 --- /dev/null +++ b/vendor/react/dns/src/Query/Query.php @@ -0,0 +1,69 @@ +name = $name; + $this->type = $type; + $this->class = $class; + } + + /** + * Describes the hostname and query type/class for this query + * + * The output format is supposed to be human readable and is subject to change. + * The format is inspired by RFC 3597 when handling unkown types/classes. + * + * @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)" + * @link https://tools.ietf.org/html/rfc3597 + */ + public function describe() + { + $class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : ''; + + $type = 'TYPE' . $this->type; + $ref = new \ReflectionClass('React\Dns\Model\Message'); + foreach ($ref->getConstants() as $name => $value) { + if ($value === $this->type && \strpos($name, 'TYPE_') === 0) { + $type = \substr($name, 5); + break; + } + } + + return $this->name . ' (' . $class . $type . ')'; + } +} diff --git a/vendor/react/dns/src/Query/RetryExecutor.php b/vendor/react/dns/src/Query/RetryExecutor.php new file mode 100644 index 0000000000..880609b222 --- /dev/null +++ b/vendor/react/dns/src/Query/RetryExecutor.php @@ -0,0 +1,85 @@ +executor = $executor; + $this->retries = $retries; + } + + public function query(Query $query) + { + return $this->tryQuery($query, $this->retries); + } + + public function tryQuery(Query $query, $retries) + { + $deferred = new Deferred(function () use (&$promise) { + if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { + $promise->cancel(); + } + }); + + $success = function ($value) use ($deferred, &$errorback) { + $errorback = null; + $deferred->resolve($value); + }; + + $executor = $this->executor; + $errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) { + if (!$e instanceof TimeoutException) { + $errorback = null; + $deferred->reject($e); + } elseif ($retries <= 0) { + $errorback = null; + $deferred->reject($e = new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: too many retries', + 0, + $e + )); + + // avoid garbage references by replacing all closures in call stack. + // what a lovely piece of code! + $r = new \ReflectionProperty('Exception', 'trace'); + $r->setAccessible(true); + $trace = $r->getValue($e); + + // Exception trace arguments are not available on some PHP 7.4 installs + // @codeCoverageIgnoreStart + foreach ($trace as $ti => $one) { + if (isset($one['args'])) { + foreach ($one['args'] as $ai => $arg) { + if ($arg instanceof \Closure) { + $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')'; + } + } + } + } + // @codeCoverageIgnoreEnd + $r->setValue($e, $trace); + } else { + --$retries; + $promise = $executor->query($query)->then( + $success, + $errorback + ); + } + }; + + $promise = $this->executor->query($query)->then( + $success, + $errorback + ); + + return $deferred->promise(); + } +} diff --git a/vendor/react/dns/src/Query/SelectiveTransportExecutor.php b/vendor/react/dns/src/Query/SelectiveTransportExecutor.php new file mode 100644 index 0000000000..0f0ca5d083 --- /dev/null +++ b/vendor/react/dns/src/Query/SelectiveTransportExecutor.php @@ -0,0 +1,85 @@ +query( + * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) + * )->then(function (Message $message) { + * foreach ($message->answers as $answer) { + * echo 'IPv6: ' . $answer->data . PHP_EOL; + * } + * }, 'printf'); + * ``` + * + * Note that this executor only implements the logic to select the correct + * transport for the given DNS query. Implementing the correct transport logic, + * implementing timeouts and any retry logic is left up to the given executors, + * see also [`UdpTransportExecutor`](#udptransportexecutor) and + * [`TcpTransportExecutor`](#tcptransportexecutor) for more details. + * + * Note that this executor is entirely async and as such allows you to execute + * any number of queries concurrently. You should probably limit the number of + * concurrent queries in your application or you're very likely going to face + * rate limitations and bans on the resolver end. For many common applications, + * you may want to avoid sending the same query multiple times when the first + * one is still pending, so you will likely want to use this in combination with + * a `CoopExecutor` like this: + * + * ```php + * $executor = new CoopExecutor( + * new SelectiveTransportExecutor( + * $datagramExecutor, + * $streamExecutor + * ) + * ); + * ``` + */ +class SelectiveTransportExecutor implements ExecutorInterface +{ + private $datagramExecutor; + private $streamExecutor; + + public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor) + { + $this->datagramExecutor = $datagramExecutor; + $this->streamExecutor = $streamExecutor; + } + + public function query(Query $query) + { + $stream = $this->streamExecutor; + $pending = $this->datagramExecutor->query($query); + + return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) { + $pending->then( + $resolve, + function ($e) use (&$pending, $stream, $query, $resolve, $reject) { + if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) { + $pending = $stream->query($query)->then($resolve, $reject); + } else { + $reject($e); + } + } + ); + }, function () use (&$pending) { + $pending->cancel(); + $pending = null; + }); + } +} diff --git a/vendor/react/dns/src/Query/TcpTransportExecutor.php b/vendor/react/dns/src/Query/TcpTransportExecutor.php new file mode 100644 index 0000000000..669fd012fd --- /dev/null +++ b/vendor/react/dns/src/Query/TcpTransportExecutor.php @@ -0,0 +1,382 @@ +query( + * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) + * )->then(function (Message $message) { + * foreach ($message->answers as $answer) { + * echo 'IPv6: ' . $answer->data . PHP_EOL; + * } + * }, 'printf'); + * ``` + * + * See also [example #92](examples). + * + * Note that this executor does not implement a timeout, so you will very likely + * want to use this in combination with a `TimeoutExecutor` like this: + * + * ```php + * $executor = new TimeoutExecutor( + * new TcpTransportExecutor($nameserver), + * 3.0 + * ); + * ``` + * + * Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP + * transport, so you do not necessarily have to implement any retry logic. + * + * Note that this executor is entirely async and as such allows you to execute + * queries concurrently. The first query will establish a TCP/IP socket + * connection to the DNS server which will be kept open for a short period. + * Additional queries will automatically reuse this existing socket connection + * to the DNS server, will pipeline multiple requests over this single + * connection and will keep an idle connection open for a short period. The + * initial TCP/IP connection overhead may incur a slight delay if you only send + * occasional queries – when sending a larger number of concurrent queries over + * an existing connection, it becomes increasingly more efficient and avoids + * creating many concurrent sockets like the UDP-based executor. You may still + * want to limit the number of (concurrent) queries in your application or you + * may be facing rate limitations and bans on the resolver end. For many common + * applications, you may want to avoid sending the same query multiple times + * when the first one is still pending, so you will likely want to use this in + * combination with a `CoopExecutor` like this: + * + * ```php + * $executor = new CoopExecutor( + * new TimeoutExecutor( + * new TcpTransportExecutor($nameserver), + * 3.0 + * ) + * ); + * ``` + * + * > Internally, this class uses PHP's TCP/IP sockets and does not take advantage + * of [react/socket](https://github.com/reactphp/socket) purely for + * organizational reasons to avoid a cyclic dependency between the two + * packages. Higher-level components should take advantage of the Socket + * component instead of reimplementing this socket logic from scratch. + */ +class TcpTransportExecutor implements ExecutorInterface +{ + private $nameserver; + private $loop; + private $parser; + private $dumper; + + /** + * @var ?resource + */ + private $socket; + + /** + * @var Deferred[] + */ + private $pending = array(); + + /** + * @var string[] + */ + private $names = array(); + + /** + * Maximum idle time when socket is current unused (i.e. no pending queries outstanding) + * + * If a new query is to be sent during the idle period, we can reuse the + * existing socket without having to wait for a new socket connection. + * This uses a rather small, hard-coded value to not keep any unneeded + * sockets open and to not keep the loop busy longer than needed. + * + * A future implementation may take advantage of `edns-tcp-keepalive` to keep + * the socket open for longer periods. This will likely require explicit + * configuration because this may consume additional resources and also keep + * the loop busy for longer than expected in some applications. + * + * @var float + * @link https://tools.ietf.org/html/rfc7766#section-6.2.1 + * @link https://tools.ietf.org/html/rfc7828 + */ + private $idlePeriod = 0.001; + + /** + * @var ?\React\EventLoop\TimerInterface + */ + private $idleTimer; + + private $writeBuffer = ''; + private $writePending = false; + + private $readBuffer = ''; + private $readPending = false; + + /** @var string */ + private $readChunk = 0xffff; + + /** + * @param string $nameserver + * @param ?LoopInterface $loop + */ + public function __construct($nameserver, $loop = null) + { + if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) { + // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets + $nameserver = '[' . $nameserver . ']'; + } + + $parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver); + if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) { + throw new \InvalidArgumentException('Invalid nameserver address given'); + } + + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + $this->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53); + $this->loop = $loop ?: Loop::get(); + $this->parser = new Parser(); + $this->dumper = new BinaryDumper(); + } + + public function query(Query $query) + { + $request = Message::createRequestForQuery($query); + + // keep shuffing message ID to avoid using the same message ID for two pending queries at the same time + while (isset($this->pending[$request->id])) { + $request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore + } + + $queryData = $this->dumper->toBinary($request); + $length = \strlen($queryData); + if ($length > 0xffff) { + return \React\Promise\reject(new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport' + )); + } + + $queryData = \pack('n', $length) . $queryData; + + if ($this->socket === null) { + // create async TCP/IP connection (may take a while) + $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); + if ($socket === false) { + return \React\Promise\reject(new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', + $errno + )); + } + + // set socket to non-blocking and wait for it to become writable (connection success/rejected) + \stream_set_blocking($socket, false); + if (\function_exists('stream_set_chunk_size')) { + \stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore + } + $this->socket = $socket; + } + + if ($this->idleTimer !== null) { + $this->loop->cancelTimer($this->idleTimer); + $this->idleTimer = null; + } + + // wait for socket to become writable to actually write out data + $this->writeBuffer .= $queryData; + if (!$this->writePending) { + $this->writePending = true; + $this->loop->addWriteStream($this->socket, array($this, 'handleWritable')); + } + + $names =& $this->names; + $that = $this; + $deferred = new Deferred(function () use ($that, &$names, $request) { + // remove from list of pending names, but remember pending query + $name = $names[$request->id]; + unset($names[$request->id]); + $that->checkIdle(); + + throw new CancellationException('DNS query for ' . $name . ' has been cancelled'); + }); + + $this->pending[$request->id] = $deferred; + $this->names[$request->id] = $query->describe(); + + return $deferred->promise(); + } + + /** + * @internal + */ + public function handleWritable() + { + if ($this->readPending === false) { + $name = @\stream_socket_get_name($this->socket, true); + if ($name === false) { + // Connection failed? Check socket error if available for underlying errno/errstr. + // @codeCoverageIgnoreStart + if (\function_exists('socket_import_stream')) { + $socket = \socket_import_stream($this->socket); + $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR); + $errstr = \socket_strerror($errno); + } else { + $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111; + $errstr = 'Connection refused'; + } + // @codeCoverageIgnoreEnd + + $this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno); + return; + } + + $this->readPending = true; + $this->loop->addReadStream($this->socket, array($this, 'handleRead')); + } + + $errno = 0; + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + // Match errstr from PHP's warning message. + // fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe + \preg_match('/errno=(\d+) (.+)/', $error, $m); + $errno = isset($m[1]) ? (int) $m[1] : 0; + $errstr = isset($m[2]) ? $m[2] : $error; + }); + + $written = \fwrite($this->socket, $this->writeBuffer); + + \restore_error_handler(); + + if ($written === false || $written === 0) { + $this->closeError( + 'Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')', + $errno + ); + return; + } + + if (isset($this->writeBuffer[$written])) { + $this->writeBuffer = \substr($this->writeBuffer, $written); + } else { + $this->loop->removeWriteStream($this->socket); + $this->writePending = false; + $this->writeBuffer = ''; + } + } + + /** + * @internal + */ + public function handleRead() + { + // read one chunk of data from the DNS server + // any error is fatal, this is a stream of TCP/IP data + $chunk = @\fread($this->socket, $this->readChunk); + if ($chunk === false || $chunk === '') { + $this->closeError('Connection to DNS server ' . $this->nameserver . ' lost'); + return; + } + + // reassemble complete message by concatenating all chunks. + $this->readBuffer .= $chunk; + + // response message header contains at least 12 bytes + while (isset($this->readBuffer[11])) { + // read response message length from first 2 bytes and ensure we have length + data in buffer + list(, $length) = \unpack('n', $this->readBuffer); + if (!isset($this->readBuffer[$length + 1])) { + return; + } + + $data = \substr($this->readBuffer, 2, $length); + $this->readBuffer = (string)substr($this->readBuffer, $length + 2); + + try { + $response = $this->parser->parseMessage($data); + } catch (\Exception $e) { + // reject all pending queries if we received an invalid message from remote server + $this->closeError('Invalid message received from DNS server ' . $this->nameserver); + return; + } + + // reject all pending queries if we received an unexpected response ID or truncated response + if (!isset($this->pending[$response->id]) || $response->tc) { + $this->closeError('Invalid response message received from DNS server ' . $this->nameserver); + return; + } + + $deferred = $this->pending[$response->id]; + unset($this->pending[$response->id], $this->names[$response->id]); + + $deferred->resolve($response); + + $this->checkIdle(); + } + } + + /** + * @internal + * @param string $reason + * @param int $code + */ + public function closeError($reason, $code = 0) + { + $this->readBuffer = ''; + if ($this->readPending) { + $this->loop->removeReadStream($this->socket); + $this->readPending = false; + } + + $this->writeBuffer = ''; + if ($this->writePending) { + $this->loop->removeWriteStream($this->socket); + $this->writePending = false; + } + + if ($this->idleTimer !== null) { + $this->loop->cancelTimer($this->idleTimer); + $this->idleTimer = null; + } + + @\fclose($this->socket); + $this->socket = null; + + foreach ($this->names as $id => $name) { + $this->pending[$id]->reject(new \RuntimeException( + 'DNS query for ' . $name . ' failed: ' . $reason, + $code + )); + } + $this->pending = $this->names = array(); + } + + /** + * @internal + */ + public function checkIdle() + { + if ($this->idleTimer === null && !$this->names) { + $that = $this; + $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) { + $that->closeError('Idle timeout'); + }); + } + } +} diff --git a/vendor/react/dns/src/Query/TimeoutException.php b/vendor/react/dns/src/Query/TimeoutException.php new file mode 100644 index 0000000000..109b0a9d09 --- /dev/null +++ b/vendor/react/dns/src/Query/TimeoutException.php @@ -0,0 +1,7 @@ +executor = $executor; + $this->loop = $loop ?: Loop::get(); + $this->timeout = $timeout; + } + + public function query(Query $query) + { + $promise = $this->executor->query($query); + + $loop = $this->loop; + $time = $this->timeout; + return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $query) { + $timer = null; + $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) { + if ($timer) { + $loop->cancelTimer($timer); + } + $timer = false; + $resolve($v); + }, function ($v) use (&$timer, $loop, $reject) { + if ($timer) { + $loop->cancelTimer($timer); + } + $timer = false; + $reject($v); + }); + + // promise already resolved => no need to start timer + if ($timer === false) { + return; + } + + // start timeout timer which will cancel the pending promise + $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $query) { + $reject(new TimeoutException( + 'DNS query for ' . $query->describe() . ' timed out' + )); + + // Cancel pending query to clean up any underlying resources and references. + // Avoid garbage references in call stack by passing pending promise by reference. + assert(\method_exists($promise, 'cancel')); + $promise->cancel(); + $promise = null; + }); + }, function () use (&$promise) { + // Cancelling this promise will cancel the pending query, thus triggering the rejection logic above. + // Avoid garbage references in call stack by passing pending promise by reference. + assert(\method_exists($promise, 'cancel')); + $promise->cancel(); + $promise = null; + }); + } +} diff --git a/vendor/react/dns/src/Query/UdpTransportExecutor.php b/vendor/react/dns/src/Query/UdpTransportExecutor.php new file mode 100644 index 0000000000..a8cbfafa20 --- /dev/null +++ b/vendor/react/dns/src/Query/UdpTransportExecutor.php @@ -0,0 +1,221 @@ +query( + * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN) + * )->then(function (Message $message) { + * foreach ($message->answers as $answer) { + * echo 'IPv6: ' . $answer->data . PHP_EOL; + * } + * }, 'printf'); + * ``` + * + * See also the [fourth example](examples). + * + * Note that this executor does not implement a timeout, so you will very likely + * want to use this in combination with a `TimeoutExecutor` like this: + * + * ```php + * $executor = new TimeoutExecutor( + * new UdpTransportExecutor($nameserver), + * 3.0 + * ); + * ``` + * + * Also note that this executor uses an unreliable UDP transport and that it + * does not implement any retry logic, so you will likely want to use this in + * combination with a `RetryExecutor` like this: + * + * ```php + * $executor = new RetryExecutor( + * new TimeoutExecutor( + * new UdpTransportExecutor($nameserver), + * 3.0 + * ) + * ); + * ``` + * + * Note that this executor is entirely async and as such allows you to execute + * any number of queries concurrently. You should probably limit the number of + * concurrent queries in your application or you're very likely going to face + * rate limitations and bans on the resolver end. For many common applications, + * you may want to avoid sending the same query multiple times when the first + * one is still pending, so you will likely want to use this in combination with + * a `CoopExecutor` like this: + * + * ```php + * $executor = new CoopExecutor( + * new RetryExecutor( + * new TimeoutExecutor( + * new UdpTransportExecutor($nameserver), + * 3.0 + * ) + * ) + * ); + * ``` + * + * > Internally, this class uses PHP's UDP sockets and does not take advantage + * of [react/datagram](https://github.com/reactphp/datagram) purely for + * organizational reasons to avoid a cyclic dependency between the two + * packages. Higher-level components should take advantage of the Datagram + * component instead of reimplementing this socket logic from scratch. + */ +final class UdpTransportExecutor implements ExecutorInterface +{ + private $nameserver; + private $loop; + private $parser; + private $dumper; + + /** + * maximum UDP packet size to send and receive + * + * @var int + */ + private $maxPacketSize = 512; + + /** + * @param string $nameserver + * @param ?LoopInterface $loop + */ + public function __construct($nameserver, $loop = null) + { + if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) { + // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets + $nameserver = '[' . $nameserver . ']'; + } + + $parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver); + if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) { + throw new \InvalidArgumentException('Invalid nameserver address given'); + } + + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + $this->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53); + $this->loop = $loop ?: Loop::get(); + $this->parser = new Parser(); + $this->dumper = new BinaryDumper(); + } + + public function query(Query $query) + { + $request = Message::createRequestForQuery($query); + + $queryData = $this->dumper->toBinary($request); + if (isset($queryData[$this->maxPacketSize])) { + return \React\Promise\reject(new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport', + \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90 + )); + } + + // UDP connections are instant, so try connection without a loop or timeout + $errno = 0; + $errstr = ''; + $socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0); + if ($socket === false) { + return \React\Promise\reject(new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', + $errno + )); + } + + // set socket to non-blocking and immediately try to send (fill write buffer) + \stream_set_blocking($socket, false); + + \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + // Write may potentially fail, but most common errors are already caught by connection check above. + // Among others, macOS is known to report here when trying to send to broadcast address. + // This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data. + // fwrite(): send of 8192 bytes failed with errno=111 Connection refused + \preg_match('/errno=(\d+) (.+)/', $error, $m); + $errno = isset($m[1]) ? (int) $m[1] : 0; + $errstr = isset($m[2]) ? $m[2] : $error; + }); + + $written = \fwrite($socket, $queryData); + + \restore_error_handler(); + + if ($written !== \strlen($queryData)) { + return \React\Promise\reject(new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')', + $errno + )); + } + + $loop = $this->loop; + $deferred = new Deferred(function () use ($loop, $socket, $query) { + // cancellation should remove socket from loop and close socket + $loop->removeReadStream($socket); + \fclose($socket); + + throw new CancellationException('DNS query for ' . $query->describe() . ' has been cancelled'); + }); + + $max = $this->maxPacketSize; + $parser = $this->parser; + $nameserver = $this->nameserver; + $loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) { + // try to read a single data packet from the DNS server + // ignoring any errors, this is uses UDP packets and not a stream of data + $data = @\fread($socket, $max); + if ($data === false) { + return; + } + + try { + $response = $parser->parseMessage($data); + } catch (\Exception $e) { + // ignore and await next if we received an invalid message from remote server + // this may as well be a fake response from an attacker (possible DOS) + return; + } + + // ignore and await next if we received an unexpected response ID + // this may as well be a fake response from an attacker (possible cache poisoning) + if ($response->id !== $request->id) { + return; + } + + // we only react to the first valid message, so remove socket from loop and close + $loop->removeReadStream($socket); + \fclose($socket); + + if ($response->tc) { + $deferred->reject(new \RuntimeException( + 'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query', + \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90 + )); + return; + } + + $deferred->resolve($response); + }); + + return $deferred->promise(); + } +} diff --git a/vendor/react/dns/src/RecordNotFoundException.php b/vendor/react/dns/src/RecordNotFoundException.php new file mode 100644 index 0000000000..3b70274281 --- /dev/null +++ b/vendor/react/dns/src/RecordNotFoundException.php @@ -0,0 +1,7 @@ +decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get())); + + return new Resolver($executor); + } + + /** + * Creates a cached DNS resolver instance for the given DNS config and cache + * + * As of v1.7.0 it's recommended to pass a `Config` object instead of a + * single nameserver address. If the given config contains more than one DNS + * nameserver, all DNS nameservers will be used in order. The primary DNS + * server will always be used first before falling back to the secondary or + * tertiary DNS server. + * + * @param Config|string $config DNS Config object (recommended) or single nameserver address + * @param ?LoopInterface $loop + * @param ?CacheInterface $cache + * @return \React\Dns\Resolver\ResolverInterface + * @throws \InvalidArgumentException for invalid DNS server address + * @throws \UnderflowException when given DNS Config object has an empty list of nameservers + */ + public function createCached($config, $loop = null, $cache = null) + { + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + if ($cache !== null && !$cache instanceof CacheInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #3 ($cache) expected null|React\Cache\CacheInterface'); + } + + // default to keeping maximum of 256 responses in cache unless explicitly given + if (!($cache instanceof CacheInterface)) { + $cache = new ArrayCache(256); + } + + $executor = $this->createExecutor($config, $loop ?: Loop::get()); + $executor = new CachingExecutor($executor, $cache); + $executor = $this->decorateHostsFileExecutor($executor); + + return new Resolver($executor); + } + + /** + * Tries to load the hosts file and decorates the given executor on success + * + * @param ExecutorInterface $executor + * @return ExecutorInterface + * @codeCoverageIgnore + */ + private function decorateHostsFileExecutor(ExecutorInterface $executor) + { + try { + $executor = new HostsFileExecutor( + HostsFile::loadFromPathBlocking(), + $executor + ); + } catch (\RuntimeException $e) { + // ignore this file if it can not be loaded + } + + // Windows does not store localhost in hosts file by default but handles this internally + // To compensate for this, we explicitly use hard-coded defaults for localhost + if (DIRECTORY_SEPARATOR === '\\') { + $executor = new HostsFileExecutor( + new HostsFile("127.0.0.1 localhost\n::1 localhost"), + $executor + ); + } + + return $executor; + } + + /** + * @param Config|string $nameserver + * @param LoopInterface $loop + * @return CoopExecutor + * @throws \InvalidArgumentException for invalid DNS server address + * @throws \UnderflowException when given DNS Config object has an empty list of nameservers + */ + private function createExecutor($nameserver, LoopInterface $loop) + { + if ($nameserver instanceof Config) { + if (!$nameserver->nameservers) { + throw new \UnderflowException('Empty config with no DNS servers'); + } + + // Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config). + // Note to future self: Recursion isn't too hard, but how deep do we really want to go? + $primary = reset($nameserver->nameservers); + $secondary = next($nameserver->nameservers); + $tertiary = next($nameserver->nameservers); + + if ($tertiary !== false) { + // 3 DNS servers given => nest first with fallback for second and third + return new CoopExecutor( + new RetryExecutor( + new FallbackExecutor( + $this->createSingleExecutor($primary, $loop), + new FallbackExecutor( + $this->createSingleExecutor($secondary, $loop), + $this->createSingleExecutor($tertiary, $loop) + ) + ) + ) + ); + } elseif ($secondary !== false) { + // 2 DNS servers given => fallback from first to second + return new CoopExecutor( + new RetryExecutor( + new FallbackExecutor( + $this->createSingleExecutor($primary, $loop), + $this->createSingleExecutor($secondary, $loop) + ) + ) + ); + } else { + // 1 DNS server given => use single executor + $nameserver = $primary; + } + } + + return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop))); + } + + /** + * @param string $nameserver + * @param LoopInterface $loop + * @return ExecutorInterface + * @throws \InvalidArgumentException for invalid DNS server address + */ + private function createSingleExecutor($nameserver, LoopInterface $loop) + { + $parts = \parse_url($nameserver); + + if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') { + $executor = $this->createTcpExecutor($nameserver, $loop); + } elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') { + $executor = $this->createUdpExecutor($nameserver, $loop); + } else { + $executor = new SelectiveTransportExecutor( + $this->createUdpExecutor($nameserver, $loop), + $this->createTcpExecutor($nameserver, $loop) + ); + } + + return $executor; + } + + /** + * @param string $nameserver + * @param LoopInterface $loop + * @return TimeoutExecutor + * @throws \InvalidArgumentException for invalid DNS server address + */ + private function createTcpExecutor($nameserver, LoopInterface $loop) + { + return new TimeoutExecutor( + new TcpTransportExecutor($nameserver, $loop), + 5.0, + $loop + ); + } + + /** + * @param string $nameserver + * @param LoopInterface $loop + * @return TimeoutExecutor + * @throws \InvalidArgumentException for invalid DNS server address + */ + private function createUdpExecutor($nameserver, LoopInterface $loop) + { + return new TimeoutExecutor( + new UdpTransportExecutor( + $nameserver, + $loop + ), + 5.0, + $loop + ); + } +} diff --git a/vendor/react/dns/src/Resolver/Resolver.php b/vendor/react/dns/src/Resolver/Resolver.php new file mode 100644 index 0000000000..92926f3f1e --- /dev/null +++ b/vendor/react/dns/src/Resolver/Resolver.php @@ -0,0 +1,147 @@ +executor = $executor; + } + + public function resolve($domain) + { + return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) { + return $ips[array_rand($ips)]; + }); + } + + public function resolveAll($domain, $type) + { + $query = new Query($domain, $type, Message::CLASS_IN); + $that = $this; + + return $this->executor->query( + $query + )->then(function (Message $response) use ($query, $that) { + return $that->extractValues($query, $response); + }); + } + + /** + * [Internal] extract all resource record values from response for this query + * + * @param Query $query + * @param Message $response + * @return array + * @throws RecordNotFoundException when response indicates an error or contains no data + * @internal + */ + public function extractValues(Query $query, Message $response) + { + // reject if response code indicates this is an error response message + $code = $response->rcode; + if ($code !== Message::RCODE_OK) { + switch ($code) { + case Message::RCODE_FORMAT_ERROR: + $message = 'Format Error'; + break; + case Message::RCODE_SERVER_FAILURE: + $message = 'Server Failure'; + break; + case Message::RCODE_NAME_ERROR: + $message = 'Non-Existent Domain / NXDOMAIN'; + break; + case Message::RCODE_NOT_IMPLEMENTED: + $message = 'Not Implemented'; + break; + case Message::RCODE_REFUSED: + $message = 'Refused'; + break; + default: + $message = 'Unknown error response code ' . $code; + } + throw new RecordNotFoundException( + 'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')', + $code + ); + } + + $answers = $response->answers; + $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type); + + // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found) + if (0 === count($addresses)) { + throw new RecordNotFoundException( + 'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)' + ); + } + + return array_values($addresses); + } + + /** + * @param \React\Dns\Model\Record[] $answers + * @param string $name + * @param int $type + * @return array + */ + private function valuesByNameAndType(array $answers, $name, $type) + { + // return all record values for this name and type (if any) + $named = $this->filterByName($answers, $name); + $records = $this->filterByType($named, $type); + if ($records) { + return $this->mapRecordData($records); + } + + // no matching records found? check if there are any matching CNAMEs instead + $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME); + if ($cnameRecords) { + $cnames = $this->mapRecordData($cnameRecords); + foreach ($cnames as $cname) { + $records = array_merge( + $records, + $this->valuesByNameAndType($answers, $cname, $type) + ); + } + } + + return $records; + } + + private function filterByName(array $answers, $name) + { + return $this->filterByField($answers, 'name', $name); + } + + private function filterByType(array $answers, $type) + { + return $this->filterByField($answers, 'type', $type); + } + + private function filterByField(array $answers, $field, $value) + { + $value = strtolower($value); + return array_filter($answers, function ($answer) use ($field, $value) { + return $value === strtolower($answer->$field); + }); + } + + private function mapRecordData(array $records) + { + return array_map(function ($record) { + return $record->data; + }, $records); + } +} diff --git a/vendor/react/dns/src/Resolver/ResolverInterface.php b/vendor/react/dns/src/Resolver/ResolverInterface.php new file mode 100644 index 0000000000..555a1cb136 --- /dev/null +++ b/vendor/react/dns/src/Resolver/ResolverInterface.php @@ -0,0 +1,94 @@ +resolve('reactphp.org')->then(function ($ip) { + * echo 'IP for reactphp.org is ' . $ip . PHP_EOL; + * }); + * ``` + * + * This is one of the main methods in this package. It sends a DNS query + * for the given $domain name to your DNS server and returns a single IP + * address on success. + * + * If the DNS server sends a DNS response message that contains more than + * one IP address for this query, it will randomly pick one of the IP + * addresses from the response. If you want the full list of IP addresses + * or want to send a different type of query, you should use the + * [`resolveAll()`](#resolveall) method instead. + * + * If the DNS server sends a DNS response message that indicates an error + * code, this method will reject with a `RecordNotFoundException`. Its + * message and code can be used to check for the response code. + * + * If the DNS communication fails and the server does not respond with a + * valid response message, this message will reject with an `Exception`. + * + * Pending DNS queries can be cancelled by cancelling its pending promise like so: + * + * ```php + * $promise = $resolver->resolve('reactphp.org'); + * + * $promise->cancel(); + * ``` + * + * @param string $domain + * @return \React\Promise\PromiseInterface + * resolves with a single IP address on success or rejects with an Exception on error. + */ + public function resolve($domain); + + /** + * Resolves all record values for the given $domain name and query $type. + * + * ```php + * $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) { + * echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; + * }); + * + * $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) { + * echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL; + * }); + * ``` + * + * This is one of the main methods in this package. It sends a DNS query + * for the given $domain name to your DNS server and returns a list with all + * record values on success. + * + * If the DNS server sends a DNS response message that contains one or more + * records for this query, it will return a list with all record values + * from the response. You can use the `Message::TYPE_*` constants to control + * which type of query will be sent. Note that this method always returns a + * list of record values, but each record value type depends on the query + * type. For example, it returns the IPv4 addresses for type `A` queries, + * the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`, + * `CNAME` and `PTR` queries and structured data for other queries. See also + * the `Record` documentation for more details. + * + * If the DNS server sends a DNS response message that indicates an error + * code, this method will reject with a `RecordNotFoundException`. Its + * message and code can be used to check for the response code. + * + * If the DNS communication fails and the server does not respond with a + * valid response message, this message will reject with an `Exception`. + * + * Pending DNS queries can be cancelled by cancelling its pending promise like so: + * + * ```php + * $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA); + * + * $promise->cancel(); + * ``` + * + * @param string $domain + * @return \React\Promise\PromiseInterface + * Resolves with all record values on success or rejects with an Exception on error. + */ + public function resolveAll($domain, $type); +} diff --git a/vendor/react/event-loop/CHANGELOG.md b/vendor/react/event-loop/CHANGELOG.md new file mode 100644 index 0000000000..e634b12ea3 --- /dev/null +++ b/vendor/react/event-loop/CHANGELOG.md @@ -0,0 +1,468 @@ +# Changelog + +## 1.5.0 (2023-11-13) + +* Feature: Improve performance by using `spl_object_id()` on PHP 7.2+. + (#267 by @samsonasik) + +* Feature: Full PHP 8.3 compatibility. + (#269 by @clue) + +* Update tests for `ext-uv` on PHP 8+ and legacy PHP. + (#270 by @clue and #268 by @SimonFrings) + +## 1.4.0 (2023-05-05) + +* Feature: Improve performance of `Loop` by avoiding unneeded method calls. + (#266 by @clue) + +* Feature: Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets`. + (#265 by @clue) + +* Minor documentation improvements. + (#254 by @nhedger) + +* Improve test suite, run tests on PHP 8.2 and report failed assertions. + (#258 by @WyriHaximus, #264 by @clue and #251, #261 and #262 by @SimonFrings) + +## 1.3.0 (2022-03-17) + +* Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams. + (#245 by @clue) + +* Feature: Improve performance of `StreamSelectLoop` when no timers are scheduled. + (#246 by @clue) + +* Fix: Fix periodic timer with zero interval for `ExtEvLoop` and legacy `ExtLibevLoop`. + (#243 by @lucasnetau) + +* Minor documentation improvements, update PHP version references. + (#240, #248 and #250 by @SimonFrings, #241 by @dbu and #249 by @clue) + +* Improve test suite and test against PHP 8.1. + (#238 by @WyriHaximus and #242 by @clue) + +## 1.2.0 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Introduce new concept of default loop with the new `Loop` class. + (#226 by @WyriHaximus, #229, #231 and #232 by @clue) + + The `Loop` class exists as a convenient global accessor for the event loop. + It provides all methods that exist on the `LoopInterface` as static methods and + will automatically execute the loop at the end of the program: + + ```php + $timer = Loop::addPeriodicTimer(0.1, function () { + echo 'Tick' . PHP_EOL; + }); + + Loop::addTimer(1.0, function () use ($timer) { + Loop::cancelTimer($timer); + echo 'Done' . PHP_EOL; + }); + ``` + + The explicit loop instructions are still valid and may still be useful in some applications, + especially for a transition period towards the more concise style. + The `Loop::get()` method can be used to get the currently active event loop instance. + + ```php + // deprecated + $loop = React\EventLoop\Factory::create(); + + // new + $loop = React\EventLoop\Loop::get(); + ``` + +* Minor documentation improvements and mark legacy extensions as deprecated. + (#234 by @SimonFrings, #214 by @WyriHaximus and #233 and #235 by @nhedger) + +* Improve test suite, use GitHub actions for continuous integration (CI), + update PHPUnit config and run tests on PHP 8. + (#212 and #215 by @SimonFrings and #230 by @clue) + +## 1.1.1 (2020-01-01) + +* Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows. + (#207 and #208 by @clue) + +* Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows. + (#205 by @clue) + +* Fix: Prevent interval overflow for timers very far in the future with `ExtUvLoop`. + (#196 by @PabloKowalczyk) + +* Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`. + (#195 by @clue) + +* Add `.gitattributes` to exclude dev files from exports. + (#201 by @reedy) + +* Improve test suite to fix testing `ExtUvLoop` on Travis, + fix Travis CI builds, do not install `libuv` on legacy PHP setups, + fix failing test cases due to inaccurate timers, + run tests on Windows via Travis CI and + run tests on PHP 7.4 and simplify test matrix and test setup. + (#197 by @WyriHaximus and #202, #203, #204 and #209 by @clue) + +## 1.1.0 (2019-02-07) + +* New UV based event loop (ext-uv). + (#112 by @WyriHaximus) + +* Use high resolution timer on PHP 7.3+. + (#182 by @clue) + +* Improve PCNTL signals by using async signal dispatching if available. + (#179 by @CharlotteDunois) + +* Improve test suite and test suite set up. + (#174 by @WyriHaximus, #181 by @clue) + +* Fix PCNTL signals edge case. + (#183 by @clue) + +## 1.0.0 (2018-07-11) + +* First stable LTS release, now following [SemVer](https://semver.org/). + We'd like to emphasize that this component is production ready and battle-tested. + We plan to support all long-term support (LTS) releases for at least 24 months, + so you have a rock-solid foundation to build on top of. + +> Contains no other changes, so it's actually fully compatible with the v0.5.3 release. + +## 0.5.3 (2018-07-09) + +* Improve performance by importing global functions. + (#167 by @Ocramius) + +* Improve test suite by simplifying test bootstrap by using dev autoloader. + (#169 by @lcobucci) + +* Minor internal changes to improved backward compatibility with PHP 5.3. + (#166 by @Donatello-za) + +## 0.5.2 (2018-04-24) + +* Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers. + (#164 by @clue) + +* Improve test suite by removing I/O dependency at `StreamSelectLoopTest` to fix Mac OS X tests. + (#161 by @nawarian) + +## 0.5.1 (2018-04-09) + +* Feature: New `ExtEvLoop` (PECL ext-ev) (#148 by @kaduev13) + +## 0.5.0 (2018-04-05) + +A major feature release with a significant documentation overhaul and long overdue API cleanup! + +This update involves a number of BC breaks due to dropped support for deprecated +functionality. We've tried hard to avoid BC breaks where possible and minimize +impact otherwise. We expect that most consumers of this package will actually +not be affected by any BC breaks, see below for more details. + +We realize that the changes listed below may seem overwhelming, but we've tried +to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP +components are already compatible and support both this new release as well as +providing backwards compatibility with the last release. + +* Feature / BC break: Add support for signal handling via new + `LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods. + (#104 by @WyriHaximus and #111 and #150 by @clue) + + ```php + $loop->addSignal(SIGINT, function () { + echo 'CTRL-C'; + }); + ``` + +* Feature: Significant documentation updates for `LoopInterface` and `Factory`. + (#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor) + +* Feature: Add examples to ease getting started + (#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor) + +* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time + and high precision timers with millisecond accuracy or below. + (#130 and #157 by @clue) + +* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners + and stream buffers and allow throwing Exception if stream resource is not supported. + (#129 and #158 by @clue) + +* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed. + (#153 by @WyriHaximus) + +* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM + and remove all `callable` type hints for consistency reasons. + (#141 and #151 by @clue) + +* BC break: Documentation for timer API and clean up unneeded timer API. + (#102 by @clue) + + Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead: + + ```php + // old (method invoked on timer instance) + $timer->cancel(); + + // already supported before: invoke method on loop instance + $loop->cancelTimer($timer); + ``` + + Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`, + use closure binding to add arbitrary data to timer instead: + + ```php + // old (limited setData() and getData() only allows single variable) + $name = 'Tester'; + $timer = $loop->addTimer(1.0, function ($timer) { + echo 'Hello ' . $timer->getData() . PHP_EOL; + }); + $timer->setData($name); + + // already supported before: closure binding allows any number of variables + $name = 'Tester'; + $loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . PHP_EOL; + }); + ``` + + Remove unneeded `TimerInterface::getLoop()`, use closure binding instead: + + ```php + // old (getLoop() called on timer instance) + $loop->addTimer(0.1, function ($timer) { + $timer->getLoop()->stop(); + }); + + // already supported before: use closure binding as usual + $loop->addTimer(0.1, function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::isTimerActive()` and + `TimerInterface::isActive()` to reduce API surface. + (#133 by @clue) + + ```php + // old (method on timer instance or on loop instance) + $timer->isActive(); + $loop->isTimerActive($timer); + ``` + +* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`. + (#138 by @WyriHaximus) + + ```php + // old (notice obsolete "Timer" namespace) + assert($timer instanceof React\EventLoop\Timer\TimerInterface); + + // new + assert($timer instanceof React\EventLoop\TimerInterface); + ``` + +* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`), + use `LoopInterface::futureTick()` instead. + (#30 by @clue) + + ```php + // old (removed) + $loop->nextTick(function () { + echo 'tick'; + }); + + // already supported before + $loop->futureTick(function () { + echo 'tick'; + }); + ``` + +* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()` + (and fix internal cyclic dependency). + (#103 by @clue) + + ```php + // old ($loop gets passed by default) + $loop->futureTick(function ($loop) { + $loop->stop(); + }); + + // already supported before: use closure binding as usual + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::tick()`. + (#72 by @jsor) + + ```php + // old (removed) + $loop->tick(); + + // suggested work around for testing purposes only + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Documentation for advanced stream API and clean up unneeded stream API. + (#110 by @clue) + + Remove unneeded `$loop` argument for `LoopInterface::addReadStream()` + and `LoopInterface::addWriteStream()`, use closure binding instead: + + ```php + // old ($loop gets passed by default) + $loop->addReadStream($stream, function ($stream, $loop) { + $loop->removeReadStream($stream); + }); + + // already supported before: use closure binding as usual + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::removeStream()` method, + use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead. + (#118 by @clue) + + ```php + // old + $loop->removeStream($stream); + + // already supported before + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); + ``` + +* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop` + for consistent naming for event loop implementations. + (#128 by @clue) + +* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop` + and make its `FEATURE_FDS` enabled by default. + (#156 by @WyriHaximus) + +* BC break: Mark all classes as final to discourage inheritance. + (#131 by @clue) + +* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount) + (#123 by @clue) + +* Fix: Ensure large timer interval does not overflow on 32bit systems + (#132 by @clue) + +* Fix: Fix separately removing readable and writable side of stream when closing + (#139 by @clue) + +* Fix: Properly clean up event watchers for `ext-event` and `ext-libev` + (#149 by @clue) + +* Fix: Minor code cleanup and remove unneeded references + (#145 by @seregazhuk) + +* Fix: Discourage outdated `ext-libevent` on PHP 7 + (#62 by @cboden) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5, + lock Travis distro so new defaults will not break the build, + improve test suite to be less fragile and increase test timeouts, + test against PHP 7.2 and reduce fwrite() call length to one chunk. + (#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik) + +* A number of changes were originally planned for this release but have been backported + to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93 + +## 0.4.3 (2017-04-27) + +* Bug fix: Bugfix in the usage sample code #57 (@dandelionred) +* Improvement: Remove branch-alias definition #53 (@WyriHaximus) +* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd) +* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder) +* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley) +* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue) +* Improvement: Travis improvements (backported from #74) #75 (@clue) +* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder) +* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder) +* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek) +* Improvement: Readme cleanup #89 (@jsor) +* Improvement: Restructure and improve README #90 (@jsor) +* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor) + +## 0.4.2 (2016-03-07) + +* Bug fix: No longer error when signals sent to StreamSelectLoop +* Support HHVM and PHP7 (@ondrejmirtes, @cebe) +* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades) +* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino) + +## 0.4.1 (2014-04-13) + +* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue) +* Bug fix: v0.3.4 changes merged for v0.4.1 + +## 0.4.0 (2014-02-02) + +* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc) +* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc) +* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc) +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: New method: `EventLoopInterface::nextTick()` +* BC break: New method: `EventLoopInterface::futureTick()` +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 + +## 0.3.5 (2016-12-28) + +This is a compatibility release that eases upgrading to the v0.4 release branch. +You should consider upgrading to the v0.4 release branch. + +* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4 + (#47 by @clue) + +## 0.3.4 (2014-03-30) + +* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25) + +## 0.3.3 (2013-07-08) + +* Bug fix: No error on removing non-existent streams (@clue) +* Bug fix: Do not silently remove feof listeners in `LibEvLoop` + +## 0.3.0 (2013-04-14) + +* BC break: New timers API (@nrk) +* BC break: Remove check on return value from stream callbacks (@nrk) + +## 0.2.7 (2013-01-05) + +* Bug fix: Fix libevent timers with PHP 5.3 +* Bug fix: Fix libevent timer cancellation (@nrk) + +## 0.2.6 (2012-12-26) + +* Bug fix: Plug memory issue in libevent timers (@cameronjacobson) +* Bug fix: Correctly pause LibEvLoop on stop() + +## 0.2.3 (2012-11-14) + +* Feature: LibEvLoop, integration of `php-libev` + +## 0.2.0 (2012-09-10) + +* Version bump + +## 0.1.1 (2012-07-12) + +* Version bump + +## 0.1.0 (2012-07-11) + +* First tagged release diff --git a/vendor/react/event-loop/LICENSE b/vendor/react/event-loop/LICENSE new file mode 100644 index 0000000000..d6f8901f9a --- /dev/null +++ b/vendor/react/event-loop/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/react/event-loop/README.md b/vendor/react/event-loop/README.md new file mode 100644 index 0000000000..88a3e18b89 --- /dev/null +++ b/vendor/react/event-loop/README.md @@ -0,0 +1,930 @@ +# EventLoop + +[![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/event-loop) + +[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. + +In order for async based libraries to be interoperable, they need to use the +same event loop. This component provides a common `LoopInterface` that any +library can target. This allows them to be used in the same loop, with one +single [`run()`](#run) call that is controlled by the user. + +**Table of contents** + +* [Quickstart example](#quickstart-example) +* [Usage](#usage) + * [Loop](#loop) + * [Loop methods](#loop-methods) + * [Loop autorun](#loop-autorun) + * [get()](#get) + * [~~Factory~~](#factory) + * [~~create()~~](#create) + * [Loop implementations](#loop-implementations) + * [StreamSelectLoop](#streamselectloop) + * [ExtEventLoop](#exteventloop) + * [ExtEvLoop](#extevloop) + * [ExtUvLoop](#extuvloop) + * [~~ExtLibeventLoop~~](#extlibeventloop) + * [~~ExtLibevLoop~~](#extlibevloop) + * [LoopInterface](#loopinterface) + * [run()](#run) + * [stop()](#stop) + * [addTimer()](#addtimer) + * [addPeriodicTimer()](#addperiodictimer) + * [cancelTimer()](#canceltimer) + * [futureTick()](#futuretick) + * [addSignal()](#addsignal) + * [removeSignal()](#removesignal) + * [addReadStream()](#addreadstream) + * [addWriteStream()](#addwritestream) + * [removeReadStream()](#removereadstream) + * [removeWriteStream()](#removewritestream) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [More](#more) + +## Quickstart example + +Here is an async HTTP server built with just the event loop. + +```php +addPeriodicTimer(0.1, function () { + echo 'Tick' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); + +$loop->run(); +``` + +While the former is more concise, the latter is more explicit. +In both cases, the program would perform the exact same steps. + +1. The event loop instance is created at the beginning of the program. This is + implicitly done the first time you call the [`Loop` class](#loop) or + explicitly when using the deprecated [`Factory::create()` method](#create) + (or manually instantiating any of the [loop implementations](#loop-implementations)). +2. The event loop is used directly or passed as an instance to library and + application code. In this example, a periodic timer is registered with the + event loop which simply outputs `Tick` every fraction of a second until another + timer stops the periodic timer after a second. +3. The event loop is run at the end of the program. This is automatically done + when using the [`Loop` class](#loop) or explicitly with a single [`run()`](#run) + call at the end of the program. + +As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop). +The explicit loop instructions are still valid and may still be useful in some +applications, especially for a transition period towards the more concise style. + +### Loop + +The `Loop` class exists as a convenient global accessor for the event loop. + +#### Loop methods + +The `Loop` class provides all methods that exist on the [`LoopInterface`](#loopinterface) +as static methods: + +* [run()](#run) +* [stop()](#stop) +* [addTimer()](#addtimer) +* [addPeriodicTimer()](#addperiodictimer) +* [cancelTimer()](#canceltimer) +* [futureTick()](#futuretick) +* [addSignal()](#addsignal) +* [removeSignal()](#removesignal) +* [addReadStream()](#addreadstream) +* [addWriteStream()](#addwritestream) +* [removeReadStream()](#removereadstream) +* [removeWriteStream()](#removewritestream) + +If you're working with the event loop in your application code, it's often +easiest to directly interface with the static methods defined on the `Loop` class +like this: + +```php +use React\EventLoop\Loop; + +$timer = Loop::addPeriodicTimer(0.1, function () { + echo 'Tick' . PHP_EOL; +}); + +Loop::addTimer(1.0, function () use ($timer) { + Loop::cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); +``` + +On the other hand, if you're familiar with object-oriented programming (OOP) and +dependency injection (DI), you may want to inject an event loop instance and +invoke instance methods on the `LoopInterface` like this: + +```php +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +class Greeter +{ + private $loop; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + } + + public function greet(string $name) + { + $this->loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . '!' . PHP_EOL; + }); + } +} + +$greeter = new Greeter(Loop::get()); +$greeter->greet('Alice'); +$greeter->greet('Bob'); +``` + +Each static method call will be forwarded as-is to the underlying event loop +instance by using the [`Loop::get()`](#get) call internally. +See [`LoopInterface`](#loopinterface) for more details about available methods. + +#### Loop autorun + +When using the `Loop` class, it will automatically execute the loop at the end of +the program. This means the following example will schedule a timer and will +automatically execute the program until the timer event fires: + +```php +use React\EventLoop\Loop; + +Loop::addTimer(1.0, function () { + echo 'Hello' . PHP_EOL; +}); +``` + +As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any +explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run) +method is still valid and may still be useful in some applications, especially +for a transition period towards the more concise style. + +If you don't want the `Loop` to run automatically, you can either explicitly +[`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using +a global exception handler like this: + +```php +use React\EventLoop\Loop; + +Loop::addTimer(10.0, function () { + echo 'Never happens'; +}); + +set_exception_handler(function (Throwable $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + Loop::stop(); +}); + +throw new RuntimeException('Demo'); +``` + +#### get() + +The `get(): LoopInterface` method can be used to +get the currently active event loop instance. + +This method will always return the same event loop instance throughout the +lifetime of your application. + +```php +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +$loop = Loop::get(); + +assert($loop instanceof LoopInterface); +assert($loop === Loop::get()); +``` + +This is particularly useful if you're using object-oriented programming (OOP) +and dependency injection (DI). In this case, you may want to inject an event +loop instance and invoke instance methods on the `LoopInterface` like this: + +```php +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +class Greeter +{ + private $loop; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + } + + public function greet(string $name) + { + $this->loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . '!' . PHP_EOL; + }); + } +} + +$greeter = new Greeter(Loop::get()); +$greeter->greet('Alice'); +$greeter->greet('Bob'); +``` + +See [`LoopInterface`](#loopinterface) for more details about available methods. + +### ~~Factory~~ + +> Deprecated since v1.2.0, see [`Loop` class](#loop) instead. + +The deprecated `Factory` class exists as a convenient way to pick the best available +[event loop implementation](#loop-implementations). + +#### ~~create()~~ + +> Deprecated since v1.2.0, see [`Loop::get()`](#get) instead. + +The deprecated `create(): LoopInterface` method can be used to +create a new event loop instance: + +```php +// deprecated +$loop = React\EventLoop\Factory::create(); + +// new +$loop = React\EventLoop\Loop::get(); +``` + +This method always returns an instance implementing [`LoopInterface`](#loopinterface), +the actual [event loop implementation](#loop-implementations) is an implementation detail. + +This method should usually only be called once at the beginning of the program. + +### Loop implementations + +In addition to the [`LoopInterface`](#loopinterface), there are a number of +event loop implementations provided. + +All of the event loops support these features: + +* File descriptor polling +* One-off timers +* Periodic timers +* Deferred execution on future loop tick + +For most consumers of this package, the underlying event loop implementation is +an implementation detail. +You should use the [`Loop` class](#loop) to automatically create a new instance. + +Advanced! If you explicitly need a certain event loop implementation, you can +manually instantiate one of the following classes. +Note that you may have to install the required PHP extensions for the respective +event loop implementation first or they will throw a `BadMethodCallException` on creation. + +#### StreamSelectLoop + +A `stream_select()` based event loop. + +This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) +function and is the only implementation that works out of the box with PHP. + +This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM. +This means that no installation is required and this library works on all +platforms and supported PHP versions. +Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) +will use this event loop by default if you do not install any of the event loop +extensions listed below. + +Under the hood, it does a simple `select` system call. +This system call is limited to the maximum file descriptor number of +`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)` +(`m` being the maximum file descriptor number passed). +This means that you may run into issues when handling thousands of streams +concurrently and you may want to look into using one of the alternative +event loop implementations listed below in this case. +If your use case is among the many common use cases that involve handling only +dozens or a few hundred streams at once, then this event loop implementation +performs really well. + +If you want to use signal handling (see also [`addSignal()`](#addsignal) below), +this event loop implementation requires `ext-pcntl`. +This extension is only available for Unix-like platforms and does not support +Windows. +It is commonly installed as part of many PHP distributions. +If this extension is missing (or you're running on Windows), signal handling is +not supported and throws a `BadMethodCallException` instead. + +This event loop is known to rely on wall-clock time to schedule future timers +when using any version before PHP 7.3, because a monotonic time source is +only available as of PHP 7.3 (`hrtime()`). +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and +then adjust your system time forward by 20s, the timer may trigger in 10s. +See also [`addTimer()`](#addtimer) for more details. + +#### ExtEventLoop + +An `ext-event` based event loop. + +This uses the [`event` PECL extension](https://pecl.php.net/package/event), +that provides an interface to `libevent` library. +`libevent` itself supports a number of system-specific backends (epoll, kqueue). + +This loop is known to work with PHP 5.4 through PHP 8+. + +#### ExtEvLoop + +An `ext-ev` based event loop. + +This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), +that provides an interface to `libev` library. +`libev` itself supports a number of system-specific backends (epoll, kqueue). + + +This loop is known to work with PHP 5.4 through PHP 8+. + +#### ExtUvLoop + +An `ext-uv` based event loop. + +This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv), +that provides an interface to `libuv` library. +`libuv` itself supports a number of system-specific backends (epoll, kqueue). + +This loop is known to work with PHP 7+. + +#### ~~ExtLibeventLoop~~ + +> Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead. + +An `ext-libevent` based event loop. + +This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent), +that provides an interface to `libevent` library. +`libevent` itself supports a number of system-specific backends (epoll, kqueue). + +This event loop does only work with PHP 5. +An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for +PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s. +To reiterate: Using this event loop on PHP 7 is not recommended. +Accordingly, neither the [`Loop` class](#loop) nor the deprecated +[`Factory` class](#factory) will try to use this event loop on PHP 7. + +This event loop is known to trigger a readable listener only if +the stream *becomes* readable (edge-triggered) and may not trigger if the +stream has already been readable from the beginning. +This also implies that a stream may not be recognized as readable when data +is still left in PHP's internal stream buffers. +As such, it's recommended to use `stream_set_read_buffer($stream, 0);` +to disable PHP's internal read buffer in this case. +See also [`addReadStream()`](#addreadstream) for more details. + +#### ~~ExtLibevLoop~~ + +> Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead. + +An `ext-libev` based event loop. + +This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev), +that provides an interface to `libev` library. +`libev` itself supports a number of system-specific backends (epoll, kqueue). + +This loop does only work with PHP 5. +An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8) +to happen any time soon. + +### LoopInterface + +#### run() + +The `run(): void` method can be used to +run the event loop until there are no more tasks to perform. + +For many applications, this method is the only directly visible +invocation on the event loop. +As a rule of thumb, it is usually recommended to attach everything to the +same loop instance and then run the loop once at the bottom end of the +application. + +```php +$loop->run(); +``` + +This method will keep the loop running until there are no more tasks +to perform. In other words: This method will block until the last +timer, stream and/or signal has been removed. + +Likewise, it is imperative to ensure the application actually invokes +this method once. Adding listeners to the loop and missing to actually +run it will result in the application exiting without actually waiting +for any of the attached listeners. + +This method MUST NOT be called while the loop is already running. +This method MAY be called more than once after it has explicitly been +[`stop()`ped](#stop) or after it automatically stopped because it +previously did no longer have anything to do. + +#### stop() + +The `stop(): void` method can be used to +instruct a running event loop to stop. + +This method is considered advanced usage and should be used with care. +As a rule of thumb, it is usually recommended to let the loop stop +only automatically when it no longer has anything to do. + +This method can be used to explicitly instruct the event loop to stop: + +```php +$loop->addTimer(3.0, function () use ($loop) { + $loop->stop(); +}); +``` + +Calling this method on a loop instance that is not currently running or +on a loop instance that has already been stopped has no effect. + +#### addTimer() + +The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to +enqueue a callback to be invoked once after the given interval. + +The second parameter MUST be a timer callback function that accepts +the timer instance as its only parameter. +If you don't use the timer instance inside your timer callback function +you MAY use a function which has no parameters at all. + +The timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +This method returns a timer instance. The same timer instance will also be +passed into the timer callback function as described above. +You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. +Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure +the callback will be invoked only once after the given interval. + +```php +$loop->addTimer(0.8, function () { + echo 'world!' . PHP_EOL; +}); + +$loop->addTimer(0.3, function () { + echo 'hello '; +}); +``` + +See also [example #1](examples). + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello($name, LoopInterface $loop) +{ + $loop->addTimer(1.0, function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester', $loop); +``` + +This interface does not enforce any particular timer resolution, so +special care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Event loop implementations SHOULD work on +a best effort basis and SHOULD provide at least millisecond accuracy +unless otherwise noted. Many existing event loop implementations are +known to provide microsecond accuracy, but it's generally not recommended +to rely on this high precision. + +Similarly, the execution order of timers scheduled to execute at the +same time (within its possible accuracy) is not guaranteed. + +This interface suggests that event loop implementations SHOULD use a +monotonic time source if available. Given that a monotonic time source is +only available as of PHP 7.3 by default, event loop implementations MAY +fall back to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer SHOULD still trigger in 30s. +See also [event loop implementations](#loop-implementations) for more details. + +#### addPeriodicTimer() + +The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to +enqueue a callback to be invoked repeatedly after the given interval. + +The second parameter MUST be a timer callback function that accepts +the timer instance as its only parameter. +If you don't use the timer instance inside your timer callback function +you MAY use a function which has no parameters at all. + +The timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +This method returns a timer instance. The same timer instance will also be +passed into the timer callback function as described above. +Unlike [`addTimer()`](#addtimer), this method will ensure the callback +will be invoked infinitely after the given interval or until you invoke +[`cancelTimer`](#canceltimer). + +```php +$timer = $loop->addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); +``` + +See also [example #2](examples). + +If you want to limit the number of executions, you can bind +arbitrary data to a callback closure like this: + +```php +function hello($name, LoopInterface $loop) +{ + $n = 3; + $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + if ($n > 0) { + --$n; + echo "hello $name\n"; + } else { + $loop->cancelTimer($timer); + } + }); +} + +hello('Tester', $loop); +``` + +This interface does not enforce any particular timer resolution, so +special care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Event loop implementations SHOULD work on +a best effort basis and SHOULD provide at least millisecond accuracy +unless otherwise noted. Many existing event loop implementations are +known to provide microsecond accuracy, but it's generally not recommended +to rely on this high precision. + +Similarly, the execution order of timers scheduled to execute at the +same time (within its possible accuracy) is not guaranteed. + +This interface suggests that event loop implementations SHOULD use a +monotonic time source if available. Given that a monotonic time source is +only available as of PHP 7.3 by default, event loop implementations MAY +fall back to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer SHOULD still trigger in 30s. +See also [event loop implementations](#loop-implementations) for more details. + +Additionally, periodic timers may be subject to timer drift due to +re-scheduling after each invocation. As such, it's generally not +recommended to rely on this for high precision intervals with millisecond +accuracy or below. + +#### cancelTimer() + +The `cancelTimer(TimerInterface $timer): void` method can be used to +cancel a pending timer. + +See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + +Calling this method on a timer instance that has not been added to this +loop instance or on a timer that has already been cancelled has no effect. + +#### futureTick() + +The `futureTick(callable $listener): void` method can be used to +schedule a callback to be invoked on a future tick of the event loop. + +This works very much similar to timers with an interval of zero seconds, +but does not require the overhead of scheduling a timer queue. + +The tick callback function MUST be able to accept zero parameters. + +The tick callback function MUST NOT throw an `Exception`. +The return value of the tick callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello($name, LoopInterface $loop) +{ + $loop->futureTick(function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester', $loop); +``` + +Unlike timers, tick callbacks are guaranteed to be executed in the order +they are enqueued. +Also, once a callback is enqueued, there's no way to cancel this operation. + +This is often used to break down bigger tasks into smaller steps (a form +of cooperative multitasking). + +```php +$loop->futureTick(function () { + echo 'b'; +}); +$loop->futureTick(function () { + echo 'c'; +}); +echo 'a'; +``` + +See also [example #3](examples). + +#### addSignal() + +The `addSignal(int $signal, callable $listener): void` method can be used to +register a listener to be notified when a signal has been caught by this process. + +This is useful to catch user interrupt signals or shutdown signals from +tools like `supervisor` or `systemd`. + +The second parameter MUST be a listener callback function that accepts +the signal as its only parameter. +If you don't use the signal inside your listener callback function +you MAY use a function which has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +```php +$loop->addSignal(SIGINT, function (int $signal) { + echo 'Caught user interrupt signal' . PHP_EOL; +}); +``` + +See also [example #4](examples). + +Signaling is only available on Unix-like platforms, Windows isn't +supported due to operating system limitations. +This method may throw a `BadMethodCallException` if signals aren't +supported on this platform, for example when required extensions are +missing. + +**Note: A listener can only be added once to the same signal, any +attempts to add it more than once will be ignored.** + +#### removeSignal() + +The `removeSignal(int $signal, callable $listener): void` method can be used to +remove a previously added signal listener. + +```php +$loop->removeSignal(SIGINT, $listener); +``` + +Any attempts to remove listeners that aren't registered will be ignored. + +#### addReadStream() + +> Advanced! Note that this low-level API is considered advanced usage. + Most use cases should probably use the higher-level + [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface) + instead. + +The `addReadStream(resource $stream, callable $callback): void` method can be used to +register a listener to be notified when a stream is ready to read. + +The first parameter MUST be a valid stream resource that supports +checking whether it is ready to read by this loop implementation. +A single stream resource MUST NOT be added more than once. +Instead, either call [`removeReadStream()`](#removereadstream) first or +react to this event with a single listener and then dispatch from this +listener. This method MAY throw an `Exception` if the given resource type +is not supported by this loop implementation. + +The second parameter MUST be a listener callback function that accepts +the stream resource as its only parameter. +If you don't use the stream resource inside your listener callback function +you MAY use a function which has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +$loop->addReadStream($stream, function ($stream) use ($name) { + echo $name . ' said: ' . fread($stream); +}); +``` + +See also [example #11](examples). + +You can invoke [`removeReadStream()`](#removereadstream) to remove the +read event listener for this stream. + +The execution order of listeners when multiple streams become ready at +the same time is not guaranteed. + +Some event loop implementations are known to only trigger the listener if +the stream *becomes* readable (edge-triggered) and may not trigger if the +stream has already been readable from the beginning. +This also implies that a stream may not be recognized as readable when data +is still left in PHP's internal stream buffers. +As such, it's recommended to use `stream_set_read_buffer($stream, 0);` +to disable PHP's internal read buffer in this case. + +#### addWriteStream() + +> Advanced! Note that this low-level API is considered advanced usage. + Most use cases should probably use the higher-level + [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) + instead. + +The `addWriteStream(resource $stream, callable $callback): void` method can be used to +register a listener to be notified when a stream is ready to write. + +The first parameter MUST be a valid stream resource that supports +checking whether it is ready to write by this loop implementation. +A single stream resource MUST NOT be added more than once. +Instead, either call [`removeWriteStream()`](#removewritestream) first or +react to this event with a single listener and then dispatch from this +listener. This method MAY throw an `Exception` if the given resource type +is not supported by this loop implementation. + +The second parameter MUST be a listener callback function that accepts +the stream resource as its only parameter. +If you don't use the stream resource inside your listener callback function +you MAY use a function which has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +$loop->addWriteStream($stream, function ($stream) use ($name) { + fwrite($stream, 'Hello ' . $name); +}); +``` + +See also [example #12](examples). + +You can invoke [`removeWriteStream()`](#removewritestream) to remove the +write event listener for this stream. + +The execution order of listeners when multiple streams become ready at +the same time is not guaranteed. + +#### removeReadStream() + +The `removeReadStream(resource $stream): void` method can be used to +remove the read event listener for the given stream. + +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + +#### removeWriteStream() + +The `removeWriteStream(resource $stream): void` method can be used to +remove the write event listener for the given stream. + +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require react/event-loop:^1.5 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and +HHVM. +It's *highly recommended to use the latest supported PHP version* for this project. + +Installing any of the event loop extensions is suggested, but entirely optional. +See also [event loop implementations](#loop-implementations) for more details. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## More + +* See our [Stream component](https://github.com/reactphp/stream) for more + information on how streams are used in real-world applications. +* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the + [dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents) + for a list of packages that use the EventLoop in real-world applications. diff --git a/vendor/react/event-loop/composer.json b/vendor/react/event-loop/composer.json new file mode 100644 index 0000000000..25a41fe171 --- /dev/null +++ b/vendor/react/event-loop/composer.json @@ -0,0 +1,47 @@ +{ + "name": "react/event-loop", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": ["event-loop", "asynchronous"], + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\EventLoop\\": "tests/" + } + } +} diff --git a/vendor/react/event-loop/src/ExtEvLoop.php b/vendor/react/event-loop/src/ExtEvLoop.php new file mode 100644 index 0000000000..a3fcec6823 --- /dev/null +++ b/vendor/react/event-loop/src/ExtEvLoop.php @@ -0,0 +1,253 @@ +loop = new EvLoop(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timers = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->readStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::READ, $callback); + $this->readStreams[$key] = $event; + } + + /** + * @param resource $stream + * @param callable $listener + * + * @return \Closure + */ + private function getStreamListenerClosure($stream, $listener) + { + return function () use ($stream, $listener) { + \call_user_func($listener, $stream); + }; + } + + public function addWriteStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->writeStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::WRITE, $callback); + $this->writeStreams[$key] = $event; + } + + public function removeReadStream($stream) + { + $key = (int)$stream; + + if (!isset($this->readStreams[$key])) { + return; + } + + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); + } + + public function removeWriteStream($stream) + { + $key = (int)$stream; + + if (!isset($this->writeStreams[$key])) { + return; + } + + $this->writeStreams[$key]->stop(); + unset($this->writeStreams[$key]); + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $that = $this; + $timers = $this->timers; + $callback = function () use ($timer, $timers, $that) { + \call_user_func($timer->getCallback(), $timer); + + if ($timers->contains($timer)) { + $that->cancelTimer($timer); + } + }; + + $event = $this->loop->timer($timer->getInterval(), 0.0, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + \call_user_func($timer->getCallback(), $timer); + }; + + $event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (!isset($this->timers[$timer])) { + return; + } + + $event = $this->timers[$timer]; + $event->stop(); + $this->timers->detach($timer); + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + $wasJustStopped = !$this->running; + $nothingLeftToDo = !$this->readStreams + && !$this->writeStreams + && !$this->timers->count() + && $this->signals->isEmpty(); + + $flags = Ev::RUN_ONCE; + if ($wasJustStopped || $hasPendingCallbacks) { + $flags |= Ev::RUN_NOWAIT; + } elseif ($nothingLeftToDo) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } + + public function __destruct() + { + /** @var TimerInterface $timer */ + foreach ($this->timers as $timer) { + $this->cancelTimer($timer); + } + + foreach ($this->readStreams as $key => $stream) { + $this->removeReadStream($key); + } + + foreach ($this->writeStreams as $key => $stream) { + $this->removeWriteStream($key); + } + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) { + $this->signals->call($signal); + }); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal]->stop(); + unset($this->signalEvents[$signal]); + } + } +} diff --git a/vendor/react/event-loop/src/ExtEventLoop.php b/vendor/react/event-loop/src/ExtEventLoop.php new file mode 100644 index 0000000000..b162a4029c --- /dev/null +++ b/vendor/react/event-loop/src/ExtEventLoop.php @@ -0,0 +1,275 @@ +requireFeatures(\EventConfig::FEATURE_FDS); + } + + $this->eventBase = new EventBase($config); + $this->futureTickQueue = new FutureTickQueue(); + $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + + $this->createTimerCallback(); + $this->createStreamCallback(); + } + + public function __destruct() + { + // explicitly clear all references to Event objects to prevent SEGFAULTs on Windows + foreach ($this->timerEvents as $timer) { + $this->timerEvents->detach($timer); + } + + $this->readEvents = array(); + $this->writeEvents = array(); + } + + public function addReadStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->readListeners[$key])) { + return; + } + + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback); + $event->add(); + $this->readEvents[$key] = $event; + $this->readListeners[$key] = $listener; + + // ext-event does not increase refcount on stream resources for PHP 7+ + // manually keep track of stream resource to prevent premature garbage collection + if (\PHP_VERSION_ID >= 70000) { + $this->readRefs[$key] = $stream; + } + } + + public function addWriteStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->writeListeners[$key])) { + return; + } + + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback); + $event->add(); + $this->writeEvents[$key] = $event; + $this->writeListeners[$key] = $listener; + + // ext-event does not increase refcount on stream resources for PHP 7+ + // manually keep track of stream resource to prevent premature garbage collection + if (\PHP_VERSION_ID >= 70000) { + $this->writeRefs[$key] = $stream; + } + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (isset($this->readEvents[$key])) { + $this->readEvents[$key]->free(); + unset( + $this->readEvents[$key], + $this->readListeners[$key], + $this->readRefs[$key] + ); + } + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (isset($this->writeEvents[$key])) { + $this->writeEvents[$key]->free(); + unset( + $this->writeEvents[$key], + $this->writeListeners[$key], + $this->writeRefs[$key] + ); + } + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if ($this->timerEvents->contains($timer)) { + $this->timerEvents[$timer]->free(); + $this->timerEvents->detach($timer); + } + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call')); + $this->signalEvents[$signal]->add(); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->free(); + unset($this->signalEvents[$signal]); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $flags = EventBase::LOOP_ONCE; + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $flags |= EventBase::LOOP_NONBLOCK; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { + break; + } + + $this->eventBase->loop($flags); + } + } + + public function stop() + { + $this->running = false; + } + + /** + * Schedule a timer for execution. + * + * @param TimerInterface $timer + */ + private function scheduleTimer(TimerInterface $timer) + { + $flags = Event::TIMEOUT; + + if ($timer->isPeriodic()) { + $flags |= Event::PERSIST; + } + + $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer); + $this->timerEvents[$timer] = $event; + + $event->add($timer->getInterval()); + } + + /** + * Create a callback used as the target of timer events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createTimerCallback() + { + $timers = $this->timerEvents; + $this->timerCallback = function ($_, $__, $timer) use ($timers) { + \call_user_func($timer->getCallback(), $timer); + + if (!$timer->isPeriodic() && $timers->contains($timer)) { + $this->cancelTimer($timer); + } + }; + } + + /** + * Create a callback used as the target of stream events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createStreamCallback() + { + $read =& $this->readListeners; + $write =& $this->writeListeners; + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { + $key = (int) $stream; + + if (Event::READ === (Event::READ & $flags) && isset($read[$key])) { + \call_user_func($read[$key], $stream); + } + + if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) { + \call_user_func($write[$key], $stream); + } + }; + } +} diff --git a/vendor/react/event-loop/src/ExtLibevLoop.php b/vendor/react/event-loop/src/ExtLibevLoop.php new file mode 100644 index 0000000000..c303fdd5c6 --- /dev/null +++ b/vendor/react/event-loop/src/ExtLibevLoop.php @@ -0,0 +1,201 @@ +loop = new EventLoop(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + if (isset($this->readEvents[(int) $stream])) { + return; + } + + $callback = function () use ($stream, $listener) { + \call_user_func($listener, $stream); + }; + + $event = new IOEvent($callback, $stream, IOEvent::READ); + $this->loop->add($event); + + $this->readEvents[(int) $stream] = $event; + } + + public function addWriteStream($stream, $listener) + { + if (isset($this->writeEvents[(int) $stream])) { + return; + } + + $callback = function () use ($stream, $listener) { + \call_user_func($listener, $stream); + }; + + $event = new IOEvent($callback, $stream, IOEvent::WRITE); + $this->loop->add($event); + + $this->writeEvents[(int) $stream] = $event; + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (isset($this->readEvents[$key])) { + $this->readEvents[$key]->stop(); + $this->loop->remove($this->readEvents[$key]); + unset($this->readEvents[$key]); + } + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (isset($this->writeEvents[$key])) { + $this->writeEvents[$key]->stop(); + $this->loop->remove($this->writeEvents[$key]); + unset($this->writeEvents[$key]); + } + } + + public function addTimer($interval, $callback) + { + $timer = new Timer( $interval, $callback, false); + + $that = $this; + $timers = $this->timerEvents; + $callback = function () use ($timer, $timers, $that) { + \call_user_func($timer->getCallback(), $timer); + + if ($timers->contains($timer)) { + $that->cancelTimer($timer); + } + }; + + $event = new TimerEvent($callback, $timer->getInterval()); + $this->timerEvents->attach($timer, $event); + $this->loop->add($event); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + \call_user_func($timer->getCallback(), $timer); + }; + + $event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval()); + $this->timerEvents->attach($timer, $event); + $this->loop->add($event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (isset($this->timerEvents[$timer])) { + $this->loop->remove($this->timerEvents[$timer]); + $this->timerEvents->detach($timer); + } + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $signals = $this->signals; + $this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) { + $signals->call($signal); + }, $signal); + $this->loop->add($this->signalEvents[$signal]); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->stop(); + $this->loop->remove($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $flags = EventLoop::RUN_ONCE; + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $flags |= EventLoop::RUN_NOWAIT; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } +} diff --git a/vendor/react/event-loop/src/ExtLibeventLoop.php b/vendor/react/event-loop/src/ExtLibeventLoop.php new file mode 100644 index 0000000000..099293a4fe --- /dev/null +++ b/vendor/react/event-loop/src/ExtLibeventLoop.php @@ -0,0 +1,285 @@ +eventBase = \event_base_new(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + + $this->createTimerCallback(); + $this->createStreamCallback(); + } + + public function addReadStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->readListeners[$key])) { + return; + } + + $event = \event_new(); + \event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback); + \event_base_set($event, $this->eventBase); + \event_add($event); + + $this->readEvents[$key] = $event; + $this->readListeners[$key] = $listener; + } + + public function addWriteStream($stream, $listener) + { + $key = (int) $stream; + if (isset($this->writeListeners[$key])) { + return; + } + + $event = \event_new(); + \event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback); + \event_base_set($event, $this->eventBase); + \event_add($event); + + $this->writeEvents[$key] = $event; + $this->writeListeners[$key] = $listener; + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (isset($this->readListeners[$key])) { + $event = $this->readEvents[$key]; + \event_del($event); + \event_free($event); + + unset( + $this->readEvents[$key], + $this->readListeners[$key] + ); + } + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (isset($this->writeListeners[$key])) { + $event = $this->writeEvents[$key]; + \event_del($event); + \event_free($event); + + unset( + $this->writeEvents[$key], + $this->writeListeners[$key] + ); + } + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $this->scheduleTimer($timer); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if ($this->timerEvents->contains($timer)) { + $event = $this->timerEvents[$timer]; + \event_del($event); + \event_free($event); + + $this->timerEvents->detach($timer); + } + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = \event_new(); + \event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call')); + \event_base_set($this->signalEvents[$signal], $this->eventBase); + \event_add($this->signalEvents[$signal]); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + \event_del($this->signalEvents[$signal]); + \event_free($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $flags = \EVLOOP_ONCE; + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $flags |= \EVLOOP_NONBLOCK; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { + break; + } + + \event_base_loop($this->eventBase, $flags); + } + } + + public function stop() + { + $this->running = false; + } + + /** + * Schedule a timer for execution. + * + * @param TimerInterface $timer + */ + private function scheduleTimer(TimerInterface $timer) + { + $this->timerEvents[$timer] = $event = \event_timer_new(); + + \event_timer_set($event, $this->timerCallback, $timer); + \event_base_set($event, $this->eventBase); + \event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND); + } + + /** + * Create a callback used as the target of timer events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createTimerCallback() + { + $that = $this; + $timers = $this->timerEvents; + $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) { + \call_user_func($timer->getCallback(), $timer); + + // Timer already cancelled ... + if (!$timers->contains($timer)) { + return; + } + + // Reschedule periodic timers ... + if ($timer->isPeriodic()) { + \event_add( + $timers[$timer], + $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND + ); + + // Clean-up one shot timers ... + } else { + $that->cancelTimer($timer); + } + }; + } + + /** + * Create a callback used as the target of stream events. + * + * A reference is kept to the callback for the lifetime of the loop + * to prevent "Cannot destroy active lambda function" fatal error from + * the event extension. + */ + private function createStreamCallback() + { + $read =& $this->readListeners; + $write =& $this->writeListeners; + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { + $key = (int) $stream; + + if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) { + \call_user_func($read[$key], $stream); + } + + if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) { + \call_user_func($write[$key], $stream); + } + }; + } +} diff --git a/vendor/react/event-loop/src/ExtUvLoop.php b/vendor/react/event-loop/src/ExtUvLoop.php new file mode 100644 index 0000000000..4434720df6 --- /dev/null +++ b/vendor/react/event-loop/src/ExtUvLoop.php @@ -0,0 +1,342 @@ +uv = \uv_loop_new(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timers = new SplObjectStorage(); + $this->streamListener = $this->createStreamListener(); + $this->signals = new SignalsHandler(); + } + + /** + * Returns the underlying ext-uv event loop. (Internal ReactPHP use only.) + * + * @internal + * + * @return resource + */ + public function getUvLoop() + { + return $this->uv; + } + + /** + * {@inheritdoc} + */ + public function addReadStream($stream, $listener) + { + if (isset($this->readStreams[(int) $stream])) { + return; + } + + $this->readStreams[(int) $stream] = $listener; + $this->addStream($stream); + } + + /** + * {@inheritdoc} + */ + public function addWriteStream($stream, $listener) + { + if (isset($this->writeStreams[(int) $stream])) { + return; + } + + $this->writeStreams[(int) $stream] = $listener; + $this->addStream($stream); + } + + /** + * {@inheritdoc} + */ + public function removeReadStream($stream) + { + if (!isset($this->streamEvents[(int) $stream])) { + return; + } + + unset($this->readStreams[(int) $stream]); + $this->removeStream($stream); + } + + /** + * {@inheritdoc} + */ + public function removeWriteStream($stream) + { + if (!isset($this->streamEvents[(int) $stream])) { + return; + } + + unset($this->writeStreams[(int) $stream]); + $this->removeStream($stream); + } + + /** + * {@inheritdoc} + */ + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $that = $this; + $timers = $this->timers; + $callback = function () use ($timer, $timers, $that) { + \call_user_func($timer->getCallback(), $timer); + + if ($timers->contains($timer)) { + $that->cancelTimer($timer); + } + }; + + $event = \uv_timer_init($this->uv); + $this->timers->attach($timer, $event); + \uv_timer_start( + $event, + $this->convertFloatSecondsToMilliseconds($interval), + 0, + $callback + ); + + return $timer; + } + + /** + * {@inheritdoc} + */ + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + \call_user_func($timer->getCallback(), $timer); + }; + + $interval = $this->convertFloatSecondsToMilliseconds($interval); + $event = \uv_timer_init($this->uv); + $this->timers->attach($timer, $event); + \uv_timer_start( + $event, + $interval, + (int) $interval === 0 ? 1 : $interval, + $callback + ); + + return $timer; + } + + /** + * {@inheritdoc} + */ + public function cancelTimer(TimerInterface $timer) + { + if (isset($this->timers[$timer])) { + @\uv_timer_stop($this->timers[$timer]); + $this->timers->detach($timer); + } + } + + /** + * {@inheritdoc} + */ + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $signals = $this->signals; + $this->signalEvents[$signal] = \uv_signal_init($this->uv); + \uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) { + $signals->call($signal); + }, $signal); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + \uv_signal_stop($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + $wasJustStopped = !$this->running; + $nothingLeftToDo = !$this->readStreams + && !$this->writeStreams + && !$this->timers->count() + && $this->signals->isEmpty(); + + // Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers, + // otherwise use UV::RUN_NOWAIT. + // @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run + $flags = \UV::RUN_ONCE; + if ($wasJustStopped || $hasPendingCallbacks) { + $flags = \UV::RUN_NOWAIT; + } elseif ($nothingLeftToDo) { + break; + } + + \uv_run($this->uv, $flags); + } + } + + /** + * {@inheritdoc} + */ + public function stop() + { + $this->running = false; + } + + private function addStream($stream) + { + if (!isset($this->streamEvents[(int) $stream])) { + $this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream); + } + + if ($this->streamEvents[(int) $stream] !== false) { + $this->pollStream($stream); + } + } + + private function removeStream($stream) + { + if (!isset($this->streamEvents[(int) $stream])) { + return; + } + + if (!isset($this->readStreams[(int) $stream]) + && !isset($this->writeStreams[(int) $stream])) { + \uv_poll_stop($this->streamEvents[(int) $stream]); + \uv_close($this->streamEvents[(int) $stream]); + unset($this->streamEvents[(int) $stream]); + return; + } + + $this->pollStream($stream); + } + + private function pollStream($stream) + { + if (!isset($this->streamEvents[(int) $stream])) { + return; + } + + $flags = 0; + if (isset($this->readStreams[(int) $stream])) { + $flags |= \UV::READABLE; + } + + if (isset($this->writeStreams[(int) $stream])) { + $flags |= \UV::WRITABLE; + } + + \uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener); + } + + /** + * Create a stream listener + * + * @return callable Returns a callback + */ + private function createStreamListener() + { + $callback = function ($event, $status, $events, $stream) { + // libuv automatically stops polling on error, re-enable polling to match other loop implementations + if ($status !== 0) { + $this->pollStream($stream); + + // libuv may report no events on error, but this should still invoke stream listeners to report closed connections + // re-enable both readable and writable, correct listeners will be checked below anyway + if ($events === 0) { + $events = \UV::READABLE | \UV::WRITABLE; + } + } + + if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) { + \call_user_func($this->readStreams[(int) $stream], $stream); + } + + if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) { + \call_user_func($this->writeStreams[(int) $stream], $stream); + } + }; + + return $callback; + } + + /** + * @param float $interval + * @return int + */ + private function convertFloatSecondsToMilliseconds($interval) + { + if ($interval < 0) { + return 0; + } + + $maxValue = (int) (\PHP_INT_MAX / 1000); + $intInterval = (int) $interval; + + if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) { + throw new \InvalidArgumentException( + "Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed." + ); + } + + return (int) \floor($interval * 1000); + } +} diff --git a/vendor/react/event-loop/src/Factory.php b/vendor/react/event-loop/src/Factory.php new file mode 100644 index 0000000000..30bbfd7c83 --- /dev/null +++ b/vendor/react/event-loop/src/Factory.php @@ -0,0 +1,75 @@ +futureTick(function () use (&$hasRun) { + $hasRun = true; + }); + + $stopped =& self::$stopped; + register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) { + // Don't run if we're coming from a fatal error (uncaught exception). + $error = error_get_last(); + if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) { + return; + } + + if (!$hasRun && !$stopped) { + $loop->run(); + } + }); + // @codeCoverageIgnoreEnd + + return self::$instance; + } + + /** + * Internal undocumented method, behavior might change or throw in the + * future. Use with caution and at your own risk. + * + * @internal + * @return void + */ + public static function set(LoopInterface $loop) + { + self::$instance = $loop; + } + + /** + * [Advanced] Register a listener to be notified when a stream is ready to read. + * + * @param resource $stream + * @param callable $listener + * @return void + * @throws \Exception + * @see LoopInterface::addReadStream() + */ + public static function addReadStream($stream, $listener) + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + self::$instance->addReadStream($stream, $listener); + } + + /** + * [Advanced] Register a listener to be notified when a stream is ready to write. + * + * @param resource $stream + * @param callable $listener + * @return void + * @throws \Exception + * @see LoopInterface::addWriteStream() + */ + public static function addWriteStream($stream, $listener) + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + self::$instance->addWriteStream($stream, $listener); + } + + /** + * Remove the read event listener for the given stream. + * + * @param resource $stream + * @return void + * @see LoopInterface::removeReadStream() + */ + public static function removeReadStream($stream) + { + if (self::$instance !== null) { + self::$instance->removeReadStream($stream); + } + } + + /** + * Remove the write event listener for the given stream. + * + * @param resource $stream + * @return void + * @see LoopInterface::removeWriteStream() + */ + public static function removeWriteStream($stream) + { + if (self::$instance !== null) { + self::$instance->removeWriteStream($stream); + } + } + + /** + * Enqueue a callback to be invoked once after the given interval. + * + * @param float $interval + * @param callable $callback + * @return TimerInterface + * @see LoopInterface::addTimer() + */ + public static function addTimer($interval, $callback) + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + return self::$instance->addTimer($interval, $callback); + } + + /** + * Enqueue a callback to be invoked repeatedly after the given interval. + * + * @param float $interval + * @param callable $callback + * @return TimerInterface + * @see LoopInterface::addPeriodicTimer() + */ + public static function addPeriodicTimer($interval, $callback) + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + return self::$instance->addPeriodicTimer($interval, $callback); + } + + /** + * Cancel a pending timer. + * + * @param TimerInterface $timer + * @return void + * @see LoopInterface::cancelTimer() + */ + public static function cancelTimer(TimerInterface $timer) + { + if (self::$instance !== null) { + self::$instance->cancelTimer($timer); + } + } + + /** + * Schedule a callback to be invoked on a future tick of the event loop. + * + * @param callable $listener + * @return void + * @see LoopInterface::futureTick() + */ + public static function futureTick($listener) + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + + self::$instance->futureTick($listener); + } + + /** + * Register a listener to be notified when a signal has been caught by this process. + * + * @param int $signal + * @param callable $listener + * @return void + * @see LoopInterface::addSignal() + */ + public static function addSignal($signal, $listener) + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + + self::$instance->addSignal($signal, $listener); + } + + /** + * Removes a previously added signal listener. + * + * @param int $signal + * @param callable $listener + * @return void + * @see LoopInterface::removeSignal() + */ + public static function removeSignal($signal, $listener) + { + if (self::$instance !== null) { + self::$instance->removeSignal($signal, $listener); + } + } + + /** + * Run the event loop until there are no more tasks to perform. + * + * @return void + * @see LoopInterface::run() + */ + public static function run() + { + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + + self::$instance->run(); + } + + /** + * Instruct a running event loop to stop. + * + * @return void + * @see LoopInterface::stop() + */ + public static function stop() + { + self::$stopped = true; + if (self::$instance !== null) { + self::$instance->stop(); + } + } +} diff --git a/vendor/react/event-loop/src/LoopInterface.php b/vendor/react/event-loop/src/LoopInterface.php new file mode 100644 index 0000000000..9266f71886 --- /dev/null +++ b/vendor/react/event-loop/src/LoopInterface.php @@ -0,0 +1,472 @@ +addReadStream($stream, function ($stream) use ($name) { + * echo $name . ' said: ' . fread($stream); + * }); + * ``` + * + * See also [example #11](examples). + * + * You can invoke [`removeReadStream()`](#removereadstream) to remove the + * read event listener for this stream. + * + * The execution order of listeners when multiple streams become ready at + * the same time is not guaranteed. + * + * @param resource $stream The PHP stream resource to check. + * @param callable $listener Invoked when the stream is ready. + * @throws \Exception if the given resource type is not supported by this loop implementation + * @see self::removeReadStream() + */ + public function addReadStream($stream, $listener); + + /** + * [Advanced] Register a listener to be notified when a stream is ready to write. + * + * Note that this low-level API is considered advanced usage. + * Most use cases should probably use the higher-level + * [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) + * instead. + * + * The first parameter MUST be a valid stream resource that supports + * checking whether it is ready to write by this loop implementation. + * A single stream resource MUST NOT be added more than once. + * Instead, either call [`removeWriteStream()`](#removewritestream) first or + * react to this event with a single listener and then dispatch from this + * listener. This method MAY throw an `Exception` if the given resource type + * is not supported by this loop implementation. + * + * The second parameter MUST be a listener callback function that accepts + * the stream resource as its only parameter. + * If you don't use the stream resource inside your listener callback function + * you MAY use a function which has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * $loop->addWriteStream($stream, function ($stream) use ($name) { + * fwrite($stream, 'Hello ' . $name); + * }); + * ``` + * + * See also [example #12](examples). + * + * You can invoke [`removeWriteStream()`](#removewritestream) to remove the + * write event listener for this stream. + * + * The execution order of listeners when multiple streams become ready at + * the same time is not guaranteed. + * + * Some event loop implementations are known to only trigger the listener if + * the stream *becomes* readable (edge-triggered) and may not trigger if the + * stream has already been readable from the beginning. + * This also implies that a stream may not be recognized as readable when data + * is still left in PHP's internal stream buffers. + * As such, it's recommended to use `stream_set_read_buffer($stream, 0);` + * to disable PHP's internal read buffer in this case. + * + * @param resource $stream The PHP stream resource to check. + * @param callable $listener Invoked when the stream is ready. + * @throws \Exception if the given resource type is not supported by this loop implementation + * @see self::removeWriteStream() + */ + public function addWriteStream($stream, $listener); + + /** + * Remove the read event listener for the given stream. + * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * + * @param resource $stream The PHP stream resource. + */ + public function removeReadStream($stream); + + /** + * Remove the write event listener for the given stream. + * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * + * @param resource $stream The PHP stream resource. + */ + public function removeWriteStream($stream); + + /** + * Enqueue a callback to be invoked once after the given interval. + * + * The second parameter MUST be a timer callback function that accepts + * the timer instance as its only parameter. + * If you don't use the timer instance inside your timer callback function + * you MAY use a function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * This method returns a timer instance. The same timer instance will also be + * passed into the timer callback function as described above. + * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure + * the callback will be invoked only once after the given interval. + * + * ```php + * $loop->addTimer(0.8, function () { + * echo 'world!' . PHP_EOL; + * }); + * + * $loop->addTimer(0.3, function () { + * echo 'hello '; + * }); + * ``` + * + * See also [example #1](examples). + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello($name, LoopInterface $loop) + * { + * $loop->addTimer(1.0, function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester', $loop); + * ``` + * + * This interface does not enforce any particular timer resolution, so + * special care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Event loop implementations SHOULD work on + * a best effort basis and SHOULD provide at least millisecond accuracy + * unless otherwise noted. Many existing event loop implementations are + * known to provide microsecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * Similarly, the execution order of timers scheduled to execute at the + * same time (within its possible accuracy) is not guaranteed. + * + * This interface suggests that event loop implementations SHOULD use a + * monotonic time source if available. Given that a monotonic time source is + * only available as of PHP 7.3 by default, event loop implementations MAY + * fall back to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you schedule a timer to trigger in 30s and then adjust + * your system time forward by 20s, the timer SHOULD still trigger in 30s. + * See also [event loop implementations](#loop-implementations) for more details. + * + * @param int|float $interval The number of seconds to wait before execution. + * @param callable $callback The callback to invoke. + * + * @return TimerInterface + */ + public function addTimer($interval, $callback); + + /** + * Enqueue a callback to be invoked repeatedly after the given interval. + * + * The second parameter MUST be a timer callback function that accepts + * the timer instance as its only parameter. + * If you don't use the timer instance inside your timer callback function + * you MAY use a function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * This method returns a timer instance. The same timer instance will also be + * passed into the timer callback function as described above. + * Unlike [`addTimer()`](#addtimer), this method will ensure the callback + * will be invoked infinitely after the given interval or until you invoke + * [`cancelTimer`](#canceltimer). + * + * ```php + * $timer = $loop->addPeriodicTimer(0.1, function () { + * echo 'tick!' . PHP_EOL; + * }); + * + * $loop->addTimer(1.0, function () use ($loop, $timer) { + * $loop->cancelTimer($timer); + * echo 'Done' . PHP_EOL; + * }); + * ``` + * + * See also [example #2](examples). + * + * If you want to limit the number of executions, you can bind + * arbitrary data to a callback closure like this: + * + * ```php + * function hello($name, LoopInterface $loop) + * { + * $n = 3; + * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + * if ($n > 0) { + * --$n; + * echo "hello $name\n"; + * } else { + * $loop->cancelTimer($timer); + * } + * }); + * } + * + * hello('Tester', $loop); + * ``` + * + * This interface does not enforce any particular timer resolution, so + * special care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Event loop implementations SHOULD work on + * a best effort basis and SHOULD provide at least millisecond accuracy + * unless otherwise noted. Many existing event loop implementations are + * known to provide microsecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * Similarly, the execution order of timers scheduled to execute at the + * same time (within its possible accuracy) is not guaranteed. + * + * This interface suggests that event loop implementations SHOULD use a + * monotonic time source if available. Given that a monotonic time source is + * only available as of PHP 7.3 by default, event loop implementations MAY + * fall back to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you schedule a timer to trigger in 30s and then adjust + * your system time forward by 20s, the timer SHOULD still trigger in 30s. + * See also [event loop implementations](#loop-implementations) for more details. + * + * Additionally, periodic timers may be subject to timer drift due to + * re-scheduling after each invocation. As such, it's generally not + * recommended to rely on this for high precision intervals with millisecond + * accuracy or below. + * + * @param int|float $interval The number of seconds to wait before execution. + * @param callable $callback The callback to invoke. + * + * @return TimerInterface + */ + public function addPeriodicTimer($interval, $callback); + + /** + * Cancel a pending timer. + * + * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + * + * Calling this method on a timer instance that has not been added to this + * loop instance or on a timer that has already been cancelled has no effect. + * + * @param TimerInterface $timer The timer to cancel. + * + * @return void + */ + public function cancelTimer(TimerInterface $timer); + + /** + * Schedule a callback to be invoked on a future tick of the event loop. + * + * This works very much similar to timers with an interval of zero seconds, + * but does not require the overhead of scheduling a timer queue. + * + * The tick callback function MUST be able to accept zero parameters. + * + * The tick callback function MUST NOT throw an `Exception`. + * The return value of the tick callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello($name, LoopInterface $loop) + * { + * $loop->futureTick(function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester', $loop); + * ``` + * + * Unlike timers, tick callbacks are guaranteed to be executed in the order + * they are enqueued. + * Also, once a callback is enqueued, there's no way to cancel this operation. + * + * This is often used to break down bigger tasks into smaller steps (a form + * of cooperative multitasking). + * + * ```php + * $loop->futureTick(function () { + * echo 'b'; + * }); + * $loop->futureTick(function () { + * echo 'c'; + * }); + * echo 'a'; + * ``` + * + * See also [example #3](examples). + * + * @param callable $listener The callback to invoke. + * + * @return void + */ + public function futureTick($listener); + + /** + * Register a listener to be notified when a signal has been caught by this process. + * + * This is useful to catch user interrupt signals or shutdown signals from + * tools like `supervisor` or `systemd`. + * + * The second parameter MUST be a listener callback function that accepts + * the signal as its only parameter. + * If you don't use the signal inside your listener callback function + * you MAY use a function which has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * ```php + * $loop->addSignal(SIGINT, function (int $signal) { + * echo 'Caught user interrupt signal' . PHP_EOL; + * }); + * ``` + * + * See also [example #4](examples). + * + * Signaling is only available on Unix-like platforms, Windows isn't + * supported due to operating system limitations. + * This method may throw a `BadMethodCallException` if signals aren't + * supported on this platform, for example when required extensions are + * missing. + * + * **Note: A listener can only be added once to the same signal, any + * attempts to add it more than once will be ignored.** + * + * @param int $signal + * @param callable $listener + * + * @throws \BadMethodCallException when signals aren't supported on this + * platform, for example when required extensions are missing. + * + * @return void + */ + public function addSignal($signal, $listener); + + /** + * Removes a previously added signal listener. + * + * ```php + * $loop->removeSignal(SIGINT, $listener); + * ``` + * + * Any attempts to remove listeners that aren't registered will be ignored. + * + * @param int $signal + * @param callable $listener + * + * @return void + */ + public function removeSignal($signal, $listener); + + /** + * Run the event loop until there are no more tasks to perform. + * + * For many applications, this method is the only directly visible + * invocation on the event loop. + * As a rule of thumb, it is usually recommended to attach everything to the + * same loop instance and then run the loop once at the bottom end of the + * application. + * + * ```php + * $loop->run(); + * ``` + * + * This method will keep the loop running until there are no more tasks + * to perform. In other words: This method will block until the last + * timer, stream and/or signal has been removed. + * + * Likewise, it is imperative to ensure the application actually invokes + * this method once. Adding listeners to the loop and missing to actually + * run it will result in the application exiting without actually waiting + * for any of the attached listeners. + * + * This method MUST NOT be called while the loop is already running. + * This method MAY be called more than once after it has explicitly been + * [`stop()`ped](#stop) or after it automatically stopped because it + * previously did no longer have anything to do. + * + * @return void + */ + public function run(); + + /** + * Instruct a running event loop to stop. + * + * This method is considered advanced usage and should be used with care. + * As a rule of thumb, it is usually recommended to let the loop stop + * only automatically when it no longer has anything to do. + * + * This method can be used to explicitly instruct the event loop to stop: + * + * ```php + * $loop->addTimer(3.0, function () use ($loop) { + * $loop->stop(); + * }); + * ``` + * + * Calling this method on a loop instance that is not currently running or + * on a loop instance that has already been stopped has no effect. + * + * @return void + */ + public function stop(); +} diff --git a/vendor/react/event-loop/src/SignalsHandler.php b/vendor/react/event-loop/src/SignalsHandler.php new file mode 100644 index 0000000000..10d125df8e --- /dev/null +++ b/vendor/react/event-loop/src/SignalsHandler.php @@ -0,0 +1,63 @@ +signals[$signal])) { + $this->signals[$signal] = array(); + } + + if (\in_array($listener, $this->signals[$signal])) { + return; + } + + $this->signals[$signal][] = $listener; + } + + public function remove($signal, $listener) + { + if (!isset($this->signals[$signal])) { + return; + } + + $index = \array_search($listener, $this->signals[$signal], true); + unset($this->signals[$signal][$index]); + + if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) { + unset($this->signals[$signal]); + } + } + + public function call($signal) + { + if (!isset($this->signals[$signal])) { + return; + } + + foreach ($this->signals[$signal] as $listener) { + \call_user_func($listener, $signal); + } + } + + public function count($signal) + { + if (!isset($this->signals[$signal])) { + return 0; + } + + return \count($this->signals[$signal]); + } + + public function isEmpty() + { + return !$this->signals; + } +} diff --git a/vendor/react/event-loop/src/StreamSelectLoop.php b/vendor/react/event-loop/src/StreamSelectLoop.php new file mode 100644 index 0000000000..1686fd7406 --- /dev/null +++ b/vendor/react/event-loop/src/StreamSelectLoop.php @@ -0,0 +1,330 @@ +futureTickQueue = new FutureTickQueue(); + $this->timers = new Timers(); + $this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'); + $this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals'); + $this->signals = new SignalsHandler(); + + // prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick + if ($this->pcntl && !$this->pcntlPoll) { + \pcntl_async_signals(true); + } + } + + public function addReadStream($stream, $listener) + { + $key = (int) $stream; + + if (!isset($this->readStreams[$key])) { + $this->readStreams[$key] = $stream; + $this->readListeners[$key] = $listener; + } + } + + public function addWriteStream($stream, $listener) + { + $key = (int) $stream; + + if (!isset($this->writeStreams[$key])) { + $this->writeStreams[$key] = $stream; + $this->writeListeners[$key] = $listener; + } + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + unset( + $this->readStreams[$key], + $this->readListeners[$key] + ); + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + unset( + $this->writeStreams[$key], + $this->writeListeners[$key] + ); + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $this->timers->add($timer); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $this->timers->add($timer); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + $this->timers->cancel($timer); + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function addSignal($signal, $listener) + { + if ($this->pcntl === false) { + throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"'); + } + + $first = $this->signals->count($signal) === 0; + $this->signals->add($signal, $listener); + + if ($first) { + \pcntl_signal($signal, array($this->signals, 'call')); + } + } + + public function removeSignal($signal, $listener) + { + if (!$this->signals->count($signal)) { + return; + } + + $this->signals->remove($signal, $listener); + + if ($this->signals->count($signal) === 0) { + \pcntl_signal($signal, \SIG_DFL); + } + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $this->timers->tick(); + + // Future-tick queue has pending callbacks ... + if (!$this->running || !$this->futureTickQueue->isEmpty()) { + $timeout = 0; + + // There is a pending timer, only block until it is due ... + } elseif ($scheduledAt = $this->timers->getFirst()) { + $timeout = $scheduledAt - $this->timers->getTime(); + if ($timeout < 0) { + $timeout = 0; + } else { + // Convert float seconds to int microseconds. + // Ensure we do not exceed maximum integer size, which may + // cause the loop to tick once every ~35min on 32bit systems. + $timeout *= self::MICROSECONDS_PER_SECOND; + $timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout; + } + + // The only possible event is stream or signal activity, so wait forever ... + } elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) { + $timeout = null; + + // There's nothing left to do ... + } else { + break; + } + + $this->waitForStreamActivity($timeout); + } + } + + public function stop() + { + $this->running = false; + } + + /** + * Wait/check for stream activity, or until the next timer is due. + * + * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. + */ + private function waitForStreamActivity($timeout) + { + $read = $this->readStreams; + $write = $this->writeStreams; + + $available = $this->streamSelect($read, $write, $timeout); + if ($this->pcntlPoll) { + \pcntl_signal_dispatch(); + } + if (false === $available) { + // if a system call has been interrupted, + // we cannot rely on it's outcome + return; + } + + foreach ($read as $stream) { + $key = (int) $stream; + + if (isset($this->readListeners[$key])) { + \call_user_func($this->readListeners[$key], $stream); + } + } + + foreach ($write as $stream) { + $key = (int) $stream; + + if (isset($this->writeListeners[$key])) { + \call_user_func($this->writeListeners[$key], $stream); + } + } + } + + /** + * Emulate a stream_select() implementation that does not break when passed + * empty stream arrays. + * + * @param array $read An array of read streams to select upon. + * @param array $write An array of write streams to select upon. + * @param int|null $timeout Activity timeout in microseconds, or null to wait forever. + * + * @return int|false The total number of streams that are ready for read/write. + * Can return false if stream_select() is interrupted by a signal. + */ + private function streamSelect(array &$read, array &$write, $timeout) + { + if ($read || $write) { + // We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`. + // However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms. + // Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts. + // We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later. + // This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix). + // Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state. + // @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select + $except = null; + if (\DIRECTORY_SEPARATOR === '\\') { + $except = array(); + foreach ($write as $key => $socket) { + if (!isset($read[$key]) && @\ftell($socket) === 0) { + $except[$key] = $socket; + } + } + } + + /** @var ?callable $previous */ + $previous = \set_error_handler(function ($errno, $errstr) use (&$previous) { + // suppress warnings that occur when `stream_select()` is interrupted by a signal + // PHP defines `EINTR` through `ext-sockets` or `ext-pcntl`, otherwise use common default (Linux & Mac) + $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : (\defined('PCNTL_EINTR') ? \PCNTL_EINTR : 4); + if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) { + return; + } + + // forward any other error to registered error handler or print warning + return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false; + }); + + try { + $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + \restore_error_handler(); + } catch (\Throwable $e) { // @codeCoverageIgnoreStart + \restore_error_handler(); + throw $e; + } catch (\Exception $e) { + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + if ($except) { + $write = \array_merge($write, $except); + } + return $ret; + } + + if ($timeout > 0) { + \usleep($timeout); + } elseif ($timeout === null) { + // wait forever (we only reach this if we're only awaiting signals) + // this may be interrupted and return earlier when a signal is received + \sleep(PHP_INT_MAX); + } + + return 0; + } +} diff --git a/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/vendor/react/event-loop/src/Tick/FutureTickQueue.php new file mode 100644 index 0000000000..efabcbc547 --- /dev/null +++ b/vendor/react/event-loop/src/Tick/FutureTickQueue.php @@ -0,0 +1,60 @@ +queue = new SplQueue(); + } + + /** + * Add a callback to be invoked on a future tick of the event loop. + * + * Callbacks are guaranteed to be executed in the order they are enqueued. + * + * @param callable $listener The callback to invoke. + */ + public function add($listener) + { + $this->queue->enqueue($listener); + } + + /** + * Flush the callback queue. + */ + public function tick() + { + // Only invoke as many callbacks as were on the queue when tick() was called. + $count = $this->queue->count(); + + while ($count--) { + \call_user_func( + $this->queue->dequeue() + ); + } + } + + /** + * Check if the next tick queue is empty. + * + * @return boolean + */ + public function isEmpty() + { + return $this->queue->isEmpty(); + } +} diff --git a/vendor/react/event-loop/src/Timer/Timer.php b/vendor/react/event-loop/src/Timer/Timer.php new file mode 100644 index 0000000000..da3602a31a --- /dev/null +++ b/vendor/react/event-loop/src/Timer/Timer.php @@ -0,0 +1,55 @@ +interval = (float) $interval; + $this->callback = $callback; + $this->periodic = (bool) $periodic; + } + + public function getInterval() + { + return $this->interval; + } + + public function getCallback() + { + return $this->callback; + } + + public function isPeriodic() + { + return $this->periodic; + } +} diff --git a/vendor/react/event-loop/src/Timer/Timers.php b/vendor/react/event-loop/src/Timer/Timers.php new file mode 100644 index 0000000000..53c46d03b1 --- /dev/null +++ b/vendor/react/event-loop/src/Timer/Timers.php @@ -0,0 +1,113 @@ +useHighResolution = \function_exists('hrtime'); + } + + public function updateTime() + { + return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true); + } + + public function getTime() + { + return $this->time ?: $this->updateTime(); + } + + public function add(TimerInterface $timer) + { + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); + $this->timers[$id] = $timer; + $this->schedule[$id] = $timer->getInterval() + $this->updateTime(); + $this->sorted = false; + } + + public function contains(TimerInterface $timer) + { + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); + return isset($this->timers[$id]); + } + + public function cancel(TimerInterface $timer) + { + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); + unset($this->timers[$id], $this->schedule[$id]); + } + + public function getFirst() + { + // ensure timers are sorted to simply accessing next (first) one + if (!$this->sorted) { + $this->sorted = true; + \asort($this->schedule); + } + + return \reset($this->schedule); + } + + public function isEmpty() + { + return \count($this->timers) === 0; + } + + public function tick() + { + // hot path: skip timers if nothing is scheduled + if (!$this->schedule) { + return; + } + + // ensure timers are sorted so we can execute in order + if (!$this->sorted) { + $this->sorted = true; + \asort($this->schedule); + } + + $time = $this->updateTime(); + + foreach ($this->schedule as $id => $scheduled) { + // schedule is ordered, so loop until first timer that is not scheduled for execution now + if ($scheduled >= $time) { + break; + } + + // skip any timers that are removed while we process the current schedule + if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) { + continue; + } + + $timer = $this->timers[$id]; + \call_user_func($timer->getCallback(), $timer); + + // re-schedule if this is a periodic timer and it has not been cancelled explicitly already + if ($timer->isPeriodic() && isset($this->timers[$id])) { + $this->schedule[$id] = $timer->getInterval() + $time; + $this->sorted = false; + } else { + unset($this->timers[$id], $this->schedule[$id]); + } + } + } +} diff --git a/vendor/react/event-loop/src/TimerInterface.php b/vendor/react/event-loop/src/TimerInterface.php new file mode 100644 index 0000000000..cdcf7732df --- /dev/null +++ b/vendor/react/event-loop/src/TimerInterface.php @@ -0,0 +1,27 @@ +` and `reject(Throwable $reason): PromiseInterface`. + It is no longer possible to resolve a promise without a value (use `null` instead) or reject a promise without a reason (use `Throwable` instead). + (#93, #141 and #142 by @jsor, #138, #149 and #247 by @WyriHaximus and #213 and #246 by @clue) + + ```php + // old (arguments used to be optional) + $promise = resolve(); + $promise = reject(); + + // new (already supported before) + $promise = resolve(null); + $promise = reject(new RuntimeException()); + ``` + +* Feature / BC break: Report all unhandled rejections by default and remove ~~`done()`~~ method. + Add new `set_rejection_handler()` function to set the global rejection handler for unhandled promise rejections. + (#248, #249 and #224 by @clue) + + ```php + // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 + reject(new RuntimeException('Unhandled')); + ``` + +* BC break: Remove all deprecated APIs and reduce API surface. + Remove ~~`some()`~~, ~~`map()`~~, ~~`reduce()`~~ functions, use `any()` and `all()` functions instead. + Remove internal ~~`FulfilledPromise`~~ and ~~`RejectedPromise`~~ classes, use `resolve()` and `reject()` functions instead. + Remove legacy promise progress API (deprecated third argument to `then()` method) and deprecated ~~`LazyPromise`~~ class. + (#32 and #98 by @jsor and #164, #219 and #220 by @clue) + +* BC break: Make all classes final to encourage composition over inheritance. + (#80 by @jsor) + +* Feature / BC break: Require `array` (or `iterable`) type for `all()` + `race()` + `any()` functions and bring in line with ES6 specification. + These functions now require a single argument with a variable number of promises or values as input. + (#225 by @clue and #35 by @jsor) + +* Fix / BC break: Fix `race()` to return a forever pending promise when called with an empty `array` (or `iterable`) and bring in line with ES6 specification. + (#83 by @jsor and #225 by @clue) + +* Minor performance improvements by initializing `Deferred` in the constructor and avoiding `call_user_func()` calls. + (#151 by @WyriHaximus and #171 by @Kubo2) + +* Minor documentation improvements. + (#110 by @seregazhuk, #132 by @CharlotteDunois, #145 by @danielecr, #178 by @WyriHaximus, #189 by @srdante, #212 by @clue, #214, #239 and #243 by @SimonFrings and #231 by @nhedger) + +The following changes had to be ported to this release due to our branching +strategy, but also appeared in the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x): + +* Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+). + (#197 by @cdosoftei and @SimonFrings) + +* Feature: Support intersection types (PHP 8.1+). + (#209 by @bzikarsky) + +* Feature: Support DNS types (PHP 8.2+). + (#236 by @nhedger) + +* Feature: Port all memory improvements from `2.x` to `3.x`. + (#150 by @clue and @WyriHaximus) + +* Fix: Fix checking whether cancellable promise is an object and avoid possible warning. + (#161 by @smscr) + +* Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function. + (#134 by @WyriHaximus) + +* Improve test suite, update PHPUnit and PHP versions and add `.gitattributes` to exclude dev files from exports. + (#107 by @carusogabriel, #148 and #234 by @WyriHaximus, #153 by @reedy, #162, #230 and #240 by @clue, #173, #177, #185 and #199 by @SimonFrings, #193 by @woodongwong and #210 by @bzikarsky) + +The following changes were originally planned for this release but later reverted +and are not part of the final release: + +* Add iterative callback queue handler to avoid recursion (later removed to improve Fiber support). + (#28, #82 and #86 by @jsor, #158 by @WyriHaximus and #229 and #238 by @clue) + +* Trigger an `E_USER_ERROR` instead of throwing an exception from `done()` (later removed entire `done()` method to globally report unhandled rejections). + (#97 by @jsor and #224 and #248 by @clue) + +* Add type declarations for `some()` (later removed entire `some()` function). + (#172 by @WyriHaximus and #219 by @clue) + +## 2.0.0 (2013-12-10) + +See [`2.x` CHANGELOG](https://github.com/reactphp/promise/blob/2.x/CHANGELOG.md) for more details. + +## 1.0.0 (2012-11-07) + +See [`1.x` CHANGELOG](https://github.com/reactphp/promise/blob/1.x/CHANGELOG.md) for more details. diff --git a/vendor/react/promise/LICENSE b/vendor/react/promise/LICENSE new file mode 100644 index 0000000000..21c1357b7a --- /dev/null +++ b/vendor/react/promise/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/react/promise/README.md b/vendor/react/promise/README.md new file mode 100644 index 0000000000..2108d982e7 --- /dev/null +++ b/vendor/react/promise/README.md @@ -0,0 +1,722 @@ +Promise +======= + +A lightweight implementation of +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +[![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise) + +Table of Contents +----------------- + +1. [Introduction](#introduction) +2. [Concepts](#concepts) + * [Deferred](#deferred) + * [Promise](#promise-1) +3. [API](#api) + * [Deferred](#deferred-1) + * [Deferred::promise()](#deferredpromise) + * [Deferred::resolve()](#deferredresolve) + * [Deferred::reject()](#deferredreject) + * [PromiseInterface](#promiseinterface) + * [PromiseInterface::then()](#promiseinterfacethen) + * [PromiseInterface::catch()](#promiseinterfacecatch) + * [PromiseInterface::finally()](#promiseinterfacefinally) + * [PromiseInterface::cancel()](#promiseinterfacecancel) + * [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise) + * [~~PromiseInterface::always()~~](#promiseinterfacealways) + * [Promise](#promise-2) + * [Functions](#functions) + * [resolve()](#resolve) + * [reject()](#reject) + * [all()](#all) + * [race()](#race) + * [any()](#any) + * [set_rejection_handler()](#set_rejection_handler) +4. [Examples](#examples) + * [How to use Deferred](#how-to-use-deferred) + * [How promise forwarding works](#how-promise-forwarding-works) + * [Resolution forwarding](#resolution-forwarding) + * [Rejection forwarding](#rejection-forwarding) + * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) +5. [Install](#install) +6. [Tests](#tests) +7. [Credits](#credits) +8. [License](#license) + +Introduction +------------ + +Promise is a library implementing +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +It also provides several other useful promise-related concepts, such as joining +multiple promises and mapping and reducing collections of promises. + +If you've never heard about promises before, +[read this first](https://gist.github.com/domenic/3889970). + +Concepts +-------- + +### Deferred + +A **Deferred** represents a computation or unit of work that may not have +completed yet. Typically (but not always), that computation will be something +that executes asynchronously and completes at some point in the future. + +### Promise + +While a deferred represents the computation itself, a **Promise** represents +the result of that computation. Thus, each deferred has a promise that acts as +a placeholder for its actual result. + +API +--- + +### Deferred + +A deferred represents an operation whose resolution is pending. It has separate +promise and resolver parts. + +```php +$deferred = new React\Promise\Deferred(); + +$promise = $deferred->promise(); + +$deferred->resolve(mixed $value); +$deferred->reject(\Throwable $reason); +``` + +The `promise` method returns the promise of the deferred. + +The `resolve` and `reject` methods control the state of the deferred. + +The constructor of the `Deferred` accepts an optional `$canceller` argument. +See [Promise](#promise-2) for more information. + +#### Deferred::promise() + +```php +$promise = $deferred->promise(); +``` + +Returns the promise of the deferred, which you can hand out to others while +keeping the authority to modify its state to yourself. + +#### Deferred::resolve() + +```php +$deferred->resolve(mixed $value); +``` + +Resolves the promise returned by `promise()`. All consumers are notified by +having `$onFulfilled` (which they registered via `$promise->then()`) called with +`$value`. + +If `$value` itself is a promise, the promise will transition to the state of +this promise once it is resolved. + +See also the [`resolve()` function](#resolve). + +#### Deferred::reject() + +```php +$deferred->reject(\Throwable $reason); +``` + +Rejects the promise returned by `promise()`, signalling that the deferred's +computation failed. +All consumers are notified by having `$onRejected` (which they registered via +`$promise->then()`) called with `$reason`. + +See also the [`reject()` function](#reject). + +### PromiseInterface + +The promise interface provides the common interface for all promise +implementations. +See [Promise](#promise-2) for the only public implementation exposed by this +package. + +A promise represents an eventual outcome, which is either fulfillment (success) +and an associated value, or rejection (failure) and an associated reason. + +Once in the fulfilled or rejected state, a promise becomes immutable. +Neither its state nor its result (or error) can be modified. + +#### PromiseInterface::then() + +```php +$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null); +``` + +Transforms a promise's value by applying a function to the promise's fulfillment +or rejection value. Returns a new promise for the transformed result. + +The `then()` method registers new fulfilled and rejection handlers with a promise +(all parameters are optional): + + * `$onFulfilled` will be invoked once the promise is fulfilled and passed + the result as the first argument. + * `$onRejected` will be invoked once the promise is rejected and passed the + reason as the first argument. + +It returns a new promise that will fulfill with the return value of either +`$onFulfilled` or `$onRejected`, whichever is called, or will reject with +the thrown exception if either throws. + +A promise makes the following guarantees about handlers registered in +the same call to `then()`: + + 1. Only one of `$onFulfilled` or `$onRejected` will be called, + never both. + 2. `$onFulfilled` and `$onRejected` will never be called more + than once. + +#### See also + +* [resolve()](#resolve) - Creating a resolved promise +* [reject()](#reject) - Creating a rejected promise + +#### PromiseInterface::catch() + +```php +$promise->catch(callable $onRejected); +``` + +Registers a rejection handler for promise. It is a shortcut for: + +```php +$promise->then(null, $onRejected); +``` + +Additionally, you can type hint the `$reason` argument of `$onRejected` to catch +only specific errors. + +```php +$promise + ->catch(function (\RuntimeException $reason) { + // Only catch \RuntimeException instances + // All other types of errors will propagate automatically + }) + ->catch(function (\Throwable $reason) { + // Catch other errors + }); +``` + +#### PromiseInterface::finally() + +```php +$newPromise = $promise->finally(callable $onFulfilledOrRejected); +``` + +Allows you to execute "cleanup" type tasks in a promise chain. + +It arranges for `$onFulfilledOrRejected` to be called, with no arguments, +when the promise is either fulfilled or rejected. + +* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, + `$newPromise` will fulfill with the same value as `$promise`. +* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a + rejected promise, `$newPromise` will reject with the thrown exception or + rejected promise's reason. +* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, + `$newPromise` will reject with the same reason as `$promise`. +* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a + rejected promise, `$newPromise` will reject with the thrown exception or + rejected promise's reason. + +`finally()` behaves similarly to the synchronous finally statement. When combined +with `catch()`, `finally()` allows you to write code that is similar to the familiar +synchronous catch/finally pair. + +Consider the following synchronous code: + +```php +try { + return doSomething(); +} catch (\Throwable $e) { + return handleError($e); +} finally { + cleanup(); +} +``` + +Similar asynchronous code (with `doSomething()` that returns a promise) can be +written: + +```php +return doSomething() + ->catch('handleError') + ->finally('cleanup'); +``` + +#### PromiseInterface::cancel() + +``` php +$promise->cancel(); +``` + +The `cancel()` method notifies the creator of the promise that there is no +further interest in the results of the operation. + +Once a promise is settled (either fulfilled or rejected), calling `cancel()` on +a promise has no effect. + +#### ~~PromiseInterface::otherwise()~~ + +> Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead. + +The `otherwise()` method registers a rejection handler for a promise. + +This method continues to exist only for BC reasons and to ease upgrading +between versions. It is an alias for: + +```php +$promise->catch($onRejected); +``` + +#### ~~PromiseInterface::always()~~ + +> Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead. + +The `always()` method allows you to execute "cleanup" type tasks in a promise chain. + +This method continues to exist only for BC reasons and to ease upgrading +between versions. It is an alias for: + +```php +$promise->finally($onFulfilledOrRejected); +``` + +### Promise + +Creates a promise whose state is controlled by the functions passed to +`$resolver`. + +```php +$resolver = function (callable $resolve, callable $reject) { + // Do some work, possibly asynchronously, and then + // resolve or reject. + + $resolve($awesomeResult); + // or throw new Exception('Promise rejected'); + // or $resolve($anotherPromise); + // or $reject($nastyError); +}; + +$canceller = function () { + // Cancel/abort any running operations like network connections, streams etc. + + // Reject promise by throwing an exception + throw new Exception('Promise cancelled'); +}; + +$promise = new React\Promise\Promise($resolver, $canceller); +``` + +The promise constructor receives a resolver function and an optional canceller +function which both will be called with two arguments: + + * `$resolve($value)` - Primary function that seals the fate of the + returned promise. Accepts either a non-promise value, or another promise. + When called with a non-promise value, fulfills promise with that value. + When called with another promise, e.g. `$resolve($otherPromise)`, promise's + fate will be equivalent to that of `$otherPromise`. + * `$reject($reason)` - Function that rejects the promise. It is recommended to + just throw an exception instead of using `$reject()`. + +If the resolver or canceller throw an exception, the promise will be rejected +with that thrown exception as the rejection reason. + +The resolver function will be called immediately, the canceller function only +once all consumers called the `cancel()` method of the promise. + +### Functions + +Useful functions for creating and joining collections of promises. + +All functions working on promise collections (like `all()`, `race()`, +etc.) support cancellation. This means, if you call `cancel()` on the returned +promise, all promises in the collection are cancelled. + +#### resolve() + +```php +$promise = React\Promise\resolve(mixed $promiseOrValue); +``` + +Creates a promise for the supplied `$promiseOrValue`. + +If `$promiseOrValue` is a value, it will be the resolution value of the +returned promise. + +If `$promiseOrValue` is a thenable (any object that provides a `then()` method), +a trusted promise that follows the state of the thenable is returned. + +If `$promiseOrValue` is a promise, it will be returned as is. + +The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) +and can be consumed like any other promise: + +```php +$promise = React\Promise\resolve(42); + +$promise->then(function (int $result): void { + var_dump($result); +}, function (\Throwable $e): void { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +#### reject() + +```php +$promise = React\Promise\reject(\Throwable $reason); +``` + +Creates a rejected promise for the supplied `$reason`. + +Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers +both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and +[`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to +reject a promise, any language error or user land exception can be used to reject a promise. + +The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) +and can be consumed like any other promise: + +```php +$promise = React\Promise\reject(new RuntimeException('Request failed')); + +$promise->then(function (int $result): void { + var_dump($result); +}, function (\Throwable $e): void { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +Note that rejected promises should always be handled similar to how any +exceptions should always be caught in a `try` + `catch` block. If you remove the +last reference to a rejected promise that has not been handled, it will +report an unhandled promise rejection: + +```php +function incorrect(): int +{ + $promise = React\Promise\reject(new RuntimeException('Request failed')); + + // Commented out: No rejection handler registered here. + // $promise->then(null, function (\Throwable $e): void { /* ignore */ }); + + // Returning from a function will remove all local variable references, hence why + // this will report an unhandled promise rejection here. + return 42; +} + +// Calling this function will log an error message plus its stack trace: +// Unhandled promise rejection with RuntimeException: Request failed in example.php:10 +incorrect(); +``` + +A rejected promise will be considered "handled" if you catch the rejection +reason with either the [`then()` method](#promiseinterfacethen), the +[`catch()` method](#promiseinterfacecatch), or the +[`finally()` method](#promiseinterfacefinally). Note that each of these methods +return a new promise that may again be rejected if you re-throw an exception. + +A rejected promise will also be considered "handled" if you abort the operation +with the [`cancel()` method](#promiseinterfacecancel) (which in turn would +usually reject the promise if it is still pending). + +See also the [`set_rejection_handler()` function](#set_rejection_handler). + +#### all() + +```php +$promise = React\Promise\all(iterable $promisesOrValues); +``` + +Returns a promise that will resolve only once all the items in +`$promisesOrValues` have resolved. The resolution value of the returned promise +will be an array containing the resolution values of each of the items in +`$promisesOrValues`. + +#### race() + +```php +$promise = React\Promise\race(iterable $promisesOrValues); +``` + +Initiates a competitive race that allows one winner. Returns a promise which is +resolved in the same way the first settled promise resolves. + +The returned promise will become **infinitely pending** if `$promisesOrValues` +contains 0 items. + +#### any() + +```php +$promise = React\Promise\any(iterable $promisesOrValues); +``` + +Returns a promise that will resolve when any one of the items in +`$promisesOrValues` resolves. The resolution value of the returned promise +will be the resolution value of the triggering item. + +The returned promise will only reject if *all* items in `$promisesOrValues` are +rejected. The rejection value will be a `React\Promise\Exception\CompositeException` +which holds all rejection reasons. The rejection reasons can be obtained with +`CompositeException::getThrowables()`. + +The returned promise will also reject with a `React\Promise\Exception\LengthException` +if `$promisesOrValues` contains 0 items. + +#### set_rejection_handler() + +```php +React\Promise\set_rejection_handler(?callable $callback): ?callable; +``` + +Sets the global rejection handler for unhandled promise rejections. + +Note that rejected promises should always be handled similar to how any +exceptions should always be caught in a `try` + `catch` block. If you remove +the last reference to a rejected promise that has not been handled, it will +report an unhandled promise rejection. See also the [`reject()` function](#reject) +for more details. + +The `?callable $callback` argument MUST be a valid callback function that +accepts a single `Throwable` argument or a `null` value to restore the +default promise rejection handler. The return value of the callback function +will be ignored and has no effect, so you SHOULD return a `void` value. The +callback function MUST NOT throw or the program will be terminated with a +fatal error. + +The function returns the previous rejection handler or `null` if using the +default promise rejection handler. + +The default promise rejection handler will log an error message plus its stack +trace: + +```php +// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 +React\Promise\reject(new RuntimeException('Unhandled')); +``` + +The promise rejection handler may be used to use customize the log message or +write to custom log targets. As a rule of thumb, this function should only be +used as a last resort and promise rejections are best handled with either the +[`then()` method](#promiseinterfacethen), the +[`catch()` method](#promiseinterfacecatch), or the +[`finally()` method](#promiseinterfacefinally). +See also the [`reject()` function](#reject) for more details. + +Examples +-------- + +### How to use Deferred + +```php +function getAwesomeResultPromise() +{ + $deferred = new React\Promise\Deferred(); + + // Execute a Node.js-style function using the callback pattern + computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) { + if ($error) { + $deferred->reject($error); + } else { + $deferred->resolve($result); + } + }); + + // Return the promise + return $deferred->promise(); +} + +getAwesomeResultPromise() + ->then( + function ($value) { + // Deferred resolved, do something with $value + }, + function (\Throwable $reason) { + // Deferred rejected, do something with $reason + } + ); +``` + +### How promise forwarding works + +A few simple examples to show how the mechanics of Promises/A forwarding works. +These examples are contrived, of course, and in real usage, promise chains will +typically be spread across several function calls, or even several levels of +your application architecture. + +#### Resolution forwarding + +Resolved promises forward resolution values to the next promise. +The first promise, `$deferred->promise()`, will resolve with the value passed +to `$deferred->resolve()` below. + +Each call to `then()` returns a new promise that will resolve with the return +value of the previous handler. This creates a promise "pipeline". + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + // $x will be the value passed to $deferred->resolve() below + // and returns a *new promise* for $x + 1 + return $x + 1; + }) + ->then(function ($x) { + // $x === 2 + // This handler receives the return value of the + // previous handler. + return $x + 1; + }) + ->then(function ($x) { + // $x === 3 + // This handler receives the return value of the + // previous handler. + return $x + 1; + }) + ->then(function ($x) { + // $x === 4 + // This handler receives the return value of the + // previous handler. + echo 'Resolve ' . $x; + }); + +$deferred->resolve(1); // Prints "Resolve 4" +``` + +#### Rejection forwarding + +Rejected promises behave similarly, and also work similarly to try/catch: +When you catch an exception, you must rethrow for it to propagate. + +Similarly, when you handle a rejected promise, to propagate the rejection, +"rethrow" it by either returning a rejected promise, or actually throwing +(since promise translates thrown exceptions into rejections) + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + throw new \Exception($x + 1); + }) + ->catch(function (\Exception $x) { + // Propagate the rejection + throw $x; + }) + ->catch(function (\Exception $x) { + // Can also propagate by returning another rejection + return React\Promise\reject( + new \Exception($x->getMessage() + 1) + ); + }) + ->catch(function ($x) { + echo 'Reject ' . $x->getMessage(); // 3 + }); + +$deferred->resolve(1); // Prints "Reject 3" +``` + +#### Mixed resolution and rejection forwarding + +Just like try/catch, you can choose to propagate or not. Mixing resolutions and +rejections will still forward handler results in a predictable way. + +```php +$deferred = new React\Promise\Deferred(); + +$deferred->promise() + ->then(function ($x) { + return $x + 1; + }) + ->then(function ($x) { + throw new \Exception($x + 1); + }) + ->catch(function (\Exception $x) { + // Handle the rejection, and don't propagate. + // This is like catch without a rethrow + return $x->getMessage() + 1; + }) + ->then(function ($x) { + echo 'Mixed ' . $x; // 4 + }); + +$deferred->resolve(1); // Prints "Mixed 4" +``` + +Install +------- + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version from this branch: + +```bash +composer require react/promise:^3.2 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on PHP 7.1 through current PHP 8+. +It's *highly recommended to use the latest supported PHP version* for this project. + +We're committed to providing long-term support (LTS) options and to provide a +smooth upgrade path. If you're using an older PHP version, you may use the +[`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or +[`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both +provide a compatible API but do not take advantage of newer language features. +You may target multiple versions at the same time to support a wider range of +PHP versions like this: + +```bash +composer require "react/promise:^3 || ^2 || ^1" +``` + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +On top of this, we use PHPStan on max level to ensure type safety across the project: + +```bash +vendor/bin/phpstan +``` + +Credits +------- + +Promise is a port of [when.js](https://github.com/cujojs/when) +by [Brian Cavalier](https://github.com/briancavalier). + +Also, large parts of the documentation have been ported from the when.js +[Wiki](https://github.com/cujojs/when/wiki) and the +[API docs](https://github.com/cujojs/when/blob/master/docs/api.md). + +License +------- + +Released under the [MIT](LICENSE) license. diff --git a/vendor/react/promise/composer.json b/vendor/react/promise/composer.json new file mode 100644 index 0000000000..5d1e277109 --- /dev/null +++ b/vendor/react/promise/composer.json @@ -0,0 +1,57 @@ +{ + "name": "react/promise", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "license": "MIT", + "authors": [ + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "React\\Promise\\": [ + "tests/fixtures/", + "tests/" + ] + }, + "files": [ + "tests/Fiber.php" + ] + }, + "keywords": [ + "promise", + "promises" + ] +} diff --git a/vendor/react/promise/src/Deferred.php b/vendor/react/promise/src/Deferred.php new file mode 100644 index 0000000000..80b8fcfbd8 --- /dev/null +++ b/vendor/react/promise/src/Deferred.php @@ -0,0 +1,52 @@ + + */ + private $promise; + + /** @var callable(T):void */ + private $resolveCallback; + + /** @var callable(\Throwable):void */ + private $rejectCallback; + + /** + * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller + */ + public function __construct(?callable $canceller = null) + { + $this->promise = new Promise(function ($resolve, $reject): void { + $this->resolveCallback = $resolve; + $this->rejectCallback = $reject; + }, $canceller); + } + + /** + * @return PromiseInterface + */ + public function promise(): PromiseInterface + { + return $this->promise; + } + + /** + * @param T $value + */ + public function resolve($value): void + { + ($this->resolveCallback)($value); + } + + public function reject(\Throwable $reason): void + { + ($this->rejectCallback)($reason); + } +} diff --git a/vendor/react/promise/src/Exception/CompositeException.php b/vendor/react/promise/src/Exception/CompositeException.php new file mode 100644 index 0000000000..2e672a04ad --- /dev/null +++ b/vendor/react/promise/src/Exception/CompositeException.php @@ -0,0 +1,32 @@ +throwables = $throwables; + } + + /** + * @return \Throwable[] + */ + public function getThrowables(): array + { + return $this->throwables; + } +} diff --git a/vendor/react/promise/src/Exception/LengthException.php b/vendor/react/promise/src/Exception/LengthException.php new file mode 100644 index 0000000000..775c48db6a --- /dev/null +++ b/vendor/react/promise/src/Exception/LengthException.php @@ -0,0 +1,7 @@ +started) { + return; + } + + $this->started = true; + $this->drain(); + } + + /** + * @param mixed $cancellable + */ + public function enqueue($cancellable): void + { + if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) { + return; + } + + $length = \array_push($this->queue, $cancellable); + + if ($this->started && 1 === $length) { + $this->drain(); + } + } + + private function drain(): void + { + for ($i = \key($this->queue); isset($this->queue[$i]); $i++) { + $cancellable = $this->queue[$i]; + assert(\method_exists($cancellable, 'cancel')); + + $exception = null; + + try { + $cancellable->cancel(); + } catch (\Throwable $exception) { + } + + unset($this->queue[$i]); + + if ($exception) { + throw $exception; + } + } + + $this->queue = []; + } +} diff --git a/vendor/react/promise/src/Internal/FulfilledPromise.php b/vendor/react/promise/src/Internal/FulfilledPromise.php new file mode 100644 index 0000000000..8664ffdb67 --- /dev/null +++ b/vendor/react/promise/src/Internal/FulfilledPromise.php @@ -0,0 +1,89 @@ + + */ +final class FulfilledPromise implements PromiseInterface +{ + /** @var T */ + private $value; + + /** + * @param T $value + * @throws \InvalidArgumentException + */ + public function __construct($value = null) + { + if ($value instanceof PromiseInterface) { + throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.'); + } + + $this->value = $value; + } + + /** + * @template TFulfilled + * @param ?(callable((T is void ? null : T)): (PromiseInterface|TFulfilled)) $onFulfilled + * @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)> + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface + { + if (null === $onFulfilled) { + return $this; + } + + try { + /** + * @var PromiseInterface|T $result + */ + $result = $onFulfilled($this->value); + return resolve($result); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } + } + + public function catch(callable $onRejected): PromiseInterface + { + return $this; + } + + public function finally(callable $onFulfilledOrRejected): PromiseInterface + { + return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }); + } + + public function cancel(): void + { + } + + /** + * @deprecated 3.0.0 Use `catch()` instead + * @see self::catch() + */ + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->catch($onRejected); + } + + /** + * @deprecated 3.0.0 Use `finally()` instead + * @see self::finally() + */ + public function always(callable $onFulfilledOrRejected): PromiseInterface + { + return $this->finally($onFulfilledOrRejected); + } +} diff --git a/vendor/react/promise/src/Internal/RejectedPromise.php b/vendor/react/promise/src/Internal/RejectedPromise.php new file mode 100644 index 0000000000..aa1dff3085 --- /dev/null +++ b/vendor/react/promise/src/Internal/RejectedPromise.php @@ -0,0 +1,128 @@ + + */ +final class RejectedPromise implements PromiseInterface +{ + /** @var \Throwable */ + private $reason; + + /** @var bool */ + private $handled = false; + + /** + * @param \Throwable $reason + */ + public function __construct(\Throwable $reason) + { + $this->reason = $reason; + } + + /** @throws void */ + public function __destruct() + { + if ($this->handled) { + return; + } + + $handler = set_rejection_handler(null); + if ($handler === null) { + $message = 'Unhandled promise rejection with ' . $this->reason; + + \error_log($message); + return; + } + + try { + $handler($this->reason); + } catch (\Throwable $e) { + \preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match); + \assert(isset($match[1], $match[2])); + $message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2]; + + \error_log($message); + exit(255); + } + } + + /** + * @template TRejected + * @param ?callable $onFulfilled + * @param ?(callable(\Throwable): (PromiseInterface|TRejected)) $onRejected + * @return PromiseInterface<($onRejected is null ? never : TRejected)> + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface + { + if (null === $onRejected) { + return $this; + } + + $this->handled = true; + + try { + return resolve($onRejected($this->reason)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } + } + + /** + * @template TThrowable of \Throwable + * @template TRejected + * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected + * @return PromiseInterface + */ + public function catch(callable $onRejected): PromiseInterface + { + if (!_checkTypehint($onRejected, $this->reason)) { + return $this; + } + + /** + * @var callable(\Throwable):(PromiseInterface|TRejected) $onRejected + */ + return $this->then(null, $onRejected); + } + + public function finally(callable $onFulfilledOrRejected): PromiseInterface + { + return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { + return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface { + return new RejectedPromise($reason); + }); + }); + } + + public function cancel(): void + { + $this->handled = true; + } + + /** + * @deprecated 3.0.0 Use `catch()` instead + * @see self::catch() + */ + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->catch($onRejected); + } + + /** + * @deprecated 3.0.0 Use `always()` instead + * @see self::always() + */ + public function always(callable $onFulfilledOrRejected): PromiseInterface + { + return $this->finally($onFulfilledOrRejected); + } +} diff --git a/vendor/react/promise/src/Promise.php b/vendor/react/promise/src/Promise.php new file mode 100644 index 0000000000..4ac27007ca --- /dev/null +++ b/vendor/react/promise/src/Promise.php @@ -0,0 +1,303 @@ + + */ +final class Promise implements PromiseInterface +{ + /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */ + private $canceller; + + /** @var ?PromiseInterface */ + private $result; + + /** @var list):void> */ + private $handlers = []; + + /** @var int */ + private $requiredCancelRequests = 0; + + /** @var bool */ + private $cancelled = false; + + /** + * @param callable(callable(T):void,callable(\Throwable):void):void $resolver + * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller + */ + public function __construct(callable $resolver, ?callable $canceller = null) + { + $this->canceller = $canceller; + + // Explicitly overwrite arguments with null values before invoking + // resolver function. This ensure that these arguments do not show up + // in the stack trace in PHP 7+ only. + $cb = $resolver; + $resolver = $canceller = null; + $this->call($cb); + } + + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface + { + if (null !== $this->result) { + return $this->result->then($onFulfilled, $onRejected); + } + + if (null === $this->canceller) { + return new static($this->resolver($onFulfilled, $onRejected)); + } + + // This promise has a canceller, so we create a new child promise which + // has a canceller that invokes the parent canceller if all other + // followers are also cancelled. We keep a reference to this promise + // instance for the static canceller function and clear this to avoid + // keeping a cyclic reference between parent and follower. + $parent = $this; + ++$parent->requiredCancelRequests; + + return new static( + $this->resolver($onFulfilled, $onRejected), + static function () use (&$parent): void { + assert($parent instanceof self); + --$parent->requiredCancelRequests; + + if ($parent->requiredCancelRequests <= 0) { + $parent->cancel(); + } + + $parent = null; + } + ); + } + + /** + * @template TThrowable of \Throwable + * @template TRejected + * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected + * @return PromiseInterface + */ + public function catch(callable $onRejected): PromiseInterface + { + return $this->then(null, static function (\Throwable $reason) use ($onRejected) { + if (!_checkTypehint($onRejected, $reason)) { + return new RejectedPromise($reason); + } + + /** + * @var callable(\Throwable):(PromiseInterface|TRejected) $onRejected + */ + return $onRejected($reason); + }); + } + + public function finally(callable $onFulfilledOrRejected): PromiseInterface + { + return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { + return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise { + return new RejectedPromise($reason); + }); + }); + } + + public function cancel(): void + { + $this->cancelled = true; + $canceller = $this->canceller; + $this->canceller = null; + + $parentCanceller = null; + + if (null !== $this->result) { + // Forward cancellation to rejected promise to avoid reporting unhandled rejection + if ($this->result instanceof RejectedPromise) { + $this->result->cancel(); + } + + // Go up the promise chain and reach the top most promise which is + // itself not following another promise + $root = $this->unwrap($this->result); + + // Return if the root promise is already resolved or a + // FulfilledPromise or RejectedPromise + if (!$root instanceof self || null !== $root->result) { + return; + } + + $root->requiredCancelRequests--; + + if ($root->requiredCancelRequests <= 0) { + $parentCanceller = [$root, 'cancel']; + } + } + + if (null !== $canceller) { + $this->call($canceller); + } + + // For BC, we call the parent canceller after our own canceller + if ($parentCanceller) { + $parentCanceller(); + } + } + + /** + * @deprecated 3.0.0 Use `catch()` instead + * @see self::catch() + */ + public function otherwise(callable $onRejected): PromiseInterface + { + return $this->catch($onRejected); + } + + /** + * @deprecated 3.0.0 Use `finally()` instead + * @see self::finally() + */ + public function always(callable $onFulfilledOrRejected): PromiseInterface + { + return $this->finally($onFulfilledOrRejected); + } + + private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable + { + return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void { + $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void { + $promise = $promise->then($onFulfilled, $onRejected); + + if ($promise instanceof self && $promise->result === null) { + $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void { + $promise->then($resolve, $reject); + }; + } else { + $promise->then($resolve, $reject); + } + }; + }; + } + + private function reject(\Throwable $reason): void + { + if (null !== $this->result) { + return; + } + + $this->settle(reject($reason)); + } + + /** + * @param PromiseInterface $result + */ + private function settle(PromiseInterface $result): void + { + $result = $this->unwrap($result); + + if ($result === $this) { + $result = new RejectedPromise( + new \LogicException('Cannot resolve a promise with itself.') + ); + } + + if ($result instanceof self) { + $result->requiredCancelRequests++; + } else { + // Unset canceller only when not following a pending promise + $this->canceller = null; + } + + $handlers = $this->handlers; + + $this->handlers = []; + $this->result = $result; + + foreach ($handlers as $handler) { + $handler($result); + } + + // Forward cancellation to rejected promise to avoid reporting unhandled rejection + if ($this->cancelled && $result instanceof RejectedPromise) { + $result->cancel(); + } + } + + /** + * @param PromiseInterface $promise + * @return PromiseInterface + */ + private function unwrap(PromiseInterface $promise): PromiseInterface + { + while ($promise instanceof self && null !== $promise->result) { + /** @var PromiseInterface $promise */ + $promise = $promise->result; + } + + return $promise; + } + + /** + * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb + */ + private function call(callable $cb): void + { + // Explicitly overwrite argument with null value. This ensure that this + // argument does not show up in the stack trace in PHP 7+ only. + $callback = $cb; + $cb = null; + + // Use reflection to inspect number of arguments expected by this callback. + // We did some careful benchmarking here: Using reflection to avoid unneeded + // function arguments is actually faster than blindly passing them. + // Also, this helps avoiding unnecessary function arguments in the call stack + // if the callback creates an Exception (creating garbage cycles). + if (\is_array($callback)) { + $ref = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (\is_object($callback) && !$callback instanceof \Closure) { + $ref = new \ReflectionMethod($callback, '__invoke'); + } else { + assert($callback instanceof \Closure || \is_string($callback)); + $ref = new \ReflectionFunction($callback); + } + $args = $ref->getNumberOfParameters(); + + try { + if ($args === 0) { + $callback(); + } else { + // Keep references to this promise instance for the static resolve/reject functions. + // By using static callbacks that are not bound to this instance + // and passing the target promise instance by reference, we can + // still execute its resolving logic and still clear this + // reference when settling the promise. This helps avoiding + // garbage cycles if any callback creates an Exception. + // These assumptions are covered by the test suite, so if you ever feel like + // refactoring this, go ahead, any alternative suggestions are welcome! + $target =& $this; + + $callback( + static function ($value) use (&$target): void { + if ($target !== null) { + $target->settle(resolve($value)); + $target = null; + } + }, + static function (\Throwable $reason) use (&$target): void { + if ($target !== null) { + $target->reject($reason); + $target = null; + } + } + ); + } + } catch (\Throwable $e) { + $target = null; + $this->reject($e); + } + } +} diff --git a/vendor/react/promise/src/PromiseInterface.php b/vendor/react/promise/src/PromiseInterface.php new file mode 100644 index 0000000000..5869f76b64 --- /dev/null +++ b/vendor/react/promise/src/PromiseInterface.php @@ -0,0 +1,152 @@ +|TFulfilled)) $onFulfilled + * @param ?(callable(\Throwable): (PromiseInterface|TRejected)) $onRejected + * @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))> + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface; + + /** + * Registers a rejection handler for promise. It is a shortcut for: + * + * ```php + * $promise->then(null, $onRejected); + * ``` + * + * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch + * only specific errors. + * + * @template TThrowable of \Throwable + * @template TRejected + * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected + * @return PromiseInterface + */ + public function catch(callable $onRejected): PromiseInterface; + + /** + * Allows you to execute "cleanup" type tasks in a promise chain. + * + * It arranges for `$onFulfilledOrRejected` to be called, with no arguments, + * when the promise is either fulfilled or rejected. + * + * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, + * `$newPromise` will fulfill with the same value as `$promise`. + * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a + * rejected promise, `$newPromise` will reject with the thrown exception or + * rejected promise's reason. + * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, + * `$newPromise` will reject with the same reason as `$promise`. + * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a + * rejected promise, `$newPromise` will reject with the thrown exception or + * rejected promise's reason. + * + * `finally()` behaves similarly to the synchronous finally statement. When combined + * with `catch()`, `finally()` allows you to write code that is similar to the familiar + * synchronous catch/finally pair. + * + * Consider the following synchronous code: + * + * ```php + * try { + * return doSomething(); + * } catch(\Exception $e) { + * return handleError($e); + * } finally { + * cleanup(); + * } + * ``` + * + * Similar asynchronous code (with `doSomething()` that returns a promise) can be + * written: + * + * ```php + * return doSomething() + * ->catch('handleError') + * ->finally('cleanup'); + * ``` + * + * @param callable(): (void|PromiseInterface) $onFulfilledOrRejected + * @return PromiseInterface + */ + public function finally(callable $onFulfilledOrRejected): PromiseInterface; + + /** + * The `cancel()` method notifies the creator of the promise that there is no + * further interest in the results of the operation. + * + * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on + * a promise has no effect. + * + * @return void + */ + public function cancel(): void; + + /** + * [Deprecated] Registers a rejection handler for a promise. + * + * This method continues to exist only for BC reasons and to ease upgrading + * between versions. It is an alias for: + * + * ```php + * $promise->catch($onRejected); + * ``` + * + * @template TThrowable of \Throwable + * @template TRejected + * @param callable(TThrowable): (PromiseInterface|TRejected) $onRejected + * @return PromiseInterface + * @deprecated 3.0.0 Use catch() instead + * @see self::catch() + */ + public function otherwise(callable $onRejected): PromiseInterface; + + /** + * [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain. + * + * This method continues to exist only for BC reasons and to ease upgrading + * between versions. It is an alias for: + * + * ```php + * $promise->finally($onFulfilledOrRejected); + * ``` + * + * @param callable(): (void|PromiseInterface) $onFulfilledOrRejected + * @return PromiseInterface + * @deprecated 3.0.0 Use finally() instead + * @see self::finally() + */ + public function always(callable $onFulfilledOrRejected): PromiseInterface; +} diff --git a/vendor/react/promise/src/functions.php b/vendor/react/promise/src/functions.php new file mode 100644 index 0000000000..2aab877e31 --- /dev/null +++ b/vendor/react/promise/src/functions.php @@ -0,0 +1,345 @@ +|T $promiseOrValue + * @return PromiseInterface + */ +function resolve($promiseOrValue): PromiseInterface +{ + if ($promiseOrValue instanceof PromiseInterface) { + return $promiseOrValue; + } + + if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) { + $canceller = null; + + if (\method_exists($promiseOrValue, 'cancel')) { + $canceller = [$promiseOrValue, 'cancel']; + assert(\is_callable($canceller)); + } + + /** @var Promise */ + return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void { + $promiseOrValue->then($resolve, $reject); + }, $canceller); + } + + return new FulfilledPromise($promiseOrValue); +} + +/** + * Creates a rejected promise for the supplied `$reason`. + * + * If `$reason` is a value, it will be the rejection value of the + * returned promise. + * + * If `$reason` is a promise, its completion value will be the rejected + * value of the returned promise. + * + * This can be useful in situations where you need to reject a promise without + * throwing an exception. For example, it allows you to propagate a rejection with + * the value of another promise. + * + * @return PromiseInterface + */ +function reject(\Throwable $reason): PromiseInterface +{ + return new RejectedPromise($reason); +} + +/** + * Returns a promise that will resolve only once all the items in + * `$promisesOrValues` have resolved. The resolution value of the returned promise + * will be an array containing the resolution values of each of the items in + * `$promisesOrValues`. + * + * @template T + * @param iterable|T> $promisesOrValues + * @return PromiseInterface> + */ +function all(iterable $promisesOrValues): PromiseInterface +{ + $cancellationQueue = new Internal\CancellationQueue(); + + /** @var Promise> */ + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { + $toResolve = 0; + /** @var bool */ + $continue = true; + $values = []; + + foreach ($promisesOrValues as $i => $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + $values[$i] = null; + ++$toResolve; + + resolve($promiseOrValue)->then( + function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void { + $values[$i] = $value; + + if (0 === --$toResolve && !$continue) { + $resolve($values); + } + }, + function (\Throwable $reason) use (&$continue, $reject): void { + $continue = false; + $reject($reason); + } + ); + + if (!$continue && !\is_array($promisesOrValues)) { + break; + } + } + + $continue = false; + if ($toResolve === 0) { + $resolve($values); + } + }, $cancellationQueue); +} + +/** + * Initiates a competitive race that allows one winner. Returns a promise which is + * resolved in the same way the first settled promise resolves. + * + * The returned promise will become **infinitely pending** if `$promisesOrValues` + * contains 0 items. + * + * @template T + * @param iterable|T> $promisesOrValues + * @return PromiseInterface + */ +function race(iterable $promisesOrValues): PromiseInterface +{ + $cancellationQueue = new Internal\CancellationQueue(); + + /** @var Promise */ + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { + $continue = true; + + foreach ($promisesOrValues as $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void { + $continue = false; + }); + + if (!$continue && !\is_array($promisesOrValues)) { + break; + } + } + }, $cancellationQueue); +} + +/** + * Returns a promise that will resolve when any one of the items in + * `$promisesOrValues` resolves. The resolution value of the returned promise + * will be the resolution value of the triggering item. + * + * The returned promise will only reject if *all* items in `$promisesOrValues` are + * rejected. The rejection value will be an array of all rejection reasons. + * + * The returned promise will also reject with a `React\Promise\Exception\LengthException` + * if `$promisesOrValues` contains 0 items. + * + * @template T + * @param iterable|T> $promisesOrValues + * @return PromiseInterface + */ +function any(iterable $promisesOrValues): PromiseInterface +{ + $cancellationQueue = new Internal\CancellationQueue(); + + /** @var Promise */ + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { + $toReject = 0; + $continue = true; + $reasons = []; + + foreach ($promisesOrValues as $i => $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + ++$toReject; + + resolve($promiseOrValue)->then( + function ($value) use ($resolve, &$continue): void { + $continue = false; + $resolve($value); + }, + function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void { + $reasons[$i] = $reason; + + if (0 === --$toReject && !$continue) { + $reject(new CompositeException( + $reasons, + 'All promises rejected.' + )); + } + } + ); + + if (!$continue && !\is_array($promisesOrValues)) { + break; + } + } + + $continue = false; + if ($toReject === 0 && !$reasons) { + $reject(new Exception\LengthException( + 'Must contain at least 1 item but contains only 0 items.' + )); + } elseif ($toReject === 0) { + $reject(new CompositeException( + $reasons, + 'All promises rejected.' + )); + } + }, $cancellationQueue); +} + +/** + * Sets the global rejection handler for unhandled promise rejections. + * + * Note that rejected promises should always be handled similar to how any + * exceptions should always be caught in a `try` + `catch` block. If you remove + * the last reference to a rejected promise that has not been handled, it will + * report an unhandled promise rejection. See also the [`reject()` function](#reject) + * for more details. + * + * The `?callable $callback` argument MUST be a valid callback function that + * accepts a single `Throwable` argument or a `null` value to restore the + * default promise rejection handler. The return value of the callback function + * will be ignored and has no effect, so you SHOULD return a `void` value. The + * callback function MUST NOT throw or the program will be terminated with a + * fatal error. + * + * The function returns the previous rejection handler or `null` if using the + * default promise rejection handler. + * + * The default promise rejection handler will log an error message plus its + * stack trace: + * + * ```php + * // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 + * React\Promise\reject(new RuntimeException('Unhandled')); + * ``` + * + * The promise rejection handler may be used to use customize the log message or + * write to custom log targets. As a rule of thumb, this function should only be + * used as a last resort and promise rejections are best handled with either the + * [`then()` method](#promiseinterfacethen), the + * [`catch()` method](#promiseinterfacecatch), or the + * [`finally()` method](#promiseinterfacefinally). + * See also the [`reject()` function](#reject) for more details. + * + * @param callable(\Throwable):void|null $callback + * @return callable(\Throwable):void|null + */ +function set_rejection_handler(?callable $callback): ?callable +{ + static $current = null; + $previous = $current; + $current = $callback; + + return $previous; +} + +/** + * @internal + */ +function _checkTypehint(callable $callback, \Throwable $reason): bool +{ + if (\is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (\is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionMethod($callback, '__invoke'); + } else { + assert($callback instanceof \Closure || \is_string($callback)); + $callbackReflection = new \ReflectionFunction($callback); + } + + $parameters = $callbackReflection->getParameters(); + + if (!isset($parameters[0])) { + return true; + } + + $expectedException = $parameters[0]; + + // Extract the type of the argument and handle different possibilities + $type = $expectedException->getType(); + + $isTypeUnion = true; + $types = []; + + switch (true) { + case $type === null: + break; + case $type instanceof \ReflectionNamedType: + $types = [$type]; + break; + case $type instanceof \ReflectionIntersectionType: + $isTypeUnion = false; + case $type instanceof \ReflectionUnionType; + $types = $type->getTypes(); + break; + default: + throw new \LogicException('Unexpected return value of ReflectionParameter::getType'); + } + + // If there is no type restriction, it matches + if (empty($types)) { + return true; + } + + foreach ($types as $type) { + + if ($type instanceof \ReflectionIntersectionType) { + foreach ($type->getTypes() as $typeToMatch) { + assert($typeToMatch instanceof \ReflectionNamedType); + $name = $typeToMatch->getName(); + if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) { + break; + } + } + assert(isset($matches)); + } else { + assert($type instanceof \ReflectionNamedType); + $name = $type->getName(); + $matches = !$type->isBuiltin() && $reason instanceof $name; + } + + // If we look for a single match (union), we can return early on match + // If we look for a full match (intersection), we can return early on mismatch + if ($matches) { + if ($isTypeUnion) { + return true; + } + } else { + if (!$isTypeUnion) { + return false; + } + } + } + + // If we look for a single match (union) and did not return early, we matched no type and are false + // If we look for a full match (intersection) and did not return early, we matched all types and are true + return $isTypeUnion ? false : true; +} diff --git a/vendor/react/promise/src/functions_include.php b/vendor/react/promise/src/functions_include.php new file mode 100644 index 0000000000..bd0c54fd5c --- /dev/null +++ b/vendor/react/promise/src/functions_include.php @@ -0,0 +1,5 @@ +connect($uri)->then(function (React\Socket\ConnectionInterface $conn) { + // … + }, function (Exception $e) { + echo 'Error:' . $e->getMessage() . PHP_EOL; + }); + ``` + +* Improve test suite, test against PHP 8.1 release. + (#274 by @SimonFrings) + +## 1.9.0 (2021-08-03) + +* Feature: Add new `SocketServer` and deprecate `Server` to avoid class name collisions. + (#263 by @clue) + + The new `SocketServer` class has been added with an improved constructor signature + as a replacement for the previous `Server` class in order to avoid any ambiguities. + The previous name has been deprecated and should not be used anymore. + In its most basic form, the deprecated `Server` can now be considered an alias for new `SocketServer`. + + ```php + // deprecated + $socket = new React\Socket\Server(0); + $socket = new React\Socket\Server('127.0.0.1:8000'); + $socket = new React\Socket\Server('127.0.0.1:8000', null, $context); + $socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context); + + // new + $socket = new React\Socket\SocketServer('127.0.0.1:0'); + $socket = new React\Socket\SocketServer('127.0.0.1:8000'); + $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context); + $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop); + ``` + +* Feature: Update `Connector` signature to take optional `$context` as first argument. + (#264 by @clue) + + The new signature has been added to match the new `SocketServer` and + consistently move the now commonly unneeded loop argument to the last argument. + The previous signature has been deprecated and should not be used anymore. + In its most basic form, both signatures are compatible. + + ```php + // deprecated + $connector = new React\Socket\Connector(null, $context); + $connector = new React\Socket\Connector($loop, $context); + + // new + $connector = new React\Socket\Connector($context); + $connector = new React\Socket\Connector($context, $loop); + ``` + +## 1.8.0 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop). + (#260 by @clue) + + ```php + // old (still supported) + $socket = new React\Socket\Server('127.0.0.1:8080', $loop); + $connector = new React\Socket\Connector($loop); + + // new (using default loop) + $socket = new React\Socket\Server('127.0.0.1:8080'); + $connector = new React\Socket\Connector(); + ``` + +## 1.7.0 (2021-06-25) + +* Feature: Support falling back to multiple DNS servers from DNS config. + (#257 by @clue) + + If you're using the default `Connector`, it will now use all DNS servers + configured on your system. If you have multiple DNS servers configured and + connectivity to the primary DNS server is broken, it will now fall back to + your other DNS servers, thus providing improved connectivity and redundancy + for broken DNS configurations. + +* Feature: Use round robin for happy eyeballs DNS responses (load balancing). + (#247 by @clue) + + If you're using the default `Connector`, it will now randomize the order of + the IP addresses resolved via DNS when connecting. This allows the load to + be distributed more evenly across all returned IP addresses. This can be + used as a very basic DNS load balancing mechanism. + +* Internal improvement to avoid unhandled rejection for future Promise API. + (#258 by @clue) + +* Improve test suite, use GitHub actions for continuous integration (CI). + (#254 by @SimonFrings) + +## 1.6.0 (2020-08-28) + +* Feature: Support upcoming PHP 8 release. + (#246 by @clue) + +* Feature: Change default socket backlog size to 511. + (#242 by @clue) + +* Fix: Fix closing connection when cancelling during TLS handshake. + (#241 by @clue) + +* Fix: Fix blocking during possible `accept()` race condition + when multiple socket servers listen on same socket address. + (#244 by @clue) + +* Improve test suite, update PHPUnit config and add full core team to the license. + (#243 by @SimonFrings and #245 by @WyriHaximus) + +## 1.5.0 (2020-07-01) + +* Feature / Fix: Improve error handling and reporting for happy eyeballs and + immediately try next connection when one connection attempt fails. + (#230, #231, #232 and #233 by @clue) + + Error messages for failed connection attempts now include more details to + ease debugging. Additionally, the happy eyeballs algorithm has been improved + to avoid having to wait for some timers to expire which significantly + improves connection setup times (in particular when IPv6 isn't available). + +* Improve test suite, minor code cleanup and improve code coverage to 100%. + Update to PHPUnit 9 and skip legacy TLS 1.0 / TLS 1.1 tests if disabled by + system. Run tests on Windows and simplify Travis CI test matrix for Mac OS X + setup and skip all TLS tests on legacy HHVM. + (#229, #235, #236 and #238 by @clue and #239 by @SimonFrings) + +## 1.4.0 (2020-03-12) + +A major new feature release, see [**release announcement**](https://clue.engineering/2020/introducing-ipv6-for-reactphp). + +* Feature: Add IPv6 support to `Connector` (implement "Happy Eyeballs" algorithm to support IPv6 probing). + IPv6 support is turned on by default, use new `happy_eyeballs` option in `Connector` to toggle behavior. + (#196, #224 and #225 by @WyriHaximus and @clue) + +* Feature: Default to using DNS cache (with max 256 entries) for `Connector`. + (#226 by @clue) + +* Add `.gitattributes` to exclude dev files from exports and some minor code style fixes. + (#219 by @reedy and #218 by @mmoreram) + +* Improve test suite to fix failing test cases when using new DNS component, + significantly improve test performance by awaiting events instead of sleeping, + exclude TLS 1.3 test on PHP 7.3, run tests on PHP 7.4 and simplify test matrix. + (#208, #209, #210, #217 and #223 by @clue) + +## 1.3.0 (2019-07-10) + +* Feature: Forward compatibility with upcoming stable DNS component. + (#206 by @clue) + +## 1.2.1 (2019-06-03) + +* Avoid uneeded fragmented TLS work around for PHP 7.3.3+ and + work around failing test case detecting EOF on TLS 1.3 socket streams. + (#201 and #202 by @clue) + +* Improve TLS certificate/passphrase example. + (#190 by @jsor) + +## 1.2.0 (2019-01-07) + +* Feature / Fix: Improve TLS 1.3 support. + (#186 by @clue) + + TLS 1.3 is now an official standard as of August 2018! :tada: + The protocol has major improvements in the areas of security, performance, and privacy. + TLS 1.3 is supported by default as of [OpenSSL 1.1.1](https://www.openssl.org/blog/blog/2018/09/11/release111/). + For example, this version ships with Ubuntu 18.10 (and newer) by default, meaning that recent installations support TLS 1.3 out of the box :shipit: + +* Fix: Avoid possibility of missing remote address when TLS handshake fails. + (#188 by @clue) + +* Improve performance by prefixing all global functions calls with `\` to skip the look up and resolve process and go straight to the global function. + (#183 by @WyriHaximus) + +* Update documentation to use full class names with namespaces. + (#187 by @clue) + +* Improve test suite to avoid some possible race conditions, + test against PHP 7.3 on Travis and + use dedicated `assertInstanceOf()` assertions. + (#185 by @clue, #178 by @WyriHaximus and #181 by @carusogabriel) + +## 1.1.0 (2018-10-01) + +* Feature: Improve error reporting for failed connection attempts and improve + cancellation forwarding during DNS lookup, TCP/IP connection or TLS handshake. + (#168, #169, #170, #171, #176 and #177 by @clue) + + All error messages now always contain a reference to the remote URI to give + more details which connection actually failed and the reason for this error. + Accordingly, failures during DNS lookup will now mention both the remote URI + as well as the DNS error reason. TCP/IP connection issues and errors during + a secure TLS handshake will both mention the remote URI as well as the + underlying socket error. Similarly, lost/dropped connections during a TLS + handshake will now report a lost connection instead of an empty error reason. + + For most common use cases this means that simply reporting the `Exception` + message should give the most relevant details for any connection issues: + + ```php + $promise = $connector->connect('tls://example.com:443'); + $promise->then(function (ConnectionInterface $conn) use ($loop) { + // … + }, function (Exception $e) { + echo $e->getMessage(); + }); + ``` + +## 1.0.0 (2018-07-11) + +* First stable LTS release, now following [SemVer](https://semver.org/). + We'd like to emphasize that this component is production ready and battle-tested. + We plan to support all long-term support (LTS) releases for at least 24 months, + so you have a rock-solid foundation to build on top of. + +> Contains no other changes, so it's actually fully compatible with the v0.8.12 release. + +## 0.8.12 (2018-06-11) + +* Feature: Improve memory consumption for failed and cancelled connection attempts. + (#161 by @clue) + +* Improve test suite to fix Travis config to test against legacy PHP 5.3 again. + (#162 by @clue) + +## 0.8.11 (2018-04-24) + +* Feature: Improve memory consumption for cancelled connection attempts and + simplify skipping DNS lookup when connecting to IP addresses. + (#159 and #160 by @clue) + +## 0.8.10 (2018-02-28) + +* Feature: Update DNS dependency to support loading system default DNS + nameserver config on all supported platforms + (`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows) + (#152 by @clue) + + This means that connecting to hosts that are managed by a local DNS server, + such as a corporate DNS server or when using Docker containers, will now + work as expected across all platforms with no changes required: + + ```php + $connector = new Connector($loop); + $connector->connect('intranet.example:80')->then(function ($connection) { + // … + }); + ``` + +## 0.8.9 (2018-01-18) + +* Feature: Support explicitly choosing TLS version to negotiate with remote side + by respecting `crypto_method` context parameter for all classes. + (#149 by @clue) + + By default, all connector and server classes support TLSv1.0+ and exclude + support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly + choose the TLS version you want to negotiate with the remote side: + + ```php + // new: now supports 'crypto_method` context parameter for all classes + $connector = new Connector($loop, array( + 'tls' => array( + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + ) + )); + ``` + +* Minor internal clean up to unify class imports + (#148 by @clue) + +## 0.8.8 (2018-01-06) + +* Improve test suite by adding test group to skip integration tests relying on + internet connection and fix minor documentation typo. + (#146 by @clue and #145 by @cn007b) + +## 0.8.7 (2017-12-24) + +* Fix: Fix closing socket resource before removing from loop + (#141 by @clue) + + This fixes the root cause of an uncaught `Exception` that only manifested + itself after the recent Stream v0.7.4 component update and only if you're + using `ext-event` (`ExtEventLoop`). + +* Improve test suite by testing against PHP 7.2 + (#140 by @carusogabriel) + +## 0.8.6 (2017-11-18) + +* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme + and add advanced `UnixServer` class. + (#120 by @andig) + + ```php + // new: Server now supports "unix://" scheme + $server = new Server('unix:///tmp/server.sock', $loop); + + // new: advanced usage + $server = new UnixServer('/tmp/server.sock', $loop); + ``` + +* Restructure examples to ease getting started + (#136 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and + ignore Mac OS X test failures for now until Travis tests work again + (#133 by @gabriel-caruso and #134 by @clue) + +## 0.8.5 (2017-10-23) + +* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X + (#123 by @andig) + +* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed + (#129 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit v5 and + forward compatibility with upcoming EventLoop releases in tests and + test Mac OS X on Travis + (#122 by @andig and #125, #127 and #130 by @clue) + +* Readme improvements + (#118 by @jsor) + +## 0.8.4 (2017-09-16) + +* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead + (#117 by @clue) + + This can be useful for consumers that do not support certain URIs, such as + when you want to explicitly connect to a Unix domain socket (UDS) path + instead of connecting to a default address assumed by an higher-level API: + + ```php + $connector = new FixedUriConnector( + 'unix:///var/run/docker.sock', + new UnixConnector($loop) + ); + + // destination will be ignored, actually connects to Unix domain socket + $promise = $connector->connect('localhost:80'); + ``` + +## 0.8.3 (2017-09-08) + +* Feature: Reduce memory consumption for failed connections + (#113 by @valga) + +* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14 + (#114 by @clue) + +## 0.8.2 (2017-08-25) + +* Feature: Update DNS dependency to support hosts file on all platforms + (#112 by @clue) + + This means that connecting to hosts such as `localhost` will now work as + expected across all platforms with no changes required: + + ```php + $connector = new Connector($loop); + $connector->connect('localhost:8080')->then(function ($connection) { + // … + }); + ``` + +## 0.8.1 (2017-08-15) + +* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and + target evenement 3.0 a long side 2.0 and 1.0 + (#104 by @clue and #111 by @WyriHaximus) + +* Improve test suite by locking Travis distro so new defaults will not break the build and + fix HHVM build for now again and ignore future HHVM build errors + (#109 and #110 by @clue) + +* Minor documentation fixes + (#103 by @christiaan and #108 by @hansott) + +## 0.8.0 (2017-05-09) + +* Feature: New `Server` class now acts as a facade for existing server classes + and renamed old `Server` to `TcpServer` for advanced usage. + (#96 and #97 by @clue) + + The `Server` class is now the main class in this package that implements the + `ServerInterface` and allows you to accept incoming streaming connections, + such as plaintext TCP/IP or secure TLS connection streams. + + > This is not a BC break and consumer code does not have to be updated. + +* Feature / BC break: All addresses are now URIs that include the URI scheme + (#98 by @clue) + + ```diff + - $parts = parse_url('tcp://' . $conn->getRemoteAddress()); + + $parts = parse_url($conn->getRemoteAddress()); + ``` + +* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths + (#100 by @clue) + +* Feature: Forward compatibility with Stream v1.0 and v0.7 + (#99 by @clue) + +## 0.7.2 (2017-04-24) + +* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs + (#94 by @clue) + +## 0.7.1 (2017-04-10) + +* Fix: Ignore HHVM errors when closing connection that is already closing + (#91 by @clue) + +## 0.7.0 (2017-04-10) + +* Feature: Merge SocketClient component into this component + (#87 by @clue) + + This means that this package now provides async, streaming plaintext TCP/IP + and secure TLS socket server and client connections for ReactPHP. + + ``` + $connector = new React\Socket\Connector($loop); + $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) { + $connection->write('…'); + }); + ``` + + Accordingly, the `ConnectionInterface` is now used to represent both incoming + server side connections as well as outgoing client side connections. + + If you've previously used the SocketClient component to establish outgoing + client connections, upgrading should take no longer than a few minutes. + All classes have been merged as-is from the latest `v0.7.0` release with no + other changes, so you can simply update your code to use the updated namespace + like this: + + ```php + // old from SocketClient component and namespace + $connector = new React\SocketClient\Connector($loop); + $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) { + $connection->write('…'); + }); + + // new + $connector = new React\Socket\Connector($loop); + $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) { + $connection->write('…'); + }); + ``` + +## 0.6.0 (2017-04-04) + +* Feature: Add `LimitingServer` to limit and keep track of open connections + (#86 by @clue) + + ```php + $server = new Server(0, $loop); + $server = new LimitingServer($server, 100); + + $server->on('connection', function (ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … + }); + ``` + +* Feature / BC break: Add `pause()` and `resume()` methods to limit active + connections + (#84 by @clue) + + ```php + $server = new Server(0, $loop); + $server->pause(); + + $loop->addTimer(1.0, function() use ($server) { + $server->resume(); + }); + ``` + +## 0.5.1 (2017-03-09) + +* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6 + (#79 by @clue) + +## 0.5.0 (2017-02-14) + +* Feature / BC break: Replace `listen()` call with URIs passed to constructor + and reject listening on hostnames with `InvalidArgumentException` + and replace `ConnectionException` with `RuntimeException` for consistency + (#61, #66 and #72 by @clue) + + ```php + // old + $server = new Server($loop); + $server->listen(8080); + + // new + $server = new Server(8080, $loop); + ``` + + Similarly, you can now pass a full listening URI to the constructor to change + the listening host: + + ```php + // old + $server = new Server($loop); + $server->listen(8080, '127.0.0.1'); + + // new + $server = new Server('127.0.0.1:8080', $loop); + ``` + + Trying to start listening on (DNS) host names will now throw an + `InvalidArgumentException`, use IP addresses instead: + + ```php + // old + $server = new Server($loop); + $server->listen(8080, 'localhost'); + + // new + $server = new Server('127.0.0.1:8080', $loop); + ``` + + If trying to listen fails (such as if port is already in use or port below + 1024 may require root access etc.), it will now throw a `RuntimeException`, + the `ConnectionException` class has been removed: + + ```php + // old: throws React\Socket\ConnectionException + $server = new Server($loop); + $server->listen(80); + + // new: throws RuntimeException + $server = new Server(80, $loop); + ``` + +* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React + (#62 by @clue) + + ```php + // old + $server->shutdown(); + + // new + $server->close(); + ``` + +* Feature / BC break: Replace `getPort()` with `getAddress()` + (#67 by @clue) + + ```php + // old + echo $server->getPort(); // 8080 + + // new + echo $server->getAddress(); // 127.0.0.1:8080 + ``` + +* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP + (#65 by @clue) + + ```php + // old + echo $connection->getRemoteAddress(); // 192.168.0.1 + + // new + echo $connection->getRemoteAddress(); // 192.168.0.1:51743 + ``` + +* Feature / BC break: Add `getLocalAddress()` method + (#68 by @clue) + + ```php + echo $connection->getLocalAddress(); // 127.0.0.1:8080 + ``` + +* BC break: The `Server` and `SecureServer` class are now marked `final` + and you can no longer `extend` them + (which was never documented or recommended anyway). + Public properties and event handlers are now internal only. + Please use composition instead of extension. + (#71, #70 and #69 by @clue) + +## 0.4.6 (2017-01-26) + +* Feature: Support socket context options passed to `Server` + (#64 by @clue) + +* Fix: Properly return `null` for unknown addresses + (#63 by @clue) + +* Improve documentation for `ServerInterface` and lock test suite requirements + (#60 by @clue, #57 by @shaunbramley) + +## 0.4.5 (2017-01-08) + +* Feature: Add `SecureServer` for secure TLS connections + (#55 by @clue) + +* Add functional integration tests + (#54 by @clue) + +## 0.4.4 (2016-12-19) + +* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation + (#50 by @clue) + +* Feature / Fix: Improve test suite and switch to normal stream handler + (#51 by @clue) + +* Feature: Add examples + (#49 by @clue) + +## 0.4.3 (2016-03-01) + +* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing +* Support for PHP7 and HHVM +* Support PHP 5.3 again + +## 0.4.2 (2014-05-25) + +* Verify stream is a valid resource in Connection + +## 0.4.1 (2014-04-13) + +* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev) +* Bug fix: v0.3.4 changes merged for v0.4.1 + +## 0.3.4 (2014-03-30) + +* Bug fix: Reset socket to non-blocking after shutting down (PHP bug) + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to React/Promise 2.0 +* BC break: Update to Evenement 2.0 +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 +* Bump React dependencies to v0.4 + +## 0.3.3 (2013-07-08) + +* Version bump + +## 0.3.2 (2013-05-10) + +* Version bump + +## 0.3.1 (2013-04-21) + +* Feature: Support binding to IPv6 addresses (@clue) + +## 0.3.0 (2013-04-14) + +* Bump React dependencies to v0.3 + +## 0.2.6 (2012-12-26) + +* Version bump + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.0 (2012-09-10) + +* Bump React dependencies to v0.2 + +## 0.1.1 (2012-07-12) + +* Version bump + +## 0.1.0 (2012-07-11) + +* First tagged release diff --git a/vendor/react/socket/LICENSE b/vendor/react/socket/LICENSE new file mode 100644 index 0000000000..d6f8901f9a --- /dev/null +++ b/vendor/react/socket/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/react/socket/README.md b/vendor/react/socket/README.md new file mode 100644 index 0000000000..e77e676459 --- /dev/null +++ b/vendor/react/socket/README.md @@ -0,0 +1,1564 @@ +# Socket + +[![CI status](https://github.com/reactphp/socket/workflows/CI/badge.svg)](https://github.com/reactphp/socket/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/socket?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/socket) + +Async, streaming plaintext TCP/IP and secure TLS socket server and client +connections for [ReactPHP](https://reactphp.org/). + +The socket library provides re-usable interfaces for a socket-layer +server and client based on the [`EventLoop`](https://github.com/reactphp/event-loop) +and [`Stream`](https://github.com/reactphp/stream) components. +Its server component allows you to build networking servers that accept incoming +connections from networking clients (such as an HTTP server). +Its client component allows you to build networking clients that establish +outgoing connections to networking servers (such as an HTTP or database client). +This library provides async, streaming means for all of this, so you can +handle multiple concurrent connections without blocking. + +**Table of Contents** + +* [Quickstart example](#quickstart-example) +* [Connection usage](#connection-usage) + * [ConnectionInterface](#connectioninterface) + * [getRemoteAddress()](#getremoteaddress) + * [getLocalAddress()](#getlocaladdress) +* [Server usage](#server-usage) + * [ServerInterface](#serverinterface) + * [connection event](#connection-event) + * [error event](#error-event) + * [getAddress()](#getaddress) + * [pause()](#pause) + * [resume()](#resume) + * [close()](#close) + * [SocketServer](#socketserver) + * [Advanced server usage](#advanced-server-usage) + * [TcpServer](#tcpserver) + * [SecureServer](#secureserver) + * [UnixServer](#unixserver) + * [LimitingServer](#limitingserver) + * [getConnections()](#getconnections) +* [Client usage](#client-usage) + * [ConnectorInterface](#connectorinterface) + * [connect()](#connect) + * [Connector](#connector) + * [Advanced client usage](#advanced-client-usage) + * [TcpConnector](#tcpconnector) + * [HappyEyeBallsConnector](#happyeyeballsconnector) + * [DnsConnector](#dnsconnector) + * [SecureConnector](#secureconnector) + * [TimeoutConnector](#timeoutconnector) + * [UnixConnector](#unixconnector) + * [FixUriConnector](#fixeduriconnector) +* [Install](#install) +* [Tests](#tests) +* [License](#license) + +## Quickstart example + +Here is a server that closes the connection if you send it anything: + +```php +$socket = new React\Socket\SocketServer('127.0.0.1:8080'); + +$socket->on('connection', function (React\Socket\ConnectionInterface $connection) { + $connection->write("Hello " . $connection->getRemoteAddress() . "!\n"); + $connection->write("Welcome to this amazing server!\n"); + $connection->write("Here's a tip: don't say anything.\n"); + + $connection->on('data', function ($data) use ($connection) { + $connection->close(); + }); +}); +``` + +See also the [examples](examples). + +Here's a client that outputs the output of said server and then attempts to +send it a string: + +```php +$connector = new React\Socket\Connector(); + +$connector->connect('127.0.0.1:8080')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->pipe(new React\Stream\WritableResourceStream(STDOUT)); + $connection->write("Hello World!\n"); +}, function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +## Connection usage + +### ConnectionInterface + +The `ConnectionInterface` is used to represent any incoming and outgoing +connection, such as a normal TCP/IP connection. + +An incoming or outgoing connection is a duplex stream (both readable and +writable) that implements React's +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). +It contains additional properties for the local and remote address (client IP) +where this connection has been established to/from. + +Most commonly, instances implementing this `ConnectionInterface` are emitted +by all classes implementing the [`ServerInterface`](#serverinterface) and +used by all classes implementing the [`ConnectorInterface`](#connectorinterface). + +Because the `ConnectionInterface` implements the underlying +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface) +you can use any of its events and methods as usual: + +```php +$connection->on('data', function ($chunk) { + echo $chunk; +}); + +$connection->on('end', function () { + echo 'ended'; +}); + +$connection->on('error', function (Exception $e) { + echo 'error: ' . $e->getMessage(); +}); + +$connection->on('close', function () { + echo 'closed'; +}); + +$connection->write($data); +$connection->end($data = null); +$connection->close(); +// … +``` + +For more details, see the +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). + +#### getRemoteAddress() + +The `getRemoteAddress(): ?string` method returns the full remote address +(URI) where this connection has been established with. + +```php +$address = $connection->getRemoteAddress(); +echo 'Connection with ' . $address . PHP_EOL; +``` + +If the remote address can not be determined or is unknown at this time (such as +after the connection has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full address (URI) as a string value, such +as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, +`unix://example.sock` or `unix:///path/to/example.sock`. +Note that individual URI components are application specific and depend +on the underlying transport protocol. + +If this is a TCP/IP based connection and you only want the remote IP, you may +use something like this: + +```php +$address = $connection->getRemoteAddress(); +$ip = trim(parse_url($address, PHP_URL_HOST), '[]'); +echo 'Connection with ' . $ip . PHP_EOL; +``` + +#### getLocalAddress() + +The `getLocalAddress(): ?string` method returns the full local address +(URI) where this connection has been established with. + +```php +$address = $connection->getLocalAddress(); +echo 'Connection with ' . $address . PHP_EOL; +``` + +If the local address can not be determined or is unknown at this time (such as +after the connection has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full address (URI) as a string value, such +as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, +`unix://example.sock` or `unix:///path/to/example.sock`. +Note that individual URI components are application specific and depend +on the underlying transport protocol. + +This method complements the [`getRemoteAddress()`](#getremoteaddress) method, +so they should not be confused. + +If your `TcpServer` instance is listening on multiple interfaces (e.g. using +the address `0.0.0.0`), you can use this method to find out which interface +actually accepted this connection (such as a public or local interface). + +If your system has multiple interfaces (e.g. a WAN and a LAN interface), +you can use this method to find out which interface was actually +used for this connection. + +## Server usage + +### ServerInterface + +The `ServerInterface` is responsible for providing an interface for accepting +incoming streaming connections, such as a normal TCP/IP connection. + +Most higher-level components (such as a HTTP server) accept an instance +implementing this interface to accept incoming streaming connections. +This is usually done via dependency injection, so it's fairly simple to actually +swap this implementation against any other implementation of this interface. +This means that you SHOULD typehint against this interface instead of a concrete +implementation of this interface. + +Besides defining a few methods, this interface also implements the +[`EventEmitterInterface`](https://github.com/igorw/evenement) +which allows you to react to certain events. + +#### connection event + +The `connection` event will be emitted whenever a new connection has been +established, i.e. a new client connects to this server socket: + +```php +$socket->on('connection', function (React\Socket\ConnectionInterface $connection) { + echo 'new connection' . PHP_EOL; +}); +``` + +See also the [`ConnectionInterface`](#connectioninterface) for more details +about handling the incoming connection. + +#### error event + +The `error` event will be emitted whenever there's an error accepting a new +connection from a client. + +```php +$socket->on('error', function (Exception $e) { + echo 'error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +Note that this is not a fatal error event, i.e. the server keeps listening for +new connections even after this event. + +#### getAddress() + +The `getAddress(): ?string` method can be used to +return the full address (URI) this server is currently listening on. + +```php +$address = $socket->getAddress(); +echo 'Server listening on ' . $address . PHP_EOL; +``` + +If the address can not be determined or is unknown at this time (such as +after the socket has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full address (URI) as a string value, such +as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443` +`unix://example.sock` or `unix:///path/to/example.sock`. +Note that individual URI components are application specific and depend +on the underlying transport protocol. + +If this is a TCP/IP based server and you only want the local port, you may +use something like this: + +```php +$address = $socket->getAddress(); +$port = parse_url($address, PHP_URL_PORT); +echo 'Server listening on port ' . $port . PHP_EOL; +``` + +#### pause() + +The `pause(): void` method can be used to +pause accepting new incoming connections. + +Removes the socket resource from the EventLoop and thus stop accepting +new connections. Note that the listening socket stays active and is not +closed. + +This means that new incoming connections will stay pending in the +operating system backlog until its configurable backlog is filled. +Once the backlog is filled, the operating system may reject further +incoming connections until the backlog is drained again by resuming +to accept new connections. + +Once the server is paused, no futher `connection` events SHOULD +be emitted. + +```php +$socket->pause(); + +$socket->on('connection', assertShouldNeverCalled()); +``` + +This method is advisory-only, though generally not recommended, the +server MAY continue emitting `connection` events. + +Unless otherwise noted, a successfully opened server SHOULD NOT start +in paused state. + +You can continue processing events by calling `resume()` again. + +Note that both methods can be called any number of times, in particular +calling `pause()` more than once SHOULD NOT have any effect. +Similarly, calling this after `close()` is a NO-OP. + +#### resume() + +The `resume(): void` method can be used to +resume accepting new incoming connections. + +Re-attach the socket resource to the EventLoop after a previous `pause()`. + +```php +$socket->pause(); + +Loop::addTimer(1.0, function () use ($socket) { + $socket->resume(); +}); +``` + +Note that both methods can be called any number of times, in particular +calling `resume()` without a prior `pause()` SHOULD NOT have any effect. +Similarly, calling this after `close()` is a NO-OP. + +#### close() + +The `close(): void` method can be used to +shut down this listening socket. + +This will stop listening for new incoming connections on this socket. + +```php +echo 'Shutting down server socket' . PHP_EOL; +$socket->close(); +``` + +Calling this method more than once on the same instance is a NO-OP. + +### SocketServer + + + +The `SocketServer` class is the main class in this package that implements the +[`ServerInterface`](#serverinterface) and allows you to accept incoming +streaming connections, such as plaintext TCP/IP or secure TLS connection streams. + +In order to accept plaintext TCP/IP connections, you can simply pass a host +and port combination like this: + +```php +$socket = new React\Socket\SocketServer('127.0.0.1:8080'); +``` + +Listening on the localhost address `127.0.0.1` means it will not be reachable from +outside of this system. +In order to change the host the socket is listening on, you can provide an IP +address of an interface or use the special `0.0.0.0` address to listen on all +interfaces: + +```php +$socket = new React\Socket\SocketServer('0.0.0.0:8080'); +``` + +If you want to listen on an IPv6 address, you MUST enclose the host in square +brackets: + +```php +$socket = new React\Socket\SocketServer('[::1]:8080'); +``` + +In order to use a random port assignment, you can use the port `0`: + +```php +$socket = new React\Socket\SocketServer('127.0.0.1:0'); +$address = $socket->getAddress(); +``` + +To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the +`unix://` scheme: + +```php +$socket = new React\Socket\SocketServer('unix:///tmp/server.sock'); +``` + +In order to listen on an existing file descriptor (FD) number, you MUST prefix +the URI with `php://fd/` like this: + +```php +$socket = new React\Socket\SocketServer('php://fd/3'); +``` + +If the given URI is invalid, does not contain a port, any other scheme or if it +contains a hostname, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException due to missing port +$socket = new React\Socket\SocketServer('127.0.0.1'); +``` + +If the given URI appears to be valid, but listening on it fails (such as if port +is already in use or port below 1024 may require root access etc.), it will +throw a `RuntimeException`: + +```php +$first = new React\Socket\SocketServer('127.0.0.1:8080'); + +// throws RuntimeException because port is already in use +$second = new React\Socket\SocketServer('127.0.0.1:8080'); +``` + +> Note that these error conditions may vary depending on your system and/or + configuration. + See the exception message and code for more details about the actual error + condition. + +Optionally, you can specify [TCP socket context options](https://www.php.net/manual/en/context.socket.php) +for the underlying stream socket resource like this: + +```php +$socket = new React\Socket\SocketServer('[::1]:8080', array( + 'tcp' => array( + 'backlog' => 200, + 'so_reuseport' => true, + 'ipv6_v6only' => true + ) +)); +``` + +> Note that available [socket context options](https://www.php.net/manual/en/context.socket.php), + their defaults and effects of changing these may vary depending on your system + and/or PHP version. + Passing unknown context options has no effect. + The `backlog` context option defaults to `511` unless given explicitly. + +You can start a secure TLS (formerly known as SSL) server by simply prepending +the `tls://` URI scheme. +Internally, it will wait for plaintext TCP/IP connections and then performs a +TLS handshake for each connection. +It thus requires valid [TLS context options](https://www.php.net/manual/en/context.ssl.php), +which in its most basic form may look something like this if you're using a +PEM encoded certificate file: + +```php +$socket = new React\Socket\SocketServer('tls://127.0.0.1:8080', array( + 'tls' => array( + 'local_cert' => 'server.pem' + ) +)); +``` + +> Note that the certificate file will not be loaded on instantiation but when an + incoming connection initializes its TLS context. + This implies that any invalid certificate file paths or contents will only cause + an `error` event at a later time. + +If your private key is encrypted with a passphrase, you have to specify it +like this: + +```php +$socket = new React\Socket\SocketServer('tls://127.0.0.1:8000', array( + 'tls' => array( + 'local_cert' => 'server.pem', + 'passphrase' => 'secret' + ) +)); +``` + +By default, this server supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$socket = new React\Socket\SocketServer('tls://127.0.0.1:8000', array( + 'tls' => array( + 'local_cert' => 'server.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER + ) +)); +``` + +> Note that available [TLS context options](https://www.php.net/manual/en/context.ssl.php), + their defaults and effects of changing these may vary depending on your system + and/or PHP version. + The outer context array allows you to also use `tcp` (and possibly more) + context options at the same time. + Passing unknown context options has no effect. + If you do not use the `tls://` scheme, then passing `tls` context options + has no effect. + +Whenever a client connects, it will emit a `connection` event with a connection +instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$socket->on('connection', function (React\Socket\ConnectionInterface $connection) { + echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +> Note that the `SocketServer` class is a concrete implementation for TCP/IP sockets. + If you want to typehint in your higher-level protocol implementation, you SHOULD + use the generic [`ServerInterface`](#serverinterface) instead. + +> Changelog v1.9.0: This class has been added with an improved constructor signature + as a replacement for the previous `Server` class in order to avoid any ambiguities. + The previous name has been deprecated and should not be used anymore. + +### Advanced server usage + +#### TcpServer + +The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and +is responsible for accepting plaintext TCP/IP connections. + +```php +$server = new React\Socket\TcpServer(8080); +``` + +As above, the `$uri` parameter can consist of only a port, in which case the +server will default to listening on the localhost address `127.0.0.1`, +which means it will not be reachable from outside of this system. + +In order to use a random port assignment, you can use the port `0`: + +```php +$server = new React\Socket\TcpServer(0); +$address = $server->getAddress(); +``` + +In order to change the host the socket is listening on, you can provide an IP +address through the first parameter provided to the constructor, optionally +preceded by the `tcp://` scheme: + +```php +$server = new React\Socket\TcpServer('192.168.0.1:8080'); +``` + +If you want to listen on an IPv6 address, you MUST enclose the host in square +brackets: + +```php +$server = new React\Socket\TcpServer('[::1]:8080'); +``` + +If the given URI is invalid, does not contain a port, any other scheme or if it +contains a hostname, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException due to missing port +$server = new React\Socket\TcpServer('127.0.0.1'); +``` + +If the given URI appears to be valid, but listening on it fails (such as if port +is already in use or port below 1024 may require root access etc.), it will +throw a `RuntimeException`: + +```php +$first = new React\Socket\TcpServer(8080); + +// throws RuntimeException because port is already in use +$second = new React\Socket\TcpServer(8080); +``` + +> Note that these error conditions may vary depending on your system and/or +configuration. +See the exception message and code for more details about the actual error +condition. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php) +for the underlying stream socket resource like this: + +```php +$server = new React\Socket\TcpServer('[::1]:8080', null, array( + 'backlog' => 200, + 'so_reuseport' => true, + 'ipv6_v6only' => true +)); +``` + +> Note that available [socket context options](https://www.php.net/manual/en/context.socket.php), +their defaults and effects of changing these may vary depending on your system +and/or PHP version. +Passing unknown context options has no effect. +The `backlog` context option defaults to `511` unless given explicitly. + +Whenever a client connects, it will emit a `connection` event with a connection +instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (React\Socket\ConnectionInterface $connection) { + echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +#### SecureServer + +The `SecureServer` class implements the [`ServerInterface`](#serverinterface) +and is responsible for providing a secure TLS (formerly known as SSL) server. + +It does so by wrapping a [`TcpServer`](#tcpserver) instance which waits for plaintext +TCP/IP connections and then performs a TLS handshake for each connection. +It thus requires valid [TLS context options](https://www.php.net/manual/en/context.ssl.php), +which in its most basic form may look something like this if you're using a +PEM encoded certificate file: + +```php +$server = new React\Socket\TcpServer(8000); +$server = new React\Socket\SecureServer($server, null, array( + 'local_cert' => 'server.pem' +)); +``` + +> Note that the certificate file will not be loaded on instantiation but when an +incoming connection initializes its TLS context. +This implies that any invalid certificate file paths or contents will only cause +an `error` event at a later time. + +If your private key is encrypted with a passphrase, you have to specify it +like this: + +```php +$server = new React\Socket\TcpServer(8000); +$server = new React\Socket\SecureServer($server, null, array( + 'local_cert' => 'server.pem', + 'passphrase' => 'secret' +)); +``` + +By default, this server supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$server = new React\Socket\TcpServer(8000); +$server = new React\Socket\SecureServer($server, null, array( + 'local_cert' => 'server.pem', + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER +)); +``` + +> Note that available [TLS context options](https://www.php.net/manual/en/context.ssl.php), +their defaults and effects of changing these may vary depending on your system +and/or PHP version. +Passing unknown context options has no effect. + +Whenever a client completes the TLS handshake, it will emit a `connection` event +with a connection instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (React\Socket\ConnectionInterface $connection) { + echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +Whenever a client fails to perform a successful TLS handshake, it will emit an +`error` event and then close the underlying TCP/IP connection: + +```php +$server->on('error', function (Exception $e) { + echo 'Error' . $e->getMessage() . PHP_EOL; +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +Note that the `SecureServer` class is a concrete implementation for TLS sockets. +If you want to typehint in your higher-level protocol implementation, you SHOULD +use the generic [`ServerInterface`](#serverinterface) instead. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +> Advanced usage: Despite allowing any `ServerInterface` as first parameter, +you SHOULD pass a `TcpServer` instance as first parameter, unless you +know what you're doing. +Internally, the `SecureServer` has to set the required TLS context options on +the underlying stream resources. +These resources are not exposed through any of the interfaces defined in this +package, but only through the internal `Connection` class. +The `TcpServer` class is guaranteed to emit connections that implement +the `ConnectionInterface` and uses the internal `Connection` class in order to +expose these underlying resources. +If you use a custom `ServerInterface` and its `connection` event does not +meet this requirement, the `SecureServer` will emit an `error` event and +then close the underlying connection. + +#### UnixServer + +The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and +is responsible for accepting connections on Unix domain sockets (UDS). + +```php +$server = new React\Socket\UnixServer('/tmp/server.sock'); +``` + +As above, the `$uri` parameter can consist of only a socket path or socket path +prefixed by the `unix://` scheme. + +If the given URI appears to be valid, but listening on it fails (such as if the +socket is already in use or the file not accessible etc.), it will throw a +`RuntimeException`: + +```php +$first = new React\Socket\UnixServer('/tmp/same.sock'); + +// throws RuntimeException because socket is already in use +$second = new React\Socket\UnixServer('/tmp/same.sock'); +``` + +> Note that these error conditions may vary depending on your system and/or + configuration. + In particular, Zend PHP does only report "Unknown error" when the UDS path + already exists and can not be bound. You may want to check `is_file()` on the + given UDS path to report a more user-friendly error message in this case. + See the exception message and code for more details about the actual error + condition. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +Whenever a client connects, it will emit a `connection` event with a connection +instance implementing [`ConnectionInterface`](#connectioninterface): + +```php +$server->on('connection', function (React\Socket\ConnectionInterface $connection) { + echo 'New connection' . PHP_EOL; + + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [`ServerInterface`](#serverinterface) for more details. + +#### LimitingServer + +The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible +for limiting and keeping track of open connections to this server instance. + +Whenever the underlying server emits a `connection` event, it will check its +limits and then either + - keep track of this connection by adding it to the list of + open connections and then forward the `connection` event + - or reject (close) the connection when its limits are exceeded and will + forward an `error` event instead. + +Whenever a connection closes, it will remove this connection from the list of +open connections. + +```php +$server = new React\Socket\LimitingServer($server, 100); +$server->on('connection', function (React\Socket\ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +See also the [second example](examples) for more details. + +You have to pass a maximum number of open connections to ensure +the server will automatically reject (close) connections once this limit +is exceeded. In this case, it will emit an `error` event to inform about +this and no `connection` event will be emitted. + +```php +$server = new React\Socket\LimitingServer($server, 100); +$server->on('connection', function (React\Socket\ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +You MAY pass a `null` limit in order to put no limit on the number of +open connections and keep accepting new connection until you run out of +operating system resources (such as open file handles). This may be +useful if you do not want to take care of applying a limit but still want +to use the `getConnections()` method. + +You can optionally configure the server to pause accepting new +connections once the connection limit is reached. In this case, it will +pause the underlying server and no longer process any new connections at +all, thus also no longer closing any excessive connections. +The underlying operating system is responsible for keeping a backlog of +pending connections until its limit is reached, at which point it will +start rejecting further connections. +Once the server is below the connection limit, it will continue consuming +connections from the backlog and will process any outstanding data on +each connection. +This mode may be useful for some protocols that are designed to wait for +a response message (such as HTTP), but may be less useful for other +protocols that demand immediate responses (such as a "welcome" message in +an interactive chat). + +```php +$server = new React\Socket\LimitingServer($server, 100, true); +$server->on('connection', function (React\Socket\ConnectionInterface $connection) { + $connection->write('hello there!' . PHP_EOL); + … +}); +``` + +##### getConnections() + +The `getConnections(): ConnectionInterface[]` method can be used to +return an array with all currently active connections. + +```php +foreach ($server->getConnection() as $connection) { + $connection->write('Hi!'); +} +``` + +## Client usage + +### ConnectorInterface + +The `ConnectorInterface` is responsible for providing an interface for +establishing streaming connections, such as a normal TCP/IP connection. + +This is the main interface defined in this package and it is used throughout +React's vast ecosystem. + +Most higher-level components (such as HTTP, database or other networking +service clients) accept an instance implementing this interface to create their +TCP/IP connection to the underlying networking service. +This is usually done via dependency injection, so it's fairly simple to actually +swap this implementation against any other implementation of this interface. + +The interface only offers a single method: + +#### connect() + +The `connect(string $uri): PromiseInterface` method can be used to +create a streaming connection to the given remote address. + +It returns a [Promise](https://github.com/reactphp/promise) which either +fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface) +on success or rejects with an `Exception` if the connection is not successful: + +```php +$connector->connect('google.com:443')->then( + function (React\Socket\ConnectionInterface $connection) { + // connection successfully established + }, + function (Exception $error) { + // failed to connect due to $error + } +); +``` + +See also [`ConnectionInterface`](#connectioninterface) for more details. + +The returned Promise MUST be implemented in such a way that it can be +cancelled when it is still pending. Cancelling a pending promise MUST +reject its value with an `Exception`. It SHOULD clean up any underlying +resources and references as applicable: + +```php +$promise = $connector->connect($uri); + +$promise->cancel(); +``` + +### Connector + +The `Connector` class is the main class in this package that implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create streaming connections. + +You can use this connector to create any kind of streaming connections, such +as plaintext TCP/IP, secure TLS or local Unix connection streams. + +It binds to the main event loop and can be used like this: + +```php +$connector = new React\Socket\Connector(); + +$connector->connect($uri)->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}, function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +In order to create a plaintext TCP/IP connection, you can simply pass a host +and port combination like this: + +```php +$connector->connect('www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +> If you do no specify a URI scheme in the destination URI, it will assume + `tcp://` as a default and establish a plaintext TCP/IP connection. + Note that TCP/IP connections require a host and port part in the destination + URI like above, all other URI components are optional. + +In order to create a secure TLS connection, you can use the `tls://` URI scheme +like this: + +```php +$connector->connect('tls://www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +In order to create a local Unix domain socket connection, you can use the +`unix://` URI scheme like this: + +```php +$connector->connect('unix:///tmp/demo.sock')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +> The [`getRemoteAddress()`](#getremoteaddress) method will return the target + Unix domain socket (UDS) path as given to the `connect()` method, including + the `unix://` scheme, for example `unix:///tmp/demo.sock`. + The [`getLocalAddress()`](#getlocaladdress) method will most likely return a + `null` value as this value is not applicable to UDS connections here. + +Under the hood, the `Connector` is implemented as a *higher-level facade* +for the lower-level connectors implemented in this package. This means it +also shares all of their features and implementation details. +If you want to typehint in your higher-level protocol implementation, you SHOULD +use the generic [`ConnectorInterface`](#connectorinterface) instead. + +As of `v1.4.0`, the `Connector` class defaults to using the +[happy eyeballs algorithm](https://en.wikipedia.org/wiki/Happy_Eyeballs) to +automatically connect over IPv4 or IPv6 when a hostname is given. +This automatically attempts to connect using both IPv4 and IPv6 at the same time +(preferring IPv6), thus avoiding the usual problems faced by users with imperfect +IPv6 connections or setups. +If you want to revert to the old behavior of only doing an IPv4 lookup and +only attempt a single IPv4 connection, you can set up the `Connector` like this: + +```php +$connector = new React\Socket\Connector(array( + 'happy_eyeballs' => false +)); +``` + +Similarly, you can also affect the default DNS behavior as follows. +The `Connector` class will try to detect your system DNS settings (and uses +Google's public DNS server `8.8.8.8` as a fallback if unable to determine your +system settings) to resolve all public hostnames into underlying IP addresses by +default. +If you explicitly want to use a custom DNS server (such as a local DNS relay or +a company wide DNS server), you can set up the `Connector` like this: + +```php +$connector = new React\Socket\Connector(array( + 'dns' => '127.0.1.1' +)); + +$connector->connect('localhost:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +If you do not want to use a DNS resolver at all and want to connect to IP +addresses only, you can also set up your `Connector` like this: + +```php +$connector = new React\Socket\Connector(array( + 'dns' => false +)); + +$connector->connect('127.0.0.1:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +Advanced: If you need a custom DNS `React\Dns\Resolver\ResolverInterface` instance, you +can also set up your `Connector` like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$resolver = $dnsResolverFactory->createCached('127.0.1.1'); + +$connector = new React\Socket\Connector(array( + 'dns' => $resolver +)); + +$connector->connect('localhost:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +By default, the `tcp://` and `tls://` URI schemes will use timeout value that +respects your `default_socket_timeout` ini setting (which defaults to 60s). +If you want a custom timeout value, you can simply pass this like this: + +```php +$connector = new React\Socket\Connector(array( + 'timeout' => 10.0 +)); +``` + +Similarly, if you do not want to apply a timeout at all and let the operating +system handle this, you can pass a boolean flag like this: + +```php +$connector = new React\Socket\Connector(array( + 'timeout' => false +)); +``` + +By default, the `Connector` supports the `tcp://`, `tls://` and `unix://` +URI schemes. If you want to explicitly prohibit any of these, you can simply +pass boolean flags like this: + +```php +// only allow secure TLS connections +$connector = new React\Socket\Connector(array( + 'tcp' => false, + 'tls' => true, + 'unix' => false, +)); + +$connector->connect('tls://google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +The `tcp://` and `tls://` also accept additional context options passed to +the underlying connectors. +If you want to explicitly pass additional context options, you can simply +pass arrays of context options like this: + +```php +// allow insecure TLS connections +$connector = new React\Socket\Connector(array( + 'tcp' => array( + 'bindto' => '192.168.0.1:0' + ), + 'tls' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + ), +)); + +$connector->connect('tls://localhost:443')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +By default, this connector supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$connector = new React\Socket\Connector(array( + 'tls' => array( + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + ) +)); +``` + +> For more details about context options, please refer to the PHP documentation + about [socket context options](https://www.php.net/manual/en/context.socket.php) + and [SSL context options](https://www.php.net/manual/en/context.ssl.php). + +Advanced: By default, the `Connector` supports the `tcp://`, `tls://` and +`unix://` URI schemes. +For this, it sets up the required connector classes automatically. +If you want to explicitly pass custom connectors for any of these, you can simply +pass an instance implementing the `ConnectorInterface` like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$resolver = $dnsResolverFactory->createCached('127.0.1.1'); +$tcp = new React\Socket\HappyEyeBallsConnector(null, new React\Socket\TcpConnector(), $resolver); + +$tls = new React\Socket\SecureConnector($tcp); + +$unix = new React\Socket\UnixConnector(); + +$connector = new React\Socket\Connector(array( + 'tcp' => $tcp, + 'tls' => $tls, + 'unix' => $unix, + + 'dns' => false, + 'timeout' => false, +)); + +$connector->connect('google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +> Internally, the `tcp://` connector will always be wrapped by the DNS resolver, + unless you disable DNS like in the above example. In this case, the `tcp://` + connector receives the actual hostname instead of only the resolved IP address + and is thus responsible for performing the lookup. + Internally, the automatically created `tls://` connector will always wrap the + underlying `tcp://` connector for establishing the underlying plaintext + TCP/IP connection before enabling secure TLS mode. If you want to use a custom + underlying `tcp://` connector for secure TLS connections only, you may + explicitly pass a `tls://` connector like above instead. + Internally, the `tcp://` and `tls://` connectors will always be wrapped by + `TimeoutConnector`, unless you disable timeouts like in the above example. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +> Changelog v1.9.0: The constructur signature has been updated to take the +> optional `$context` as the first parameter and the optional `$loop` as a second +> argument. The previous signature has been deprecated and should not be used anymore. +> +> ```php +> // constructor signature as of v1.9.0 +> $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null); +> +> // legacy constructor signature before v1.9.0 +> $connector = new React\Socket\Connector(?LoopInterface $loop = null, array $context = []); +> ``` + +### Advanced client usage + +#### TcpConnector + +The `TcpConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext +TCP/IP connections to any IP-port-combination: + +```php +$tcpConnector = new React\Socket\TcpConnector(); + +$tcpConnector->connect('127.0.0.1:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $tcpConnector->connect('127.0.0.1:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will close the underlying socket +resource, thus cancelling the pending TCP/IP connection, and reject the +resulting promise. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +You can optionally pass additional +[socket context options](https://www.php.net/manual/en/context.socket.php) +to the constructor like this: + +```php +$tcpConnector = new React\Socket\TcpConnector(null, array( + 'bindto' => '192.168.0.1:0' +)); +``` + +Note that this class only allows you to connect to IP-port-combinations. +If the given URI is invalid, does not contain a valid IP address and port +or contains any other scheme, it will reject with an +`InvalidArgumentException`: + +If the given URI appears to be valid, but connecting to it fails (such as if +the remote host rejects the connection etc.), it will reject with a +`RuntimeException`. + +If you want to connect to hostname-port-combinations, see also the following chapter. + +> Advanced usage: Internally, the `TcpConnector` allocates an empty *context* +resource for each stream resource. +If the destination URI contains a `hostname` query parameter, its value will +be used to set up the TLS peer name. +This is used by the `SecureConnector` and `DnsConnector` to verify the peer +name and can also be used if you want a custom TLS peer name. + +#### HappyEyeBallsConnector + +The `HappyEyeBallsConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext +TCP/IP connections to any hostname-port-combination. Internally it implements the +happy eyeballs algorithm from [`RFC6555`](https://tools.ietf.org/html/rfc6555) and +[`RFC8305`](https://tools.ietf.org/html/rfc8305) to support IPv6 and IPv4 hostnames. + +It does so by decorating a given `TcpConnector` instance so that it first +looks up the given domain name via DNS (if applicable) and then establishes the +underlying TCP/IP connection to the resolved target IP address. + +Make sure to set up your DNS resolver and underlying TCP connector like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$dns = $dnsResolverFactory->createCached('8.8.8.8'); + +$dnsConnector = new React\Socket\HappyEyeBallsConnector(null, $tcpConnector, $dns); + +$dnsConnector->connect('www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $dnsConnector->connect('www.google.com:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying DNS lookups +and/or the underlying TCP/IP connection(s) and reject the resulting promise. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +> Advanced usage: Internally, the `HappyEyeBallsConnector` relies on a `Resolver` to +look up the IP addresses for the given hostname. +It will then replace the hostname in the destination URI with this IP's and +append a `hostname` query parameter and pass this updated URI to the underlying +connector. +The Happy Eye Balls algorithm describes looking the IPv6 and IPv4 address for +the given hostname so this connector sends out two DNS lookups for the A and +AAAA records. It then uses all IP addresses (both v6 and v4) and tries to +connect to all of them with a 50ms interval in between. Alterating between IPv6 +and IPv4 addresses. When a connection is established all the other DNS lookups +and connection attempts are cancelled. + +#### DnsConnector + +The `DnsConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext +TCP/IP connections to any hostname-port-combination. + +It does so by decorating a given `TcpConnector` instance so that it first +looks up the given domain name via DNS (if applicable) and then establishes the +underlying TCP/IP connection to the resolved target IP address. + +Make sure to set up your DNS resolver and underlying TCP connector like this: + +```php +$dnsResolverFactory = new React\Dns\Resolver\Factory(); +$dns = $dnsResolverFactory->createCached('8.8.8.8'); + +$dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns); + +$dnsConnector->connect('www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write('...'); + $connection->end(); +}); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $dnsConnector->connect('www.google.com:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying DNS lookup +and/or the underlying TCP/IP connection and reject the resulting promise. + +> Advanced usage: Internally, the `DnsConnector` relies on a `React\Dns\Resolver\ResolverInterface` +to look up the IP address for the given hostname. +It will then replace the hostname in the destination URI with this IP and +append a `hostname` query parameter and pass this updated URI to the underlying +connector. +The underlying connector is thus responsible for creating a connection to the +target IP address, while this query parameter can be used to check the original +hostname and is used by the `TcpConnector` to set up the TLS peer name. +If a `hostname` is given explicitly, this query parameter will not be modified, +which can be useful if you want a custom TLS peer name. + +#### SecureConnector + +The `SecureConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to create secure +TLS (formerly known as SSL) connections to any hostname-port-combination. + +It does so by decorating a given `DnsConnector` instance so that it first +creates a plaintext TCP/IP connection and then enables TLS encryption on this +stream. + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector); + +$secureConnector->connect('www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); + ... +}); +``` + +See also the [examples](examples). + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $secureConnector->connect('www.google.com:443'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying TCP/IP +connection and/or the SSL/TLS negotiation and reject the resulting promise. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +You can optionally pass additional +[SSL context options](https://www.php.net/manual/en/context.ssl.php) +to the constructor like this: + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector, null, array( + 'verify_peer' => false, + 'verify_peer_name' => false +)); +``` + +By default, this connector supports TLSv1.0+ and excludes support for legacy +SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you +want to negotiate with the remote side: + +```php +$secureConnector = new React\Socket\SecureConnector($dnsConnector, null, array( + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT +)); +``` + +> Advanced usage: Internally, the `SecureConnector` relies on setting up the +required *context options* on the underlying stream resource. +It should therefor be used with a `TcpConnector` somewhere in the connector +stack so that it can allocate an empty *context* resource for each stream +resource and verify the peer name. +Failing to do so may result in a TLS peer name mismatch error or some hard to +trace race conditions, because all stream resources will use a single, shared +*default context* resource otherwise. + +#### TimeoutConnector + +The `TimeoutConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to add timeout +handling to any existing connector instance. + +It does so by decorating any given [`ConnectorInterface`](#connectorinterface) +instance and starting a timer that will automatically reject and abort any +underlying connection attempt if it takes too long. + +```php +$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0); + +$timeoutConnector->connect('google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { + // connection succeeded within 3.0 seconds +}); +``` + +See also any of the [examples](examples). + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +Pending connection attempts can be cancelled by cancelling its pending promise like so: + +```php +$promise = $timeoutConnector->connect('google.com:80'); + +$promise->cancel(); +``` + +Calling `cancel()` on a pending promise will cancel the underlying connection +attempt, abort the timer and reject the resulting promise. + +#### UnixConnector + +The `UnixConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and allows you to connect to +Unix domain socket (UDS) paths like this: + +```php +$connector = new React\Socket\UnixConnector(); + +$connector->connect('/tmp/demo.sock')->then(function (React\Socket\ConnectionInterface $connection) { + $connection->write("HELLO\n"); +}); +``` + +Connecting to Unix domain sockets is an atomic operation, i.e. its promise will +settle (either resolve or reject) immediately. +As such, calling `cancel()` on the resulting promise has no effect. + +> The [`getRemoteAddress()`](#getremoteaddress) method will return the target + Unix domain socket (UDS) path as given to the `connect()` method, prepended + with the `unix://` scheme, for example `unix:///tmp/demo.sock`. + The [`getLocalAddress()`](#getlocaladdress) method will most likely return a + `null` value as this value is not applicable to UDS connections here. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +#### FixedUriConnector + +The `FixedUriConnector` class implements the +[`ConnectorInterface`](#connectorinterface) and decorates an existing Connector +to always use a fixed, preconfigured URI. + +This can be useful for consumers that do not support certain URIs, such as +when you want to explicitly connect to a Unix domain socket (UDS) path +instead of connecting to a default address assumed by an higher-level API: + +```php +$connector = new React\Socket\FixedUriConnector( + 'unix:///var/run/docker.sock', + new React\Socket\UnixConnector() +); + +// destination will be ignored, actually connects to Unix domain socket +$promise = $connector->connect('localhost:80'); +``` + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org/). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require react/socket:^1.16 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. +It's *highly recommended to use the latest supported PHP version* for this project, +partly due to its vast performance improvements and partly because legacy PHP +versions require several workarounds as described below. + +Secure TLS connections received some major upgrades starting with PHP 5.6, with +the defaults now being more secure, while older versions required explicit +context options. +This library does not take responsibility over these context options, so it's +up to consumers of this library to take care of setting appropriate context +options as described above. + +PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might +block with 100% CPU usage on fragmented TLS records. +We try to work around this by always consuming the complete receive +buffer at once to avoid stale data in TLS buffers. This is known to +work around high CPU usage for well-behaving peers, but this may +cause very large data chunks for high throughput scenarios. The buggy +behavior can still be triggered due to network I/O buffers or +malicious peers on affected versions, upgrading is highly recommended. + +PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big +chunks of data over TLS streams at once. +We try to work around this by limiting the write chunk size to 8192 +bytes for older PHP versions only. +This is only a work-around and has a noticable performance penalty on +affected versions. + +This project also supports running on HHVM. +Note that really old HHVM < 3.8 does not support secure TLS connections, as it +lacks the required `stream_socket_enable_crypto()` function. +As such, trying to create a secure TLS connections on affected versions will +return a rejected promise instead. +This issue is also covered by our test suite, which will skip related tests +on affected versions. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org/): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). diff --git a/vendor/react/socket/composer.json b/vendor/react/socket/composer.json new file mode 100644 index 0000000000..b1e1d25359 --- /dev/null +++ b/vendor/react/socket/composer.json @@ -0,0 +1,52 @@ +{ + "name": "react/socket", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": ["async", "socket", "stream", "connection", "ReactPHP"], + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Socket\\": "tests/" + } + } +} diff --git a/vendor/react/socket/src/Connection.php b/vendor/react/socket/src/Connection.php new file mode 100644 index 0000000000..65ae26b48c --- /dev/null +++ b/vendor/react/socket/src/Connection.php @@ -0,0 +1,183 @@ += 70300 && \PHP_VERSION_ID < 70303); + + // PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big + // chunks of data over TLS streams at once. + // We try to work around this by limiting the write chunk size to 8192 + // bytes for older PHP versions only. + // This is only a work-around and has a noticable performance penalty on + // affected versions. Please update your PHP version. + // This applies to all streams because TLS may be enabled later on. + // See https://github.com/reactphp/socket/issues/105 + $limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104)); + + $this->input = new DuplexResourceStream( + $resource, + $loop, + $clearCompleteBuffer ? -1 : null, + new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null) + ); + + $this->stream = $resource; + + Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain')); + + $this->input->on('close', array($this, 'close')); + } + + public function isReadable() + { + return $this->input->isReadable(); + } + + public function isWritable() + { + return $this->input->isWritable(); + } + + public function pause() + { + $this->input->pause(); + } + + public function resume() + { + $this->input->resume(); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return $this->input->pipe($dest, $options); + } + + public function write($data) + { + return $this->input->write($data); + } + + public function end($data = null) + { + $this->input->end($data); + } + + public function close() + { + $this->input->close(); + $this->handleClose(); + $this->removeAllListeners(); + } + + public function handleClose() + { + if (!\is_resource($this->stream)) { + return; + } + + // Try to cleanly shut down socket and ignore any errors in case other + // side already closed. Underlying Stream implementation will take care + // of closing stream resource, so we otherwise keep this open here. + @\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR); + } + + public function getRemoteAddress() + { + if (!\is_resource($this->stream)) { + return null; + } + + return $this->parseAddress(\stream_socket_get_name($this->stream, true)); + } + + public function getLocalAddress() + { + if (!\is_resource($this->stream)) { + return null; + } + + return $this->parseAddress(\stream_socket_get_name($this->stream, false)); + } + + private function parseAddress($address) + { + if ($address === false) { + return null; + } + + if ($this->unix) { + // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo + // note that technically ":" is a valid address, so keep this in place otherwise + if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) { + $address = (string)\substr($address, 0, -1); // @codeCoverageIgnore + } + + // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556 + // PHP uses "\0" string and HHVM uses empty string (colon removed above) + if ($address === '' || $address[0] === "\x00" ) { + return null; // @codeCoverageIgnore + } + + return 'unix://' . $address; + } + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = \strrpos($address, ':'); + if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') { + $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore + } + + return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address; + } +} diff --git a/vendor/react/socket/src/ConnectionInterface.php b/vendor/react/socket/src/ConnectionInterface.php new file mode 100644 index 0000000000..64613b58af --- /dev/null +++ b/vendor/react/socket/src/ConnectionInterface.php @@ -0,0 +1,119 @@ +on('data', function ($chunk) { + * echo $chunk; + * }); + * + * $connection->on('end', function () { + * echo 'ended'; + * }); + * + * $connection->on('error', function (Exception $e) { + * echo 'error: ' . $e->getMessage(); + * }); + * + * $connection->on('close', function () { + * echo 'closed'; + * }); + * + * $connection->write($data); + * $connection->end($data = null); + * $connection->close(); + * // … + * ``` + * + * For more details, see the + * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). + * + * @see DuplexStreamInterface + * @see ServerInterface + * @see ConnectorInterface + */ +interface ConnectionInterface extends DuplexStreamInterface +{ + /** + * Returns the full remote address (URI) where this connection has been established with + * + * ```php + * $address = $connection->getRemoteAddress(); + * echo 'Connection with ' . $address . PHP_EOL; + * ``` + * + * If the remote address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, + * `unix://example.sock` or `unix:///path/to/example.sock`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * If this is a TCP/IP based connection and you only want the remote IP, you may + * use something like this: + * + * ```php + * $address = $connection->getRemoteAddress(); + * $ip = trim(parse_url($address, PHP_URL_HOST), '[]'); + * echo 'Connection with ' . $ip . PHP_EOL; + * ``` + * + * @return ?string remote address (URI) or null if unknown + */ + public function getRemoteAddress(); + + /** + * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with + * + * ```php + * $address = $connection->getLocalAddress(); + * echo 'Connection with ' . $address . PHP_EOL; + * ``` + * + * If the local address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`, + * `unix://example.sock` or `unix:///path/to/example.sock`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * This method complements the [`getRemoteAddress()`](#getremoteaddress) method, + * so they should not be confused. + * + * If your `TcpServer` instance is listening on multiple interfaces (e.g. using + * the address `0.0.0.0`), you can use this method to find out which interface + * actually accepted this connection (such as a public or local interface). + * + * If your system has multiple interfaces (e.g. a WAN and a LAN interface), + * you can use this method to find out which interface was actually + * used for this connection. + * + * @return ?string local address (URI) or null if unknown + * @see self::getRemoteAddress() + */ + public function getLocalAddress(); +} diff --git a/vendor/react/socket/src/Connector.php b/vendor/react/socket/src/Connector.php new file mode 100644 index 0000000000..15faa4699e --- /dev/null +++ b/vendor/react/socket/src/Connector.php @@ -0,0 +1,236 @@ + true, + 'tls' => true, + 'unix' => true, + + 'dns' => true, + 'timeout' => true, + 'happy_eyeballs' => true, + ); + + if ($context['timeout'] === true) { + $context['timeout'] = (float)\ini_get("default_socket_timeout"); + } + + if ($context['tcp'] instanceof ConnectorInterface) { + $tcp = $context['tcp']; + } else { + $tcp = new TcpConnector( + $loop, + \is_array($context['tcp']) ? $context['tcp'] : array() + ); + } + + if ($context['dns'] !== false) { + if ($context['dns'] instanceof ResolverInterface) { + $resolver = $context['dns']; + } else { + if ($context['dns'] !== true) { + $config = $context['dns']; + } else { + // try to load nameservers from system config or default to Google's public DNS + $config = DnsConfig::loadSystemConfigBlocking(); + if (!$config->nameservers) { + $config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore + } + } + + $factory = new DnsFactory(); + $resolver = $factory->createCached( + $config, + $loop + ); + } + + if ($context['happy_eyeballs'] === true) { + $tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver); + } else { + $tcp = new DnsConnector($tcp, $resolver); + } + } + + if ($context['tcp'] !== false) { + $context['tcp'] = $tcp; + + if ($context['timeout'] !== false) { + $context['tcp'] = new TimeoutConnector( + $context['tcp'], + $context['timeout'], + $loop + ); + } + + $this->connectors['tcp'] = $context['tcp']; + } + + if ($context['tls'] !== false) { + if (!$context['tls'] instanceof ConnectorInterface) { + $context['tls'] = new SecureConnector( + $tcp, + $loop, + \is_array($context['tls']) ? $context['tls'] : array() + ); + } + + if ($context['timeout'] !== false) { + $context['tls'] = new TimeoutConnector( + $context['tls'], + $context['timeout'], + $loop + ); + } + + $this->connectors['tls'] = $context['tls']; + } + + if ($context['unix'] !== false) { + if (!$context['unix'] instanceof ConnectorInterface) { + $context['unix'] = new UnixConnector($loop); + } + $this->connectors['unix'] = $context['unix']; + } + } + + public function connect($uri) + { + $scheme = 'tcp'; + if (\strpos($uri, '://') !== false) { + $scheme = (string)\substr($uri, 0, \strpos($uri, '://')); + } + + if (!isset($this->connectors[$scheme])) { + return \React\Promise\reject(new \RuntimeException( + 'No connector available for URI scheme "' . $scheme . '" (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + return $this->connectors[$scheme]->connect($uri); + } + + + /** + * [internal] Builds on URI from the given URI parts and ip address with original hostname as query + * + * @param array $parts + * @param string $host + * @param string $ip + * @return string + * @internal + */ + public static function uri(array $parts, $host, $ip) + { + $uri = ''; + + // prepend original scheme if known + if (isset($parts['scheme'])) { + $uri .= $parts['scheme'] . '://'; + } + + if (\strpos($ip, ':') !== false) { + // enclose IPv6 addresses in square brackets before appending port + $uri .= '[' . $ip . ']'; + } else { + $uri .= $ip; + } + + // append original port if known + if (isset($parts['port'])) { + $uri .= ':' . $parts['port']; + } + + // append orignal path if known + if (isset($parts['path'])) { + $uri .= $parts['path']; + } + + // append original query if known + if (isset($parts['query'])) { + $uri .= '?' . $parts['query']; + } + + // append original hostname as query if resolved via DNS and if + // destination URI does not contain "hostname" query param already + $args = array(); + \parse_str(isset($parts['query']) ? $parts['query'] : '', $args); + if ($host !== $ip && !isset($args['hostname'])) { + $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($host); + } + + // append original fragment if known + if (isset($parts['fragment'])) { + $uri .= '#' . $parts['fragment']; + } + + return $uri; + } +} diff --git a/vendor/react/socket/src/ConnectorInterface.php b/vendor/react/socket/src/ConnectorInterface.php new file mode 100644 index 0000000000..1f07b753ea --- /dev/null +++ b/vendor/react/socket/src/ConnectorInterface.php @@ -0,0 +1,59 @@ +connect('google.com:443')->then( + * function (React\Socket\ConnectionInterface $connection) { + * // connection successfully established + * }, + * function (Exception $error) { + * // failed to connect due to $error + * } + * ); + * ``` + * + * The returned Promise MUST be implemented in such a way that it can be + * cancelled when it is still pending. Cancelling a pending promise MUST + * reject its value with an Exception. It SHOULD clean up any underlying + * resources and references as applicable. + * + * ```php + * $promise = $connector->connect($uri); + * + * $promise->cancel(); + * ``` + * + * @param string $uri + * @return \React\Promise\PromiseInterface + * Resolves with a `ConnectionInterface` on success or rejects with an `Exception` on error. + * @see ConnectionInterface + */ + public function connect($uri); +} diff --git a/vendor/react/socket/src/DnsConnector.php b/vendor/react/socket/src/DnsConnector.php new file mode 100644 index 0000000000..d2fb2c7dde --- /dev/null +++ b/vendor/react/socket/src/DnsConnector.php @@ -0,0 +1,117 @@ +connector = $connector; + $this->resolver = $resolver; + } + + public function connect($uri) + { + $original = $uri; + if (\strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + $parts = \parse_url($uri); + if (isset($parts['scheme'])) { + unset($parts['scheme']); + } + } else { + $parts = \parse_url($uri); + } + + if (!$parts || !isset($parts['host'])) { + return Promise\reject(new \InvalidArgumentException( + 'Given URI "' . $original . '" is invalid (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + $host = \trim($parts['host'], '[]'); + $connector = $this->connector; + + // skip DNS lookup / URI manipulation if this URI already contains an IP + if (@\inet_pton($host) !== false) { + return $connector->connect($original); + } + + $promise = $this->resolver->resolve($host); + $resolved = null; + + return new Promise\Promise( + function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) { + // resolve/reject with result of DNS lookup + $promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) { + $resolved = $ip; + + return $promise = $connector->connect( + Connector::uri($parts, $host, $ip) + )->then(null, function (\Exception $e) use ($uri) { + if ($e instanceof \RuntimeException) { + $message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage()); + $e = new \RuntimeException( + 'Connection to ' . $uri . ' failed: ' . $message, + $e->getCode(), + $e + ); + + // avoid garbage references by replacing all closures in call stack. + // what a lovely piece of code! + $r = new \ReflectionProperty('Exception', 'trace'); + $r->setAccessible(true); + $trace = $r->getValue($e); + + // Exception trace arguments are not available on some PHP 7.4 installs + // @codeCoverageIgnoreStart + foreach ($trace as $ti => $one) { + if (isset($one['args'])) { + foreach ($one['args'] as $ai => $arg) { + if ($arg instanceof \Closure) { + $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')'; + } + } + } + } + // @codeCoverageIgnoreEnd + $r->setValue($e, $trace); + } + + throw $e; + }); + }, function ($e) use ($uri, $reject) { + $reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e)); + })->then($resolve, $reject); + }, + function ($_, $reject) use (&$promise, &$resolved, $uri) { + // cancellation should reject connection attempt + // reject DNS resolution with custom reason, otherwise rely on connection cancellation below + if ($resolved === null) { + $reject(new \RuntimeException( + 'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)', + \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103 + )); + } + + // (try to) cancel pending DNS lookup / connection attempt + if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { + // overwrite callback arguments for PHP7+ only, so they do not show + // up in the Exception trace and do not cause a possible cyclic reference. + $_ = $reject = null; + + $promise->cancel(); + $promise = null; + } + } + ); + } +} diff --git a/vendor/react/socket/src/FdServer.php b/vendor/react/socket/src/FdServer.php new file mode 100644 index 0000000000..8e46719aa5 --- /dev/null +++ b/vendor/react/socket/src/FdServer.php @@ -0,0 +1,222 @@ +on('connection', function (ConnectionInterface $connection) { + * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + * @internal + */ +final class FdServer extends EventEmitter implements ServerInterface +{ + private $master; + private $loop; + private $unix = false; + private $listening = false; + + /** + * Creates a socket server and starts listening on the given file descriptor + * + * This starts accepting new incoming connections on the given file descriptor. + * See also the `connection event` documented in the `ServerInterface` + * for more details. + * + * ```php + * $socket = new React\Socket\FdServer(3); + * ``` + * + * If the given FD is invalid or out of range, it will throw an `InvalidArgumentException`: + * + * ```php + * // throws InvalidArgumentException + * $socket = new React\Socket\FdServer(-1); + * ``` + * + * If the given FD appears to be valid, but listening on it fails (such as + * if the FD does not exist or does not refer to a socket server), it will + * throw a `RuntimeException`: + * + * ```php + * // throws RuntimeException because FD does not reference a socket server + * $socket = new React\Socket\FdServer(0, $loop); + * ``` + * + * Note that these error conditions may vary depending on your system and/or + * configuration. + * See the exception message and code for more details about the actual error + * condition. + * + * @param int|string $fd FD number such as `3` or as URL in the form of `php://fd/3` + * @param ?LoopInterface $loop + * @throws \InvalidArgumentException if the listening address is invalid + * @throws \RuntimeException if listening on this address fails (already in use etc.) + */ + public function __construct($fd, $loop = null) + { + if (\preg_match('#^php://fd/(\d+)$#', $fd, $m)) { + $fd = (int) $m[1]; + } + if (!\is_int($fd) || $fd < 0 || $fd >= \PHP_INT_MAX) { + throw new \InvalidArgumentException( + 'Invalid FD number given (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + ); + } + + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + $this->loop = $loop ?: Loop::get(); + + $errno = 0; + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + // Match errstr from PHP's warning message. + // fopen(php://fd/3): Failed to open stream: Error duping file descriptor 3; possibly it doesn't exist: [9]: Bad file descriptor + \preg_match('/\[(\d+)\]: (.*)/', $error, $m); + $errno = isset($m[1]) ? (int) $m[1] : 0; + $errstr = isset($m[2]) ? $m[2] : $error; + }); + + $this->master = \fopen('php://fd/' . $fd, 'r+'); + + \restore_error_handler(); + + if (false === $this->master) { + throw new \RuntimeException( + 'Failed to listen on FD ' . $fd . ': ' . $errstr . SocketServer::errconst($errno), + $errno + ); + } + + $meta = \stream_get_meta_data($this->master); + if (!isset($meta['stream_type']) || $meta['stream_type'] !== 'tcp_socket') { + \fclose($this->master); + + $errno = \defined('SOCKET_ENOTSOCK') ? \SOCKET_ENOTSOCK : 88; + $errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Not a socket'; + + throw new \RuntimeException( + 'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (ENOTSOCK)', + $errno + ); + } + + // Socket should not have a peer address if this is a listening socket. + // Looks like this work-around is the closest we can get because PHP doesn't expose SO_ACCEPTCONN even with ext-sockets. + if (\stream_socket_get_name($this->master, true) !== false) { + \fclose($this->master); + + $errno = \defined('SOCKET_EISCONN') ? \SOCKET_EISCONN : 106; + $errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Socket is connected'; + + throw new \RuntimeException( + 'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (EISCONN)', + $errno + ); + } + + // Assume this is a Unix domain socket (UDS) when its listening address doesn't parse as a valid URL with a port. + // Looks like this work-around is the closest we can get because PHP doesn't expose SO_DOMAIN even with ext-sockets. + $this->unix = \parse_url($this->getAddress(), \PHP_URL_PORT) === false; + + \stream_set_blocking($this->master, false); + + $this->resume(); + } + + public function getAddress() + { + if (!\is_resource($this->master)) { + return null; + } + + $address = \stream_socket_get_name($this->master, false); + + if ($this->unix === true) { + return 'unix://' . $address; + } + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = \strrpos($address, ':'); + if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') { + $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore + } + + return 'tcp://' . $address; + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !\is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + try { + $newSocket = SocketServer::accept($master); + } catch (\RuntimeException $e) { + $that->emit('error', array($e)); + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!\is_resource($this->master)) { + return; + } + + $this->pause(); + \fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $connection = new Connection($socket, $this->loop); + $connection->unix = $this->unix; + + $this->emit('connection', array($connection)); + } +} diff --git a/vendor/react/socket/src/FixedUriConnector.php b/vendor/react/socket/src/FixedUriConnector.php new file mode 100644 index 0000000000..f83241d6ce --- /dev/null +++ b/vendor/react/socket/src/FixedUriConnector.php @@ -0,0 +1,41 @@ +connect('localhost:80'); + * ``` + */ +class FixedUriConnector implements ConnectorInterface +{ + private $uri; + private $connector; + + /** + * @param string $uri + * @param ConnectorInterface $connector + */ + public function __construct($uri, ConnectorInterface $connector) + { + $this->uri = $uri; + $this->connector = $connector; + } + + public function connect($_) + { + return $this->connector->connect($this->uri); + } +} diff --git a/vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php b/vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php new file mode 100644 index 0000000000..d4f05e8579 --- /dev/null +++ b/vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php @@ -0,0 +1,334 @@ + false, + Message::TYPE_AAAA => false, + ); + public $resolverPromises = array(); + public $connectionPromises = array(); + public $connectQueue = array(); + public $nextAttemptTimer; + public $parts; + public $ipsCount = 0; + public $failureCount = 0; + public $resolve; + public $reject; + + public $lastErrorFamily; + public $lastError6; + public $lastError4; + + public function __construct(LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts) + { + $this->loop = $loop; + $this->connector = $connector; + $this->resolver = $resolver; + $this->uri = $uri; + $this->host = $host; + $this->parts = $parts; + } + + public function connect() + { + $that = $this; + return new Promise\Promise(function ($resolve, $reject) use ($that) { + $lookupResolve = function ($type) use ($that, $resolve, $reject) { + return function (array $ips) use ($that, $type, $resolve, $reject) { + unset($that->resolverPromises[$type]); + $that->resolved[$type] = true; + + $that->mixIpsIntoConnectQueue($ips); + + // start next connection attempt if not already awaiting next + if ($that->nextAttemptTimer === null && $that->connectQueue) { + $that->check($resolve, $reject); + } + }; + }; + + $that->resolverPromises[Message::TYPE_AAAA] = $that->resolve(Message::TYPE_AAAA, $reject)->then($lookupResolve(Message::TYPE_AAAA)); + $that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that) { + // happy path: IPv6 has resolved already (or could not resolve), continue with IPv4 addresses + if ($that->resolved[Message::TYPE_AAAA] === true || !$ips) { + return $ips; + } + + // Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime + $deferred = new Promise\Deferred(function () use (&$ips) { + // discard all IPv4 addresses if cancelled + $ips = array(); + }); + $timer = $that->loop->addTimer($that::RESOLUTION_DELAY, function () use ($deferred, $ips) { + $deferred->resolve($ips); + }); + + $that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, &$ips) { + $that->loop->cancelTimer($timer); + $deferred->resolve($ips); + }); + + return $deferred->promise(); + })->then($lookupResolve(Message::TYPE_A)); + }, function ($_, $reject) use ($that) { + $reject(new \RuntimeException( + 'Connection to ' . $that->uri . ' cancelled' . (!$that->connectionPromises ? ' during DNS lookup' : '') . ' (ECONNABORTED)', + \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103 + )); + $_ = $reject = null; + + $that->cleanUp(); + }); + } + + /** + * @internal + * @param int $type DNS query type + * @param callable $reject + * @return \React\Promise\PromiseInterface Returns a promise that + * always resolves with a list of IP addresses on success or an empty + * list on error. + */ + public function resolve($type, $reject) + { + $that = $this; + return $that->resolver->resolveAll($that->host, $type)->then(null, function (\Exception $e) use ($type, $reject, $that) { + unset($that->resolverPromises[$type]); + $that->resolved[$type] = true; + + if ($type === Message::TYPE_A) { + $that->lastError4 = $e->getMessage(); + $that->lastErrorFamily = 4; + } else { + $that->lastError6 = $e->getMessage(); + $that->lastErrorFamily = 6; + } + + // cancel next attempt timer when there are no more IPs to connect to anymore + if ($that->nextAttemptTimer !== null && !$that->connectQueue) { + $that->loop->cancelTimer($that->nextAttemptTimer); + $that->nextAttemptTimer = null; + } + + if ($that->hasBeenResolved() && $that->ipsCount === 0) { + $reject(new \RuntimeException( + $that->error(), + 0, + $e + )); + } + + // Exception already handled above, so don't throw an unhandled rejection here + return array(); + }); + } + + /** + * @internal + */ + public function check($resolve, $reject) + { + $ip = \array_shift($this->connectQueue); + + // start connection attempt and remember array position to later unset again + $this->connectionPromises[] = $this->attemptConnection($ip); + \end($this->connectionPromises); + $index = \key($this->connectionPromises); + + $that = $this; + $that->connectionPromises[$index]->then(function ($connection) use ($that, $index, $resolve) { + unset($that->connectionPromises[$index]); + + $that->cleanUp(); + + $resolve($connection); + }, function (\Exception $e) use ($that, $index, $ip, $resolve, $reject) { + unset($that->connectionPromises[$index]); + + $that->failureCount++; + + $message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage()); + if (\strpos($ip, ':') === false) { + $that->lastError4 = $message; + $that->lastErrorFamily = 4; + } else { + $that->lastError6 = $message; + $that->lastErrorFamily = 6; + } + + // start next connection attempt immediately on error + if ($that->connectQueue) { + if ($that->nextAttemptTimer !== null) { + $that->loop->cancelTimer($that->nextAttemptTimer); + $that->nextAttemptTimer = null; + } + + $that->check($resolve, $reject); + } + + if ($that->hasBeenResolved() === false) { + return; + } + + if ($that->ipsCount === $that->failureCount) { + $that->cleanUp(); + + $reject(new \RuntimeException( + $that->error(), + $e->getCode(), + $e + )); + } + }); + + // Allow next connection attempt in 100ms: https://tools.ietf.org/html/rfc8305#section-5 + // Only start timer when more IPs are queued or when DNS query is still pending (might add more IPs) + if ($this->nextAttemptTimer === null && (\count($this->connectQueue) > 0 || $this->resolved[Message::TYPE_A] === false || $this->resolved[Message::TYPE_AAAA] === false)) { + $this->nextAttemptTimer = $this->loop->addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($that, $resolve, $reject) { + $that->nextAttemptTimer = null; + + if ($that->connectQueue) { + $that->check($resolve, $reject); + } + }); + } + } + + /** + * @internal + */ + public function attemptConnection($ip) + { + $uri = Connector::uri($this->parts, $this->host, $ip); + + return $this->connector->connect($uri); + } + + /** + * @internal + */ + public function cleanUp() + { + // clear list of outstanding IPs to avoid creating new connections + $this->connectQueue = array(); + + // cancel pending connection attempts + foreach ($this->connectionPromises as $connectionPromise) { + if ($connectionPromise instanceof PromiseInterface && \method_exists($connectionPromise, 'cancel')) { + $connectionPromise->cancel(); + } + } + + // cancel pending DNS resolution (cancel IPv4 first in case it is awaiting IPv6 resolution delay) + foreach (\array_reverse($this->resolverPromises) as $resolverPromise) { + if ($resolverPromise instanceof PromiseInterface && \method_exists($resolverPromise, 'cancel')) { + $resolverPromise->cancel(); + } + } + + if ($this->nextAttemptTimer instanceof TimerInterface) { + $this->loop->cancelTimer($this->nextAttemptTimer); + $this->nextAttemptTimer = null; + } + } + + /** + * @internal + */ + public function hasBeenResolved() + { + foreach ($this->resolved as $typeHasBeenResolved) { + if ($typeHasBeenResolved === false) { + return false; + } + } + + return true; + } + + /** + * Mixes an array of IP addresses into the connect queue in such a way they alternate when attempting to connect. + * The goal behind it is first attempt to connect to IPv6, then to IPv4, then to IPv6 again until one of those + * attempts succeeds. + * + * @link https://tools.ietf.org/html/rfc8305#section-4 + * + * @internal + */ + public function mixIpsIntoConnectQueue(array $ips) + { + \shuffle($ips); + $this->ipsCount += \count($ips); + $connectQueueStash = $this->connectQueue; + $this->connectQueue = array(); + while (\count($connectQueueStash) > 0 || \count($ips) > 0) { + if (\count($ips) > 0) { + $this->connectQueue[] = \array_shift($ips); + } + if (\count($connectQueueStash) > 0) { + $this->connectQueue[] = \array_shift($connectQueueStash); + } + } + } + + /** + * @internal + * @return string + */ + public function error() + { + if ($this->lastError4 === $this->lastError6) { + $message = $this->lastError6; + } elseif ($this->lastErrorFamily === 6) { + $message = 'Last error for IPv6: ' . $this->lastError6 . '. Previous error for IPv4: ' . $this->lastError4; + } else { + $message = 'Last error for IPv4: ' . $this->lastError4 . '. Previous error for IPv6: ' . $this->lastError6; + } + + if ($this->hasBeenResolved() && $this->ipsCount === 0) { + if ($this->lastError6 === $this->lastError4) { + $message = ' during DNS lookup: ' . $this->lastError6; + } else { + $message = ' during DNS lookup. ' . $message; + } + } else { + $message = ': ' . $message; + } + + return 'Connection to ' . $this->uri . ' failed' . $message; + } +} diff --git a/vendor/react/socket/src/HappyEyeBallsConnector.php b/vendor/react/socket/src/HappyEyeBallsConnector.php new file mode 100644 index 0000000000..a5511ac9e3 --- /dev/null +++ b/vendor/react/socket/src/HappyEyeBallsConnector.php @@ -0,0 +1,80 @@ +loop = $loop ?: Loop::get(); + $this->connector = $connector; + $this->resolver = $resolver; + } + + public function connect($uri) + { + $original = $uri; + if (\strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + $parts = \parse_url($uri); + if (isset($parts['scheme'])) { + unset($parts['scheme']); + } + } else { + $parts = \parse_url($uri); + } + + if (!$parts || !isset($parts['host'])) { + return Promise\reject(new \InvalidArgumentException( + 'Given URI "' . $original . '" is invalid (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + $host = \trim($parts['host'], '[]'); + + // skip DNS lookup / URI manipulation if this URI already contains an IP + if (@\inet_pton($host) !== false) { + return $this->connector->connect($original); + } + + $builder = new HappyEyeBallsConnectionBuilder( + $this->loop, + $this->connector, + $this->resolver, + $uri, + $host, + $parts + ); + return $builder->connect(); + } +} diff --git a/vendor/react/socket/src/LimitingServer.php b/vendor/react/socket/src/LimitingServer.php new file mode 100644 index 0000000000..d19000b366 --- /dev/null +++ b/vendor/react/socket/src/LimitingServer.php @@ -0,0 +1,203 @@ +on('connection', function (React\Socket\ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +class LimitingServer extends EventEmitter implements ServerInterface +{ + private $connections = array(); + private $server; + private $limit; + + private $pauseOnLimit = false; + private $autoPaused = false; + private $manuPaused = false; + + /** + * Instantiates a new LimitingServer. + * + * You have to pass a maximum number of open connections to ensure + * the server will automatically reject (close) connections once this limit + * is exceeded. In this case, it will emit an `error` event to inform about + * this and no `connection` event will be emitted. + * + * ```php + * $server = new React\Socket\LimitingServer($server, 100); + * $server->on('connection', function (React\Socket\ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * You MAY pass a `null` limit in order to put no limit on the number of + * open connections and keep accepting new connection until you run out of + * operating system resources (such as open file handles). This may be + * useful if you do not want to take care of applying a limit but still want + * to use the `getConnections()` method. + * + * You can optionally configure the server to pause accepting new + * connections once the connection limit is reached. In this case, it will + * pause the underlying server and no longer process any new connections at + * all, thus also no longer closing any excessive connections. + * The underlying operating system is responsible for keeping a backlog of + * pending connections until its limit is reached, at which point it will + * start rejecting further connections. + * Once the server is below the connection limit, it will continue consuming + * connections from the backlog and will process any outstanding data on + * each connection. + * This mode may be useful for some protocols that are designed to wait for + * a response message (such as HTTP), but may be less useful for other + * protocols that demand immediate responses (such as a "welcome" message in + * an interactive chat). + * + * ```php + * $server = new React\Socket\LimitingServer($server, 100, true); + * $server->on('connection', function (React\Socket\ConnectionInterface $connection) { + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * @param ServerInterface $server + * @param int|null $connectionLimit + * @param bool $pauseOnLimit + */ + public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false) + { + $this->server = $server; + $this->limit = $connectionLimit; + if ($connectionLimit !== null) { + $this->pauseOnLimit = $pauseOnLimit; + } + + $this->server->on('connection', array($this, 'handleConnection')); + $this->server->on('error', array($this, 'handleError')); + } + + /** + * Returns an array with all currently active connections + * + * ```php + * foreach ($server->getConnection() as $connection) { + * $connection->write('Hi!'); + * } + * ``` + * + * @return ConnectionInterface[] + */ + public function getConnections() + { + return $this->connections; + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + if (!$this->manuPaused) { + $this->manuPaused = true; + + if (!$this->autoPaused) { + $this->server->pause(); + } + } + } + + public function resume() + { + if ($this->manuPaused) { + $this->manuPaused = false; + + if (!$this->autoPaused) { + $this->server->resume(); + } + } + } + + public function close() + { + $this->server->close(); + } + + /** @internal */ + public function handleConnection(ConnectionInterface $connection) + { + // close connection if limit exceeded + if ($this->limit !== null && \count($this->connections) >= $this->limit) { + $this->handleError(new \OverflowException('Connection closed because server reached connection limit')); + $connection->close(); + return; + } + + $this->connections[] = $connection; + $that = $this; + $connection->on('close', function () use ($that, $connection) { + $that->handleDisconnection($connection); + }); + + // pause accepting new connections if limit exceeded + if ($this->pauseOnLimit && !$this->autoPaused && \count($this->connections) >= $this->limit) { + $this->autoPaused = true; + + if (!$this->manuPaused) { + $this->server->pause(); + } + } + + $this->emit('connection', array($connection)); + } + + /** @internal */ + public function handleDisconnection(ConnectionInterface $connection) + { + unset($this->connections[\array_search($connection, $this->connections)]); + + // continue accepting new connection if below limit + if ($this->autoPaused && \count($this->connections) < $this->limit) { + $this->autoPaused = false; + + if (!$this->manuPaused) { + $this->server->resume(); + } + } + } + + /** @internal */ + public function handleError(\Exception $error) + { + $this->emit('error', array($error)); + } +} diff --git a/vendor/react/socket/src/SecureConnector.php b/vendor/react/socket/src/SecureConnector.php new file mode 100644 index 0000000000..08255ac928 --- /dev/null +++ b/vendor/react/socket/src/SecureConnector.php @@ -0,0 +1,132 @@ +connector = $connector; + $this->streamEncryption = new StreamEncryption($loop ?: Loop::get(), false); + $this->context = $context; + } + + public function connect($uri) + { + if (!\function_exists('stream_socket_enable_crypto')) { + return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore + } + + if (\strpos($uri, '://') === false) { + $uri = 'tls://' . $uri; + } + + $parts = \parse_url($uri); + if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') { + return Promise\reject(new \InvalidArgumentException( + 'Given URI "' . $uri . '" is invalid (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + $context = $this->context; + $encryption = $this->streamEncryption; + $connected = false; + /** @var \React\Promise\PromiseInterface $promise */ + $promise = $this->connector->connect( + \str_replace('tls://', '', $uri) + )->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promise, &$connected) { + // (unencrypted) TCP/IP connection succeeded + $connected = true; + + if (!$connection instanceof Connection) { + $connection->close(); + throw new \UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource'); + } + + // set required SSL/TLS context options + foreach ($context as $name => $value) { + \stream_context_set_option($connection->stream, 'ssl', $name, $value); + } + + // try to enable encryption + return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) { + // establishing encryption failed => close invalid connection and return error + $connection->close(); + + throw new \RuntimeException( + 'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(), + $error->getCode() + ); + }); + }, function (\Exception $e) use ($uri) { + if ($e instanceof \RuntimeException) { + $message = \preg_replace('/^Connection to [^ ]+/', '', $e->getMessage()); + $e = new \RuntimeException( + 'Connection to ' . $uri . $message, + $e->getCode(), + $e + ); + + // avoid garbage references by replacing all closures in call stack. + // what a lovely piece of code! + $r = new \ReflectionProperty('Exception', 'trace'); + $r->setAccessible(true); + $trace = $r->getValue($e); + + // Exception trace arguments are not available on some PHP 7.4 installs + // @codeCoverageIgnoreStart + foreach ($trace as $ti => $one) { + if (isset($one['args'])) { + foreach ($one['args'] as $ai => $arg) { + if ($arg instanceof \Closure) { + $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')'; + } + } + } + } + // @codeCoverageIgnoreEnd + $r->setValue($e, $trace); + } + + throw $e; + }); + + return new \React\Promise\Promise( + function ($resolve, $reject) use ($promise) { + $promise->then($resolve, $reject); + }, + function ($_, $reject) use (&$promise, $uri, &$connected) { + if ($connected) { + $reject(new \RuntimeException( + 'Connection to ' . $uri . ' cancelled during TLS handshake (ECONNABORTED)', + \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103 + )); + } + + $promise->cancel(); + $promise = null; + } + ); + } +} diff --git a/vendor/react/socket/src/SecureServer.php b/vendor/react/socket/src/SecureServer.php new file mode 100644 index 0000000000..5a202d27bc --- /dev/null +++ b/vendor/react/socket/src/SecureServer.php @@ -0,0 +1,210 @@ +on('connection', function (React\Socket\ConnectionInterface $connection) { + * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; + * + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * Whenever a client fails to perform a successful TLS handshake, it will emit an + * `error` event and then close the underlying TCP/IP connection: + * + * ```php + * $server->on('error', function (Exception $e) { + * echo 'Error' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * Note that the `SecureServer` class is a concrete implementation for TLS sockets. + * If you want to typehint in your higher-level protocol implementation, you SHOULD + * use the generic `ServerInterface` instead. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class SecureServer extends EventEmitter implements ServerInterface +{ + private $tcp; + private $encryption; + private $context; + + /** + * Creates a secure TLS server and starts waiting for incoming connections + * + * It does so by wrapping a `TcpServer` instance which waits for plaintext + * TCP/IP connections and then performs a TLS handshake for each connection. + * It thus requires valid [TLS context options], + * which in its most basic form may look something like this if you're using a + * PEM encoded certificate file: + * + * ```php + * $server = new React\Socket\TcpServer(8000); + * $server = new React\Socket\SecureServer($server, null, array( + * 'local_cert' => 'server.pem' + * )); + * ``` + * + * Note that the certificate file will not be loaded on instantiation but when an + * incoming connection initializes its TLS context. + * This implies that any invalid certificate file paths or contents will only cause + * an `error` event at a later time. + * + * If your private key is encrypted with a passphrase, you have to specify it + * like this: + * + * ```php + * $server = new React\Socket\TcpServer(8000); + * $server = new React\Socket\SecureServer($server, null, array( + * 'local_cert' => 'server.pem', + * 'passphrase' => 'secret' + * )); + * ``` + * + * Note that available [TLS context options], + * their defaults and effects of changing these may vary depending on your system + * and/or PHP version. + * Passing unknown context options has no effect. + * + * This class takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this object. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * + * Advanced usage: Despite allowing any `ServerInterface` as first parameter, + * you SHOULD pass a `TcpServer` instance as first parameter, unless you + * know what you're doing. + * Internally, the `SecureServer` has to set the required TLS context options on + * the underlying stream resources. + * These resources are not exposed through any of the interfaces defined in this + * package, but only through the internal `Connection` class. + * The `TcpServer` class is guaranteed to emit connections that implement + * the `ConnectionInterface` and uses the internal `Connection` class in order to + * expose these underlying resources. + * If you use a custom `ServerInterface` and its `connection` event does not + * meet this requirement, the `SecureServer` will emit an `error` event and + * then close the underlying connection. + * + * @param ServerInterface|TcpServer $tcp + * @param ?LoopInterface $loop + * @param array $context + * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support + * @see TcpServer + * @link https://www.php.net/manual/en/context.ssl.php for TLS context options + */ + public function __construct(ServerInterface $tcp, $loop = null, array $context = array()) + { + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + if (!\function_exists('stream_socket_enable_crypto')) { + throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore + } + + // default to empty passphrase to suppress blocking passphrase prompt + $context += array( + 'passphrase' => '' + ); + + $this->tcp = $tcp; + $this->encryption = new StreamEncryption($loop ?: Loop::get()); + $this->context = $context; + + $that = $this; + $this->tcp->on('connection', function ($connection) use ($that) { + $that->handleConnection($connection); + }); + $this->tcp->on('error', function ($error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + $address = $this->tcp->getAddress(); + if ($address === null) { + return null; + } + + return \str_replace('tcp://' , 'tls://', $address); + } + + public function pause() + { + $this->tcp->pause(); + } + + public function resume() + { + $this->tcp->resume(); + } + + public function close() + { + return $this->tcp->close(); + } + + /** @internal */ + public function handleConnection(ConnectionInterface $connection) + { + if (!$connection instanceof Connection) { + $this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource'))); + $connection->close(); + return; + } + + foreach ($this->context as $name => $value) { + \stream_context_set_option($connection->stream, 'ssl', $name, $value); + } + + // get remote address before starting TLS handshake in case connection closes during handshake + $remote = $connection->getRemoteAddress(); + $that = $this; + + $this->encryption->enable($connection)->then( + function ($conn) use ($that) { + $that->emit('connection', array($conn)); + }, + function ($error) use ($that, $connection, $remote) { + $error = new \RuntimeException( + 'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(), + $error->getCode() + ); + + $that->emit('error', array($error)); + $connection->close(); + } + ); + } +} diff --git a/vendor/react/socket/src/Server.php b/vendor/react/socket/src/Server.php new file mode 100644 index 0000000000..b24c556482 --- /dev/null +++ b/vendor/react/socket/src/Server.php @@ -0,0 +1,118 @@ + $context); + } + + // apply default options if not explicitly given + $context += array( + 'tcp' => array(), + 'tls' => array(), + 'unix' => array() + ); + + $scheme = 'tcp'; + $pos = \strpos($uri, '://'); + if ($pos !== false) { + $scheme = \substr($uri, 0, $pos); + } + + if ($scheme === 'unix') { + $server = new UnixServer($uri, $loop, $context['unix']); + } else { + $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); + + if ($scheme === 'tls') { + $server = new SecureServer($server, $loop, $context['tls']); + } + } + + $this->server = $server; + + $that = $this; + $server->on('connection', function (ConnectionInterface $conn) use ($that) { + $that->emit('connection', array($conn)); + }); + $server->on('error', function (Exception $error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + $this->server->pause(); + } + + public function resume() + { + $this->server->resume(); + } + + public function close() + { + $this->server->close(); + } +} diff --git a/vendor/react/socket/src/ServerInterface.php b/vendor/react/socket/src/ServerInterface.php new file mode 100644 index 0000000000..aa79fa17e5 --- /dev/null +++ b/vendor/react/socket/src/ServerInterface.php @@ -0,0 +1,151 @@ +on('connection', function (React\Socket\ConnectionInterface $connection) { + * echo 'new connection' . PHP_EOL; + * }); + * ``` + * + * See also the `ConnectionInterface` for more details about handling the + * incoming connection. + * + * error event: + * The `error` event will be emitted whenever there's an error accepting a new + * connection from a client. + * + * ```php + * $socket->on('error', function (Exception $e) { + * echo 'error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * Note that this is not a fatal error event, i.e. the server keeps listening for + * new connections even after this event. + * + * @see ConnectionInterface + */ +interface ServerInterface extends EventEmitterInterface +{ + /** + * Returns the full address (URI) this server is currently listening on + * + * ```php + * $address = $socket->getAddress(); + * echo 'Server listening on ' . $address . PHP_EOL; + * ``` + * + * If the address can not be determined or is unknown at this time (such as + * after the socket has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full address (URI) as a string value, such + * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`. + * Note that individual URI components are application specific and depend + * on the underlying transport protocol. + * + * If this is a TCP/IP based server and you only want the local port, you may + * use something like this: + * + * ```php + * $address = $socket->getAddress(); + * $port = parse_url($address, PHP_URL_PORT); + * echo 'Server listening on port ' . $port . PHP_EOL; + * ``` + * + * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed) + */ + public function getAddress(); + + /** + * Pauses accepting new incoming connections. + * + * Removes the socket resource from the EventLoop and thus stop accepting + * new connections. Note that the listening socket stays active and is not + * closed. + * + * This means that new incoming connections will stay pending in the + * operating system backlog until its configurable backlog is filled. + * Once the backlog is filled, the operating system may reject further + * incoming connections until the backlog is drained again by resuming + * to accept new connections. + * + * Once the server is paused, no futher `connection` events SHOULD + * be emitted. + * + * ```php + * $socket->pause(); + * + * $socket->on('connection', assertShouldNeverCalled()); + * ``` + * + * This method is advisory-only, though generally not recommended, the + * server MAY continue emitting `connection` events. + * + * Unless otherwise noted, a successfully opened server SHOULD NOT start + * in paused state. + * + * You can continue processing events by calling `resume()` again. + * + * Note that both methods can be called any number of times, in particular + * calling `pause()` more than once SHOULD NOT have any effect. + * Similarly, calling this after `close()` is a NO-OP. + * + * @see self::resume() + * @return void + */ + public function pause(); + + /** + * Resumes accepting new incoming connections. + * + * Re-attach the socket resource to the EventLoop after a previous `pause()`. + * + * ```php + * $socket->pause(); + * + * Loop::addTimer(1.0, function () use ($socket) { + * $socket->resume(); + * }); + * ``` + * + * Note that both methods can be called any number of times, in particular + * calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + * Similarly, calling this after `close()` is a NO-OP. + * + * @see self::pause() + * @return void + */ + public function resume(); + + /** + * Shuts down this listening socket + * + * This will stop listening for new incoming connections on this socket. + * + * Calling this method more than once on the same instance is a NO-OP. + * + * @return void + */ + public function close(); +} diff --git a/vendor/react/socket/src/SocketServer.php b/vendor/react/socket/src/SocketServer.php new file mode 100644 index 0000000000..e987f5f6a9 --- /dev/null +++ b/vendor/react/socket/src/SocketServer.php @@ -0,0 +1,215 @@ + array(), + 'tls' => array(), + 'unix' => array() + ); + + $scheme = 'tcp'; + $pos = \strpos($uri, '://'); + if ($pos !== false) { + $scheme = \substr($uri, 0, $pos); + } + + if ($scheme === 'unix') { + $server = new UnixServer($uri, $loop, $context['unix']); + } elseif ($scheme === 'php') { + $server = new FdServer($uri, $loop); + } else { + if (preg_match('#^(?:\w+://)?\d+$#', $uri)) { + throw new \InvalidArgumentException( + 'Invalid URI given (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + ); + } + + $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']); + + if ($scheme === 'tls') { + $server = new SecureServer($server, $loop, $context['tls']); + } + } + + $this->server = $server; + + $that = $this; + $server->on('connection', function (ConnectionInterface $conn) use ($that) { + $that->emit('connection', array($conn)); + }); + $server->on('error', function (\Exception $error) use ($that) { + $that->emit('error', array($error)); + }); + } + + public function getAddress() + { + return $this->server->getAddress(); + } + + public function pause() + { + $this->server->pause(); + } + + public function resume() + { + $this->server->resume(); + } + + public function close() + { + $this->server->close(); + } + + /** + * [internal] Internal helper method to accept new connection from given server socket + * + * @param resource $socket server socket to accept connection from + * @return resource new client socket if any + * @throws \RuntimeException if accepting fails + * @internal + */ + public static function accept($socket) + { + $errno = 0; + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + // Match errstr from PHP's warning message. + // stream_socket_accept(): accept failed: Connection timed out + $errstr = \preg_replace('#.*: #', '', $error); + $errno = SocketServer::errno($errstr); + }); + + $newSocket = \stream_socket_accept($socket, 0); + + \restore_error_handler(); + + if (false === $newSocket) { + throw new \RuntimeException( + 'Unable to accept new connection: ' . $errstr . self::errconst($errno), + $errno + ); + } + + return $newSocket; + } + + /** + * [Internal] Returns errno value for given errstr + * + * The errno and errstr values describes the type of error that has been + * encountered. This method tries to look up the given errstr and find a + * matching errno value which can be useful to provide more context to error + * messages. It goes through the list of known errno constants when either + * `ext-sockets`, `ext-posix` or `ext-pcntl` is available to find an errno + * matching the given errstr. + * + * @param string $errstr + * @return int errno value (e.g. value of `SOCKET_ECONNREFUSED`) or 0 if not found + * @internal + * @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission + * @codeCoverageIgnore + */ + public static function errno($errstr) + { + // PHP defines the required `strerror()` function through either `ext-sockets`, `ext-posix` or `ext-pcntl` + $strerror = \function_exists('socket_strerror') ? 'socket_strerror' : (\function_exists('posix_strerror') ? 'posix_strerror' : (\function_exists('pcntl_strerror') ? 'pcntl_strerror' : null)); + if ($strerror !== null) { + assert(\is_string($strerror) && \is_callable($strerror)); + + // PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED` + // PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE` + // go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errstr` + foreach (\get_defined_constants(false) as $name => $value) { + if (\is_int($value) && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0) && $strerror($value) === $errstr) { + return $value; + } + } + + // if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available) + // go through list of all possible errno values from 1 to `MAX_ERRNO` and see if they match the given `$errstr` + for ($errno = 1, $max = \defined('MAX_ERRNO') ? \MAX_ERRNO : 4095; $errno <= $max; ++$errno) { + if ($strerror($errno) === $errstr) { + return $errno; + } + } + } + + // if we reach this, no matching errno value could be found (unlikely when either `ext-sockets`, `ext-posix` or `ext-pcntl` is available) + return 0; + } + + /** + * [Internal] Returns errno constant name for given errno value + * + * The errno value describes the type of error that has been encountered. + * This method tries to look up the given errno value and find a matching + * errno constant name which can be useful to provide more context and more + * descriptive error messages. It goes through the list of known errno + * constants when either `ext-sockets` or `ext-pcntl` is available to find + * the matching errno constant name. + * + * Because this method is used to append more context to error messages, the + * constant name will be prefixed with a space and put between parenthesis + * when found. + * + * @param int $errno + * @return string e.g. ` (ECONNREFUSED)` or empty string if no matching const for the given errno could be found + * @internal + * @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission + * @codeCoverageIgnore + */ + public static function errconst($errno) + { + // PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED` + // PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE` + // go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errno` + foreach (\get_defined_constants(false) as $name => $value) { + if ($value === $errno && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0)) { + return ' (' . \substr($name, \strpos($name, '_') + 1) . ')'; + } + } + + // if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available) + return ''; + } +} diff --git a/vendor/react/socket/src/StreamEncryption.php b/vendor/react/socket/src/StreamEncryption.php new file mode 100644 index 0000000000..f91a3597e5 --- /dev/null +++ b/vendor/react/socket/src/StreamEncryption.php @@ -0,0 +1,158 @@ +loop = $loop; + $this->server = $server; + + // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3. + // As of PHP 7.2+ the main crypto method constant includes all TLS versions. + // As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions. + // For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions. + // @link https://3v4l.org/9PSST + if ($server) { + $this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER; + + if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) { + $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; // @codeCoverageIgnore + } + } else { + $this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT; + + if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) { + $this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; // @codeCoverageIgnore + } + } + } + + /** + * @param Connection $stream + * @return \React\Promise\PromiseInterface + */ + public function enable(Connection $stream) + { + return $this->toggle($stream, true); + } + + /** + * @param Connection $stream + * @param bool $toggle + * @return \React\Promise\PromiseInterface + */ + public function toggle(Connection $stream, $toggle) + { + // pause actual stream instance to continue operation on raw stream socket + $stream->pause(); + + // TODO: add write() event to make sure we're not sending any excessive data + + // cancelling this leaves this stream in an inconsistent state… + $deferred = new Deferred(function () { + throw new \RuntimeException(); + }); + + // get actual stream socket from stream instance + $socket = $stream->stream; + + // get crypto method from context options or use global setting from constructor + $method = $this->method; + $context = \stream_context_get_options($socket); + if (isset($context['ssl']['crypto_method'])) { + $method = $context['ssl']['crypto_method']; + } + + $that = $this; + $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) { + $that->toggleCrypto($socket, $deferred, $toggle, $method); + }; + + $this->loop->addReadStream($socket, $toggleCrypto); + + if (!$this->server) { + $toggleCrypto(); + } + + $loop = $this->loop; + + return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) { + $loop->removeReadStream($socket); + + $stream->encryptionEnabled = $toggle; + $stream->resume(); + + return $stream; + }, function($error) use ($stream, $socket, $loop) { + $loop->removeReadStream($socket); + $stream->resume(); + throw $error; + }); + } + + /** + * @internal + * @param resource $socket + * @param Deferred $deferred + * @param bool $toggle + * @param int $method + * @return void + */ + public function toggleCrypto($socket, Deferred $deferred, $toggle, $method) + { + $error = null; + \set_error_handler(function ($_, $errstr) use (&$error) { + $error = \str_replace(array("\r", "\n"), ' ', $errstr); + + // remove useless function name from error message + if (($pos = \strpos($error, "): ")) !== false) { + $error = \substr($error, $pos + 3); + } + }); + + $result = \stream_socket_enable_crypto($socket, $toggle, $method); + + \restore_error_handler(); + + if (true === $result) { + $deferred->resolve(null); + } else if (false === $result) { + // overwrite callback arguments for PHP7+ only, so they do not show + // up in the Exception trace and do not cause a possible cyclic reference. + $d = $deferred; + $deferred = null; + + if (\feof($socket) || $error === null) { + // EOF or failed without error => connection closed during handshake + $d->reject(new \UnexpectedValueException( + 'Connection lost during TLS handshake (ECONNRESET)', + \defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 104 + )); + } else { + // handshake failed with error message + $d->reject(new \UnexpectedValueException( + $error + )); + } + } else { + // need more data, will retry + } + } +} diff --git a/vendor/react/socket/src/TcpConnector.php b/vendor/react/socket/src/TcpConnector.php new file mode 100644 index 0000000000..9d2599e81d --- /dev/null +++ b/vendor/react/socket/src/TcpConnector.php @@ -0,0 +1,173 @@ +loop = $loop ?: Loop::get(); + $this->context = $context; + } + + public function connect($uri) + { + if (\strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + $parts = \parse_url($uri); + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { + return Promise\reject(new \InvalidArgumentException( + 'Given URI "' . $uri . '" is invalid (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + $ip = \trim($parts['host'], '[]'); + if (@\inet_pton($ip) === false) { + return Promise\reject(new \InvalidArgumentException( + 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + // use context given in constructor + $context = array( + 'socket' => $this->context + ); + + // parse arguments from query component of URI + $args = array(); + if (isset($parts['query'])) { + \parse_str($parts['query'], $args); + } + + // If an original hostname has been given, use this for TLS setup. + // This can happen due to layers of nested connectors, such as a + // DnsConnector reporting its original hostname. + // These context options are here in case TLS is enabled later on this stream. + // If TLS is not enabled later, this doesn't hurt either. + if (isset($args['hostname'])) { + $context['ssl'] = array( + 'SNI_enabled' => true, + 'peer_name' => $args['hostname'] + ); + + // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead. + // The SNI_server_name context option has to be set here during construction, + // as legacy PHP ignores any values set later. + // @codeCoverageIgnoreStart + if (\PHP_VERSION_ID < 50600) { + $context['ssl'] += array( + 'SNI_server_name' => $args['hostname'], + 'CN_match' => $args['hostname'] + ); + } + // @codeCoverageIgnoreEnd + } + + // latest versions of PHP no longer accept any other URI components and + // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here + $remote = 'tcp://' . $parts['host'] . ':' . $parts['port']; + + $stream = @\stream_socket_client( + $remote, + $errno, + $errstr, + 0, + \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT, + \stream_context_create($context) + ); + + if (false === $stream) { + return Promise\reject(new \RuntimeException( + 'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno), + $errno + )); + } + + // wait for connection + $loop = $this->loop; + return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream, $uri) { + $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject, $uri) { + $loop->removeWriteStream($stream); + + // The following hack looks like the only way to + // detect connection refused errors with PHP's stream sockets. + if (false === \stream_socket_get_name($stream, true)) { + // If we reach this point, we know the connection is dead, but we don't know the underlying error condition. + // @codeCoverageIgnoreStart + if (\function_exists('socket_import_stream')) { + // actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+ + $socket = \socket_import_stream($stream); + $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR); + $errstr = \socket_strerror($errno); + } elseif (\PHP_OS === 'Linux') { + // Linux reports socket errno and errstr again when trying to write to the dead socket. + // Suppress error reporting to get error message below and close dead socket before rejecting. + // This is only known to work on Linux, Mac and Windows are known to not support this. + $errno = 0; + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + // Match errstr from PHP's warning message. + // fwrite(): send of 1 bytes failed with errno=111 Connection refused + \preg_match('/errno=(\d+) (.+)/', $error, $m); + $errno = isset($m[1]) ? (int) $m[1] : 0; + $errstr = isset($m[2]) ? $m[2] : $error; + }); + + \fwrite($stream, \PHP_EOL); + + \restore_error_handler(); + } else { + // Not on Linux and ext-sockets not available? Too bad. + $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111; + $errstr = 'Connection refused?'; + } + // @codeCoverageIgnoreEnd + + \fclose($stream); + $reject(new \RuntimeException( + 'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno), + $errno + )); + } else { + $resolve(new Connection($stream, $loop)); + } + }); + }, function () use ($loop, $stream, $uri) { + $loop->removeWriteStream($stream); + \fclose($stream); + + // @codeCoverageIgnoreStart + // legacy PHP 5.3 sometimes requires a second close call (see tests) + if (\PHP_VERSION_ID < 50400 && \is_resource($stream)) { + \fclose($stream); + } + // @codeCoverageIgnoreEnd + + throw new \RuntimeException( + 'Connection to ' . $uri . ' cancelled during TCP/IP handshake (ECONNABORTED)', + \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103 + ); + }); + } +} diff --git a/vendor/react/socket/src/TcpServer.php b/vendor/react/socket/src/TcpServer.php new file mode 100644 index 0000000000..01b2b46dc6 --- /dev/null +++ b/vendor/react/socket/src/TcpServer.php @@ -0,0 +1,262 @@ +on('connection', function (React\Socket\ConnectionInterface $connection) { + * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; + * $connection->write('hello there!' . PHP_EOL); + * … + * }); + * ``` + * + * See also the `ServerInterface` for more details. + * + * @see ServerInterface + * @see ConnectionInterface + */ +final class TcpServer extends EventEmitter implements ServerInterface +{ + private $master; + private $loop; + private $listening = false; + + /** + * Creates a plaintext TCP/IP socket server and starts listening on the given address + * + * This starts accepting new incoming connections on the given address. + * See also the `connection event` documented in the `ServerInterface` + * for more details. + * + * ```php + * $server = new React\Socket\TcpServer(8080); + * ``` + * + * As above, the `$uri` parameter can consist of only a port, in which case the + * server will default to listening on the localhost address `127.0.0.1`, + * which means it will not be reachable from outside of this system. + * + * In order to use a random port assignment, you can use the port `0`: + * + * ```php + * $server = new React\Socket\TcpServer(0); + * $address = $server->getAddress(); + * ``` + * + * In order to change the host the socket is listening on, you can provide an IP + * address through the first parameter provided to the constructor, optionally + * preceded by the `tcp://` scheme: + * + * ```php + * $server = new React\Socket\TcpServer('192.168.0.1:8080'); + * ``` + * + * If you want to listen on an IPv6 address, you MUST enclose the host in square + * brackets: + * + * ```php + * $server = new React\Socket\TcpServer('[::1]:8080'); + * ``` + * + * If the given URI is invalid, does not contain a port, any other scheme or if it + * contains a hostname, it will throw an `InvalidArgumentException`: + * + * ```php + * // throws InvalidArgumentException due to missing port + * $server = new React\Socket\TcpServer('127.0.0.1'); + * ``` + * + * If the given URI appears to be valid, but listening on it fails (such as if port + * is already in use or port below 1024 may require root access etc.), it will + * throw a `RuntimeException`: + * + * ```php + * $first = new React\Socket\TcpServer(8080); + * + * // throws RuntimeException because port is already in use + * $second = new React\Socket\TcpServer(8080); + * ``` + * + * Note that these error conditions may vary depending on your system and/or + * configuration. + * See the exception message and code for more details about the actual error + * condition. + * + * This class takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this object. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * + * Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php) + * for the underlying stream socket resource like this: + * + * ```php + * $server = new React\Socket\TcpServer('[::1]:8080', null, array( + * 'backlog' => 200, + * 'so_reuseport' => true, + * 'ipv6_v6only' => true + * )); + * ``` + * + * Note that available [socket context options](https://www.php.net/manual/en/context.socket.php), + * their defaults and effects of changing these may vary depending on your system + * and/or PHP version. + * Passing unknown context options has no effect. + * The `backlog` context option defaults to `511` unless given explicitly. + * + * @param string|int $uri + * @param ?LoopInterface $loop + * @param array $context + * @throws InvalidArgumentException if the listening address is invalid + * @throws RuntimeException if listening on this address fails (already in use etc.) + */ + public function __construct($uri, $loop = null, array $context = array()) + { + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + $this->loop = $loop ?: Loop::get(); + + // a single port has been given => assume localhost + if ((string)(int)$uri === (string)$uri) { + $uri = '127.0.0.1:' . $uri; + } + + // assume default scheme if none has been given + if (\strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + // parse_url() does not accept null ports (random port assignment) => manually remove + if (\substr($uri, -2) === ':0') { + $parts = \parse_url(\substr($uri, 0, -2)); + if ($parts) { + $parts['port'] = 0; + } + } else { + $parts = \parse_url($uri); + } + + // ensure URI contains TCP scheme, host and port + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { + throw new \InvalidArgumentException( + 'Invalid URI "' . $uri . '" given (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + ); + } + + if (@\inet_pton(\trim($parts['host'], '[]')) === false) { + throw new \InvalidArgumentException( + 'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + ); + } + + $this->master = @\stream_socket_server( + $uri, + $errno, + $errstr, + \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN, + \stream_context_create(array('socket' => $context + array('backlog' => 511))) + ); + if (false === $this->master) { + if ($errno === 0) { + // PHP does not seem to report errno, so match errno from errstr + // @link https://3v4l.org/3qOBl + $errno = SocketServer::errno($errstr); + } + + throw new \RuntimeException( + 'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno), + $errno + ); + } + \stream_set_blocking($this->master, false); + + $this->resume(); + } + + public function getAddress() + { + if (!\is_resource($this->master)) { + return null; + } + + $address = \stream_socket_get_name($this->master, false); + + // check if this is an IPv6 address which includes multiple colons but no square brackets + $pos = \strrpos($address, ':'); + if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') { + $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore + } + + return 'tcp://' . $address; + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !\is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + try { + $newSocket = SocketServer::accept($master); + } catch (\RuntimeException $e) { + $that->emit('error', array($e)); + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!\is_resource($this->master)) { + return; + } + + $this->pause(); + \fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $this->emit('connection', array( + new Connection($socket, $this->loop) + )); + } +} diff --git a/vendor/react/socket/src/TimeoutConnector.php b/vendor/react/socket/src/TimeoutConnector.php new file mode 100644 index 0000000000..9ef252f7d7 --- /dev/null +++ b/vendor/react/socket/src/TimeoutConnector.php @@ -0,0 +1,79 @@ +connector = $connector; + $this->timeout = $timeout; + $this->loop = $loop ?: Loop::get(); + } + + public function connect($uri) + { + $promise = $this->connector->connect($uri); + + $loop = $this->loop; + $time = $this->timeout; + return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $uri) { + $timer = null; + $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) { + if ($timer) { + $loop->cancelTimer($timer); + } + $timer = false; + $resolve($v); + }, function ($v) use (&$timer, $loop, $reject) { + if ($timer) { + $loop->cancelTimer($timer); + } + $timer = false; + $reject($v); + }); + + // promise already resolved => no need to start timer + if ($timer === false) { + return; + } + + // start timeout timer which will cancel the pending promise + $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $uri) { + $reject(new \RuntimeException( + 'Connection to ' . $uri . ' timed out after ' . $time . ' seconds (ETIMEDOUT)', + \defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110 + )); + + // Cancel pending connection to clean up any underlying resources and references. + // Avoid garbage references in call stack by passing pending promise by reference. + assert(\method_exists($promise, 'cancel')); + $promise->cancel(); + $promise = null; + }); + }, function () use (&$promise) { + // Cancelling this promise will cancel the pending connection, thus triggering the rejection logic above. + // Avoid garbage references in call stack by passing pending promise by reference. + assert(\method_exists($promise, 'cancel')); + $promise->cancel(); + $promise = null; + }); + } +} diff --git a/vendor/react/socket/src/UnixConnector.php b/vendor/react/socket/src/UnixConnector.php new file mode 100644 index 0000000000..95f932cb02 --- /dev/null +++ b/vendor/react/socket/src/UnixConnector.php @@ -0,0 +1,58 @@ +loop = $loop ?: Loop::get(); + } + + public function connect($path) + { + if (\strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (\substr($path, 0, 7) !== 'unix://') { + return Promise\reject(new \InvalidArgumentException( + 'Given URI "' . $path . '" is invalid (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + )); + } + + $resource = @\stream_socket_client($path, $errno, $errstr, 1.0); + + if (!$resource) { + return Promise\reject(new \RuntimeException( + 'Unable to connect to unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno), + $errno + )); + } + + $connection = new Connection($resource, $this->loop); + $connection->unix = true; + + return Promise\resolve($connection); + } +} diff --git a/vendor/react/socket/src/UnixServer.php b/vendor/react/socket/src/UnixServer.php new file mode 100644 index 0000000000..27b014d15d --- /dev/null +++ b/vendor/react/socket/src/UnixServer.php @@ -0,0 +1,162 @@ +loop = $loop ?: Loop::get(); + + if (\strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (\substr($path, 0, 7) !== 'unix://') { + throw new \InvalidArgumentException( + 'Given URI "' . $path . '" is invalid (EINVAL)', + \defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22) + ); + } + + $errno = 0; + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errno, &$errstr) { + // PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now. + // This only applies to UDS server sockets, see also https://3v4l.org/NAhpr. + // Parse PHP warning message containing unknown error, HHVM reports proper info at least. + if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error, $match)) { + $errstr = isset($match[3]) ? $match['3'] : $match[1]; + $errno = isset($match[2]) ? (int)$match[2] : 0; + } + }); + + $this->master = \stream_socket_server( + $path, + $errno, + $errstr, + \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN, + \stream_context_create(array('socket' => $context)) + ); + + \restore_error_handler(); + + if (false === $this->master) { + throw new \RuntimeException( + 'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno), + $errno + ); + } + \stream_set_blocking($this->master, 0); + + $this->resume(); + } + + public function getAddress() + { + if (!\is_resource($this->master)) { + return null; + } + + return 'unix://' . \stream_socket_get_name($this->master, false); + } + + public function pause() + { + if (!$this->listening) { + return; + } + + $this->loop->removeReadStream($this->master); + $this->listening = false; + } + + public function resume() + { + if ($this->listening || !is_resource($this->master)) { + return; + } + + $that = $this; + $this->loop->addReadStream($this->master, function ($master) use ($that) { + try { + $newSocket = SocketServer::accept($master); + } catch (\RuntimeException $e) { + $that->emit('error', array($e)); + return; + } + $that->handleConnection($newSocket); + }); + $this->listening = true; + } + + public function close() + { + if (!\is_resource($this->master)) { + return; + } + + $this->pause(); + \fclose($this->master); + $this->removeAllListeners(); + } + + /** @internal */ + public function handleConnection($socket) + { + $connection = new Connection($socket, $this->loop); + $connection->unix = true; + + $this->emit('connection', array( + $connection + )); + } +} diff --git a/vendor/react/stream/CHANGELOG.md b/vendor/react/stream/CHANGELOG.md new file mode 100644 index 0000000000..639db65857 --- /dev/null +++ b/vendor/react/stream/CHANGELOG.md @@ -0,0 +1,460 @@ +# Changelog + +## 1.4.0 (2024-06-11) + +* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations. + (#179 by @clue) + +* Feature: Full PHP 8.3 compatibility. + (#172 by @clue) + +* Fix: Fix `drain` event of `ThroughStream` to handle potential race condition. + (#171 by @clue) + +## 1.3.0 (2023-06-16) + +* Feature: Full PHP 8.1 and PHP 8.2 compatibility. + (#160 by @SimonFrings, #165 by @clue and #169 by @WyriHaximus) + +* Feature: Avoid unneeded syscall when creating non-blocking `DuplexResourceStream`. + (#164 by @clue) + +* Minor documentation improvements. + (#161 by @mrsimonbennett, #162 by @SimonFrings and #166 by @nhedger) + +* Improve test suite and project setup and report failed assertions. + (#168 and #170 by @clue and #163 by @SimonFrings) + +## 1.2.0 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop). + (#159 by @clue) + + ```php + // old (still supported) + $stream = new ReadableResourceStream($resource, $loop); + $stream = new WritabeResourceStream($resource, $loop); + $stream = new DuplexResourceStream($resource, $loop); + + // new (using default loop) + $stream = new ReadableResourceStream($resource); + $stream = new WritabeResourceStream($resource); + $stream = new DuplexResourceStream($resource); + ``` + +* Improve test suite, use GitHub actions for continuous integration (CI), + update PHPUnit config, run tests on PHP 8 and add full core team to the license. + (#153, #156 and #157 by @SimonFrings and #154 by @WyriHaximus) + +## 1.1.1 (2020-05-04) + +* Fix: Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only). + (#150 by @clue) + +* Minor code style improvements to fix phpstan analysis warnings and + add `.gitattributes` to exclude dev files from exports. + (#140 by @flow-control and #144 by @reedy) + +* Improve test suite to run tests on PHP 7.4 and simplify test matrix. + (#147 by @clue) + +## 1.1.0 (2019-01-01) + +* Improvement: Increase performance by optimizing global function and constant look ups. + (#137 by @WyriHaximus) + +* Travis: Test against PHP 7.3. + (#138 by @WyriHaximus) + +* Fix: Ignore empty reads. + (#139 by @WyriHaximus) + +## 1.0.0 (2018-07-11) + +* First stable LTS release, now following [SemVer](https://semver.org/). + We'd like to emphasize that this component is production ready and battle-tested. + We plan to support all long-term support (LTS) releases for at least 24 months, + so you have a rock-solid foundation to build on top of. + +> Contains no other changes, so it's actually fully compatible with the v0.7.7 release. + +## 0.7.7 (2018-01-19) + +* Improve test suite by fixing forward compatibility with upcoming EventLoop + releases, avoid risky tests and add test group to skip integration tests + relying on internet connection and apply appropriate test timeouts. + (#128, #131 and #132 by @clue) + +## 0.7.6 (2017-12-21) + +* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12 + (#126 by @clue) + +* Improve test suite by simplifying test bootstrapping logic via Composer and + test against PHP 7.2 + (#127 by @clue and #124 by @carusogabriel) + +## 0.7.5 (2017-11-20) + +* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream` + (#119 by @clue) + +* Fix: Fix forward compatibility with upcoming EventLoop releases + (#121 by @clue) + +* Restructure examples to ease getting started + (#123 by @clue) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and + ignore Mac OS X test failures for now until Travis tests work again + (#122 by @gabriel-caruso and #120 by @clue) + +## 0.7.4 (2017-10-11) + +* Fix: Remove event listeners from `CompositeStream` once closed and + remove undocumented left-over `close` event argument + (#116 by @clue) + +* Minor documentation improvements: Fix wrong class name in example, + fix typos in README and + fix forward compatibility with upcoming EventLoop releases in example + (#113 by @docteurklein and #114 and #115 by @clue) + +* Improve test suite by running against Mac OS X on Travis + (#112 by @clue) + +## 0.7.3 (2017-08-05) + +* Improvement: Support Événement 3.0 a long side 2.0 and 1.0 + (#108 by @WyriHaximus) + +* Readme: Corrected loop initialization in usage example + (#109 by @pulyavin) + +* Travis: Lock linux distribution preventing future builds from breaking + (#110 by @clue) + +## 0.7.2 (2017-06-15) + +* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream. + (#107 by @WyriHaximus) + +## 0.7.1 (2017-05-20) + +* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of + bytes to write at once. + (#105 by @clue) + + ```php + $stream = new WritableResourceStream(STDOUT, $loop, null, 8192); + ``` + +* Ignore HHVM test failures for now until Travis tests work again + (#106 by @clue) + +## 0.7.0 (2017-05-04) + +* Removed / BC break: Remove deprecated and unneeded functionality + (#45, #87, #90, #91 and #93 by @clue) + + * Remove deprecated `Stream` class, use `DuplexResourceStream` instead + (#87 by @clue) + + * Remove public `$buffer` property, use new constructor parameters instead + (#91 by @clue) + + * Remove public `$stream` property from all resource streams + (#90 by @clue) + + * Remove undocumented and now unused `ReadableStream` and `WritableStream` + (#93 by @clue) + + * Remove `BufferedSink` + (#45 by @clue) + +* Feature / BC break: Simplify `ThroughStream` by using data callback instead of + inheritance. It is now a direct implementation of `DuplexStreamInterface`. + (#88 and #89 by @clue) + + ```php + $through = new ThroughStream(function ($data) { + return json_encode($data) . PHP_EOL; + }); + $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); + + $through->write(array(2, true)); + ``` + +* Feature / BC break: The `CompositeStream` starts closed if either side is + already closed and forwards pause to pipe source on first write attempt. + (#96 and #103 by @clue) + + If either side of the composite stream closes, it will also close the other + side. We now also ensure that if either side is already closed during + instantiation, it will also close the other side. + +* BC break: Mark all classes as `final` and + mark internal API as `private` to discourage inheritance + (#95 and #99 by @clue) + +* Feature / BC break: Only emit `error` event for fatal errors + (#92 by @clue) + + > The `error` event was previously also allowed to be emitted for non-fatal + errors, but our implementations actually only ever emitted this as a fatal + error and then closed the stream. + +* Feature: Explicitly allow custom events and exclude any semantics + (#97 by @clue) + +* Strict definition for event callback functions + (#101 by @clue) + +* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation + (#100 and #102 by @clue) + +* Actually require all dependencies so this is self-contained and improve + forward compatibility with EventLoop v1.0 and v0.5 + (#94 and #98 by @clue) + +## 0.6.0 (2017-03-26) + +* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream` + (#85 by @clue) + + ```php + // old (does still work for BC reasons) + $stream = new Stream($connection, $loop); + + // new + $stream = new DuplexResourceStream($connection, $loop); + ``` + + Note that the `DuplexResourceStream` now rejects read-only or write-only + streams, so this may affect BC. If you want a read-only or write-only + resource, use `ReadableResourceStream` or `WritableResourceStream` instead of + `DuplexResourceStream`. + + > BC note: This class was previously called `Stream`. The `Stream` class still + exists for BC reasons and will be removed in future versions of this package. + +* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`) + (#84 by @clue) + + ```php + // old + $stream = new Buffer(STDOUT, $loop); + + // new + $stream = new WritableResourceStream(STDOUT, $loop); + ``` + +* Feature: Add `ReadableResourceStream` + (#83 by @clue) + + ```php + $stream = new ReadableResourceStream(STDIN, $loop); + ``` + +* Fix / BC Break: Enforce using non-blocking I/O + (#46 by @clue) + + > BC note: This is known to affect process pipes on Windows which do not + support non-blocking I/O and could thus block the whole EventLoop previously. + +* Feature / Fix / BC break: Consistent semantics for + `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side + (#86 by @clue) + +* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4 + (#80 by @clue) + +## 0.5.0 (2017-03-08) + +* Feature / BC break: Consistent `end` event semantics (EOF) + (#70 by @clue) + + The `end` event will now only be emitted for a *successful* end, not if the + stream closes due to an unrecoverable `error` event or if you call `close()` + explicitly. + If you want to detect when the stream closes (terminates), use the `close` + event instead. + +* BC break: Remove custom (undocumented) `full-drain` event from `Buffer` + (#63 and #68 by @clue) + + > The `full-drain` event was undocumented and mostly used internally. + Relying on this event has attracted some low-quality code in the past, so + we've removed this from the public API in order to work out a better + solution instead. + If you want to detect when the buffer finishes flushing data to the stream, + you may want to look into its `end()` method or the `close` event instead. + +* Feature / BC break: Consistent event semantics and documentation, + explicitly state *when* events will be emitted and *which* arguments they + receive. + (#73 and #69 by @clue) + + The documentation now explicitly defines each event and its arguments. + Custom events and event arguments are still supported. + Most notably, all defined events only receive inherently required event + arguments and no longer transmit the instance they are emitted on for + consistency and performance reasons. + + ```php + // old (inconsistent and not supported by all implementations) + $stream->on('data', function ($data, $stream) { + // process $data + }); + + // new (consistent throughout the whole ecosystem) + $stream->on('data', function ($data) use ($stream) { + // process $data + }); + ``` + + > This mostly adds documentation (and thus some stricter, consistent + definitions) for the existing behavior, it does NOT define any major + changes otherwise. + Most existing code should be compatible with these changes, unless + it relied on some undocumented/unintended semantics. + +* Feature / BC break: Consistent method semantics and documentation + (#72 by @clue) + + > This mostly adds documentation (and thus some stricter, consistent + definitions) for the existing behavior, it does NOT define any major + changes otherwise. + Most existing code should be compatible with these changes, unless + it relied on some undocumented/unintended semantics. + +* Feature: Consistent `pipe()` semantics for closed and closing streams + (#71 from @clue) + + The source stream will now always be paused via `pause()` when the + destination stream closes. Also, properly stop piping if the source + stream closes and remove all event forwarding. + +* Improve test suite by adding PHPUnit to `require-dev` and improving coverage. + (#74 and #75 by @clue, #66 by @nawarian) + +## 0.4.6 (2017-01-25) + +* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone) + (#62 by @clue) + +* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream` + (#60 by @clue) + +* Fix: Consistent `close` event behavior for `Buffer` + (#61 by @clue) + +## 0.4.5 (2016-11-13) + +* Feature: Support setting read buffer size to `null` (infinite) + (#42 by @clue) + +* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event + (#55 by @clue) + +* Vastly improved performance by factor of 10x to 20x. + Raise default buffer sizes to 64 KiB and simplify and improve error handling + and unneeded function calls. + (#53, #55, #56 by @clue) + +## 0.4.4 (2016-08-22) + +* Bug fix: Emit `error` event and close `Stream` when accessing the underlying + stream resource fails with a permanent error. + (#52 and #40 by @clue, #25 by @lysenkobv) + +* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF) + (#39 by @clue) + +* Bug fix: Ignore empty writes to `Buffer` + (#51 by @clue) + +* Add benchmarking script to measure throughput in CI + (#41 by @clue) + +## 0.4.3 (2015-10-07) + +* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau) +* Bug fix: No double-write during drain call (@arnaud-lb) +* Bug fix: Support HHVM (@clue) +* Adjust compatibility to 5.3 (@clue) + +## 0.4.2 (2014-09-09) + +* Added DuplexStreamInterface +* Stream sets stream resources to non-blocking +* Fixed potential race condition in pipe + +## 0.4.1 (2014-04-13) + +* Bug fix: v0.3.4 changes merged for v0.4.1 + +## 0.3.4 (2014-03-30) + +* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream + +## 0.4.0 (2014-02-02) + +* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks +* BC break: Update to Evenement 2.0 +* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 + +## 0.3.3 (2013-07-08) + +* Bug fix: [Stream] Correctly detect closed connections + +## 0.3.2 (2013-05-10) + +* Bug fix: [Stream] Make sure CompositeStream is closed properly + +## 0.3.1 (2013-04-21) + +* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()` + +## 0.3.0 (2013-04-14) + +* Feature: [Stream] Factory method for BufferedSink + +## 0.2.6 (2012-12-26) + +* Version bump + +## 0.2.5 (2012-11-26) + +* Feature: Make BufferedSink trigger progress events on the promise (@jsor) + +## 0.2.4 (2012-11-18) + +* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream +* Feature: Added BufferedSink + +## 0.2.3 (2012-11-14) + +* Version bump + +## 0.2.2 (2012-10-28) + +* Version bump + +## 0.2.1 (2012-10-14) + +* Bug fix: Check for EOF in `Buffer::write()` + +## 0.2.0 (2012-09-10) + +* Version bump + +## 0.1.1 (2012-07-12) + +* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8 + +## 0.1.0 (2012-07-11) + +* First tagged release diff --git a/vendor/react/stream/LICENSE b/vendor/react/stream/LICENSE new file mode 100644 index 0000000000..d6f8901f9a --- /dev/null +++ b/vendor/react/stream/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/react/stream/README.md b/vendor/react/stream/README.md new file mode 100644 index 0000000000..9c0468a659 --- /dev/null +++ b/vendor/react/stream/README.md @@ -0,0 +1,1249 @@ +# Stream + +[![CI status](https://github.com/reactphp/stream/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/stream/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/stream?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/stream) + +Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). + +In order to make the [EventLoop](https://github.com/reactphp/event-loop) +easier to use, this component introduces the powerful concept of "streams". +Streams allow you to efficiently process huge amounts of data (such as a multi +Gigabyte file download) in small chunks without having to store everything in +memory at once. +They are very similar to the streams found in PHP itself, +but have an interface more suited for async, non-blocking I/O. + +**Table of contents** + +* [Stream usage](#stream-usage) + * [ReadableStreamInterface](#readablestreaminterface) + * [data event](#data-event) + * [end event](#end-event) + * [error event](#error-event) + * [close event](#close-event) + * [isReadable()](#isreadable) + * [pause()](#pause) + * [resume()](#resume) + * [pipe()](#pipe) + * [close()](#close) + * [WritableStreamInterface](#writablestreaminterface) + * [drain event](#drain-event) + * [pipe event](#pipe-event) + * [error event](#error-event-1) + * [close event](#close-event-1) + * [isWritable()](#iswritable) + * [write()](#write) + * [end()](#end) + * [close()](#close-1) + * [DuplexStreamInterface](#duplexstreaminterface) +* [Creating streams](#creating-streams) + * [ReadableResourceStream](#readableresourcestream) + * [WritableResourceStream](#writableresourcestream) + * [DuplexResourceStream](#duplexresourcestream) + * [ThroughStream](#throughstream) + * [CompositeStream](#compositestream) +* [Usage](#usage) +* [Install](#install) +* [Tests](#tests) +* [License](#license) +* [More](#more) + +## Stream usage + +ReactPHP uses the concept of "streams" throughout its ecosystem to provide a +consistent higher-level abstraction for processing streams of arbitrary data +contents and size. +While a stream itself is a quite low-level concept, it can be used as a powerful +abstraction to build higher-level components and protocols on top. + +If you're new to this concept, it helps to think of them as a water pipe: +You can consume water from a source or you can produce water and forward (pipe) +it to any destination (sink). + +Similarly, streams can either be + +* readable (such as `STDIN` terminal input) or +* writable (such as `STDOUT` terminal output) or +* duplex (both readable *and* writable, such as a TCP/IP connection) + +Accordingly, this package defines the following three interfaces + +* [`ReadableStreamInterface`](#readablestreaminterface) +* [`WritableStreamInterface`](#writablestreaminterface) +* [`DuplexStreamInterface`](#duplexstreaminterface) + +### ReadableStreamInterface + +The `ReadableStreamInterface` is responsible for providing an interface for +read-only streams and the readable side of duplex streams. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to certain events. + +The event callback functions MUST be a valid `callable` that obeys strict +parameter definitions and MUST accept event parameters exactly as documented. +The event callback functions MUST NOT throw an `Exception`. +The return value of the event callback functions will be ignored and has no +effect, so for performance reasons you're recommended to not return any +excessive data structures. + +Every implementation of this interface MUST follow these event semantics in +order to be considered a well-behaving stream. + +> Note that higher-level implementations of this interface may choose to + define additional events with dedicated semantics not defined as part of + this low-level stream specification. Conformance with these event semantics + is out of scope for this interface, so you may also have to refer to the + documentation of such a higher-level implementation. + +#### data event + +The `data` event will be emitted whenever some data was read/received +from this source stream. +The event receives a single mixed argument for incoming data. + +```php +$stream->on('data', function ($data) { + echo $data; +}); +``` + +This event MAY be emitted any number of times, which may be zero times if +this stream does not send any data at all. +It SHOULD not be emitted after an `end` or `close` event. + +The given `$data` argument may be of mixed type, but it's usually +recommended it SHOULD be a `string` value or MAY use a type that allows +representation as a `string` for maximum compatibility. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will emit the raw (binary) payload data that is received over the wire as +chunks of `string` values. + +Due to the stream-based nature of this, the sender may send any number +of chunks with varying sizes. There are no guarantees that these chunks +will be received with the exact same framing the sender intended to send. +In other words, many lower-level protocols (such as TCP/IP) transfer the +data in chunks that may be anywhere between single-byte values to several +dozens of kilobytes. You may want to apply a higher-level protocol to +these low-level data chunks in order to achieve proper message framing. + +#### end event + +The `end` event will be emitted once the source stream has successfully +reached the end of the stream (EOF). + +```php +$stream->on('end', function () { + echo 'END'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +a successful end was detected. +It SHOULD NOT be emitted after a previous `end` or `close` event. +It MUST NOT be emitted if the stream closes due to a non-successful +end, such as after a previous `error` event. + +After the stream is ended, it MUST switch to non-readable mode, +see also `isReadable()`. + +This event will only be emitted if the *end* was reached successfully, +not if the stream was interrupted by an unrecoverable error or explicitly +closed. Not all streams know this concept of a "successful end". +Many use-cases involve detecting when the stream closes (terminates) +instead, in this case you should use the `close` event. +After the stream emits an `end` event, it SHOULD usually be followed by a +`close` event. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will emit this event if either the remote side closes the connection or +a file handle was successfully read until reaching its end (EOF). + +Note that this event should not be confused with the `end()` method. +This event defines a successful end *reading* from a source stream, while +the `end()` method defines *writing* a successful end to a destination +stream. + +#### error event + +The `error` event will be emitted once a fatal error occurs, usually while +trying to read from this stream. +The event receives a single `Exception` argument for the error instance. + +```php +$server->on('error', function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +This event SHOULD be emitted once the stream detects a fatal error, such +as a fatal transmission error or after an unexpected `data` or premature +`end` event. +It SHOULD NOT be emitted after a previous `error`, `end` or `close` event. +It MUST NOT be emitted if this is not a fatal error condition, such as +a temporary network issue that did not cause any data to be lost. + +After the stream errors, it MUST close the stream and SHOULD thus be +followed by a `close` event and then switch to non-readable mode, see +also `close()` and `isReadable()`. + +Many common streams (such as a TCP/IP connection or a file-based stream) +only deal with data transmission and do not make assumption about data +boundaries (such as unexpected `data` or premature `end` events). +In other words, many lower-level protocols (such as TCP/IP) may choose +to only emit this for a fatal transmission error once and will then +close (terminate) the stream in response. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements an `error` event. +In other words, an error may occur while either reading or writing the +stream which should result in the same error processing. + +#### close event + +The `close` event will be emitted once the stream closes (terminates). + +```php +$stream->on('close', function () { + echo 'CLOSED'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +the stream ever terminates. +It SHOULD NOT be emitted after a previous `close` event. + +After the stream is closed, it MUST switch to non-readable mode, +see also `isReadable()`. + +Unlike the `end` event, this event SHOULD be emitted whenever the stream +closes, irrespective of whether this happens implicitly due to an +unrecoverable error or explicitly when either side closes the stream. +If you only want to detect a *successful* end, you should use the `end` +event instead. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will likely choose to emit this event after reading a *successful* `end` +event or after a fatal transmission `error` event. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements a `close` event. +In other words, after receiving this event, the stream MUST switch into +non-writable AND non-readable mode, see also `isWritable()`. +Note that this event should not be confused with the `end` event. + +#### isReadable() + +The `isReadable(): bool` method can be used to +check whether this stream is in a readable state (not closed already). + +This method can be used to check if the stream still accepts incoming +data events or if it is ended or closed already. +Once the stream is non-readable, no further `data` or `end` events SHOULD +be emitted. + +```php +assert($stream->isReadable() === false); + +$stream->on('data', assertNeverCalled()); +$stream->on('end', assertNeverCalled()); +``` + +A successfully opened stream always MUST start in readable mode. + +Once the stream ends or closes, it MUST switch to non-readable mode. +This can happen any time, explicitly through `close()` or +implicitly due to a remote close or an unrecoverable transmission error. +Once a stream has switched to non-readable mode, it MUST NOT transition +back to readable mode. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements an `isWritable()` +method. Unless this is a half-open duplex stream, they SHOULD usually +have the same return value. + +#### pause() + +The `pause(): void` method can be used to +pause reading incoming data events. + +Removes the data source file descriptor from the event loop. This +allows you to throttle incoming data. + +Unless otherwise noted, a successfully opened stream SHOULD NOT start +in paused state. + +Once the stream is paused, no futher `data` or `end` events SHOULD +be emitted. + +```php +$stream->pause(); + +$stream->on('data', assertShouldNeverCalled()); +$stream->on('end', assertShouldNeverCalled()); +``` + +This method is advisory-only, though generally not recommended, the +stream MAY continue emitting `data` events. + +You can continue processing events by calling `resume()` again. + +Note that both methods can be called any number of times, in particular +calling `pause()` more than once SHOULD NOT have any effect. + +See also `resume()`. + +#### resume() + +The `resume(): void` method can be used to +resume reading incoming data events. + +Re-attach the data source after a previous `pause()`. + +```php +$stream->pause(); + +Loop::addTimer(1.0, function () use ($stream) { + $stream->resume(); +}); +``` + +Note that both methods can be called any number of times, in particular +calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + +See also `pause()`. + +#### pipe() + +The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to +pipe all the data from this readable source into the given writable destination. + +Automatically sends all incoming data to the destination. +Automatically throttles the source based on what the destination can handle. + +```php +$source->pipe($dest); +``` + +Similarly, you can also pipe an instance implementing `DuplexStreamInterface` +into itself in order to write back all the data that is received. +This may be a useful feature for a TCP/IP echo service: + +```php +$connection->pipe($connection); +``` + +This method returns the destination stream as-is, which can be used to +set up chains of piped streams: + +```php +$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest); +``` + +By default, this will call `end()` on the destination stream once the +source stream emits an `end` event. This can be disabled like this: + +```php +$source->pipe($dest, array('end' => false)); +``` + +Note that this only applies to the `end` event. +If an `error` or explicit `close` event happens on the source stream, +you'll have to manually close the destination stream: + +```php +$source->pipe($dest); +$source->on('close', function () use ($dest) { + $dest->end('BYE!'); +}); +``` + +If the source stream is not readable (closed state), then this is a NO-OP. + +```php +$source->close(); +$source->pipe($dest); // NO-OP +``` + +If the destinantion stream is not writable (closed state), then this will simply +throttle (pause) the source stream: + +```php +$dest->close(); +$source->pipe($dest); // calls $source->pause() +``` + +Similarly, if the destination stream is closed while the pipe is still +active, it will also throttle (pause) the source stream: + +```php +$source->pipe($dest); +$dest->close(); // calls $source->pause() +``` + +Once the pipe is set up successfully, the destination stream MUST emit +a `pipe` event with this source stream an event argument. + +#### close() + +The `close(): void` method can be used to +close the stream (forcefully). + +This method can be used to (forcefully) close the stream. + +```php +$stream->close(); +``` + +Once the stream is closed, it SHOULD emit a `close` event. +Note that this event SHOULD NOT be emitted more than once, in particular +if this method is called multiple times. + +After calling this method, the stream MUST switch into a non-readable +mode, see also `isReadable()`. +This means that no further `data` or `end` events SHOULD be emitted. + +```php +$stream->close(); +assert($stream->isReadable() === false); + +$stream->on('data', assertNeverCalled()); +$stream->on('end', assertNeverCalled()); +``` + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements a `close()` method. +In other words, after calling this method, the stream MUST switch into +non-writable AND non-readable mode, see also `isWritable()`. +Note that this method should not be confused with the `end()` method. + +### WritableStreamInterface + +The `WritableStreamInterface` is responsible for providing an interface for +write-only streams and the writable side of duplex streams. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to certain events. + +The event callback functions MUST be a valid `callable` that obeys strict +parameter definitions and MUST accept event parameters exactly as documented. +The event callback functions MUST NOT throw an `Exception`. +The return value of the event callback functions will be ignored and has no +effect, so for performance reasons you're recommended to not return any +excessive data structures. + +Every implementation of this interface MUST follow these event semantics in +order to be considered a well-behaving stream. + +> Note that higher-level implementations of this interface may choose to + define additional events with dedicated semantics not defined as part of + this low-level stream specification. Conformance with these event semantics + is out of scope for this interface, so you may also have to refer to the + documentation of such a higher-level implementation. + +#### drain event + +The `drain` event will be emitted whenever the write buffer became full +previously and is now ready to accept more data. + +```php +$stream->on('drain', function () use ($stream) { + echo 'Stream is now ready to accept more data'; +}); +``` + +This event SHOULD be emitted once every time the buffer became full +previously and is now ready to accept more data. +In other words, this event MAY be emitted any number of times, which may +be zero times if the buffer never became full in the first place. +This event SHOULD NOT be emitted if the buffer has not become full +previously. + +This event is mostly used internally, see also `write()` for more details. + +#### pipe event + +The `pipe` event will be emitted whenever a readable stream is `pipe()`d +into this stream. +The event receives a single `ReadableStreamInterface` argument for the +source stream. + +```php +$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + echo 'Now receiving piped data'; + + // explicitly close target if source emits an error + $source->on('error', function () use ($stream) { + $stream->close(); + }); +}); + +$source->pipe($stream); +``` + +This event MUST be emitted once for each readable stream that is +successfully piped into this destination stream. +In other words, this event MAY be emitted any number of times, which may +be zero times if no stream is ever piped into this stream. +This event MUST NOT be emitted if either the source is not readable +(closed already) or this destination is not writable (closed already). + +This event is mostly used internally, see also `pipe()` for more details. + +#### error event + +The `error` event will be emitted once a fatal error occurs, usually while +trying to write to this stream. +The event receives a single `Exception` argument for the error instance. + +```php +$stream->on('error', function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +This event SHOULD be emitted once the stream detects a fatal error, such +as a fatal transmission error. +It SHOULD NOT be emitted after a previous `error` or `close` event. +It MUST NOT be emitted if this is not a fatal error condition, such as +a temporary network issue that did not cause any data to be lost. + +After the stream errors, it MUST close the stream and SHOULD thus be +followed by a `close` event and then switch to non-writable mode, see +also `close()` and `isWritable()`. + +Many common streams (such as a TCP/IP connection or a file-based stream) +only deal with data transmission and may choose +to only emit this for a fatal transmission error once and will then +close (terminate) the stream in response. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements an `error` event. +In other words, an error may occur while either reading or writing the +stream which should result in the same error processing. + +#### close event + +The `close` event will be emitted once the stream closes (terminates). + +```php +$stream->on('close', function () { + echo 'CLOSED'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +the stream ever terminates. +It SHOULD NOT be emitted after a previous `close` event. + +After the stream is closed, it MUST switch to non-writable mode, +see also `isWritable()`. + +This event SHOULD be emitted whenever the stream closes, irrespective of +whether this happens implicitly due to an unrecoverable error or +explicitly when either side closes the stream. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will likely choose to emit this event after flushing the buffer from +the `end()` method, after receiving a *successful* `end` event or after +a fatal transmission `error` event. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements a `close` event. +In other words, after receiving this event, the stream MUST switch into +non-writable AND non-readable mode, see also `isReadable()`. +Note that this event should not be confused with the `end` event. + +#### isWritable() + +The `isWritable(): bool` method can be used to +check whether this stream is in a writable state (not closed already). + +This method can be used to check if the stream still accepts writing +any data or if it is ended or closed already. +Writing any data to a non-writable stream is a NO-OP: + +```php +assert($stream->isWritable() === false); + +$stream->write('end'); // NO-OP +$stream->end('end'); // NO-OP +``` + +A successfully opened stream always MUST start in writable mode. + +Once the stream ends or closes, it MUST switch to non-writable mode. +This can happen any time, explicitly through `end()` or `close()` or +implicitly due to a remote close or an unrecoverable transmission error. +Once a stream has switched to non-writable mode, it MUST NOT transition +back to writable mode. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements an `isReadable()` +method. Unless this is a half-open duplex stream, they SHOULD usually +have the same return value. + +#### write() + +The `write(mixed $data): bool` method can be used to +write some data into the stream. + +A successful write MUST be confirmed with a boolean `true`, which means +that either the data was written (flushed) immediately or is buffered and +scheduled for a future write. Note that this interface gives you no +control over explicitly flushing the buffered data, as finding the +appropriate time for this is beyond the scope of this interface and left +up to the implementation of this interface. + +Many common streams (such as a TCP/IP connection or file-based stream) +may choose to buffer all given data and schedule a future flush by using +an underlying EventLoop to check when the resource is actually writable. + +If a stream cannot handle writing (or flushing) the data, it SHOULD emit +an `error` event and MAY `close()` the stream if it can not recover from +this error. + +If the internal buffer is full after adding `$data`, then `write()` +SHOULD return `false`, indicating that the caller should stop sending +data until the buffer drains. +The stream SHOULD send a `drain` event once the buffer is ready to accept +more data. + +Similarly, if the stream is not writable (already in a closed state) +it MUST NOT process the given `$data` and SHOULD return `false`, +indicating that the caller should stop sending data. + +The given `$data` argument MAY be of mixed type, but it's usually +recommended it SHOULD be a `string` value or MAY use a type that allows +representation as a `string` for maximum compatibility. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will only accept the raw (binary) payload data that is transferred over +the wire as chunks of `string` values. + +Due to the stream-based nature of this, the sender may send any number +of chunks with varying sizes. There are no guarantees that these chunks +will be received with the exact same framing the sender intended to send. +In other words, many lower-level protocols (such as TCP/IP) transfer the +data in chunks that may be anywhere between single-byte values to several +dozens of kilobytes. You may want to apply a higher-level protocol to +these low-level data chunks in order to achieve proper message framing. + +#### end() + +The `end(mixed $data = null): void` method can be used to +successfully end the stream (after optionally sending some final data). + +This method can be used to successfully end the stream, i.e. close +the stream after sending out all data that is currently buffered. + +```php +$stream->write('hello'); +$stream->write('world'); +$stream->end(); +``` + +If there's no data currently buffered and nothing to be flushed, then +this method MAY `close()` the stream immediately. + +If there's still data in the buffer that needs to be flushed first, then +this method SHOULD try to write out this data and only then `close()` +the stream. +Once the stream is closed, it SHOULD emit a `close` event. + +Note that this interface gives you no control over explicitly flushing +the buffered data, as finding the appropriate time for this is beyond the +scope of this interface and left up to the implementation of this +interface. + +Many common streams (such as a TCP/IP connection or file-based stream) +may choose to buffer all given data and schedule a future flush by using +an underlying EventLoop to check when the resource is actually writable. + +You can optionally pass some final data that is written to the stream +before ending the stream. If a non-`null` value is given as `$data`, then +this method will behave just like calling `write($data)` before ending +with no data. + +```php +// shorter version +$stream->end('bye'); + +// same as longer version +$stream->write('bye'); +$stream->end(); +``` + +After calling this method, the stream MUST switch into a non-writable +mode, see also `isWritable()`. +This means that no further writes are possible, so any additional +`write()` or `end()` calls have no effect. + +```php +$stream->end(); +assert($stream->isWritable() === false); + +$stream->write('nope'); // NO-OP +$stream->end(); // NO-OP +``` + +If this stream is a `DuplexStreamInterface`, calling this method SHOULD +also end its readable side, unless the stream supports half-open mode. +In other words, after calling this method, these streams SHOULD switch +into non-writable AND non-readable mode, see also `isReadable()`. +This implies that in this case, the stream SHOULD NOT emit any `data` +or `end` events anymore. +Streams MAY choose to use the `pause()` method logic for this, but +special care may have to be taken to ensure a following call to the +`resume()` method SHOULD NOT continue emitting readable events. + +Note that this method should not be confused with the `close()` method. + +#### close() + +The `close(): void` method can be used to +close the stream (forcefully). + +This method can be used to forcefully close the stream, i.e. close +the stream without waiting for any buffered data to be flushed. +If there's still data in the buffer, this data SHOULD be discarded. + +```php +$stream->close(); +``` + +Once the stream is closed, it SHOULD emit a `close` event. +Note that this event SHOULD NOT be emitted more than once, in particular +if this method is called multiple times. + +After calling this method, the stream MUST switch into a non-writable +mode, see also `isWritable()`. +This means that no further writes are possible, so any additional +`write()` or `end()` calls have no effect. + +```php +$stream->close(); +assert($stream->isWritable() === false); + +$stream->write('nope'); // NO-OP +$stream->end(); // NO-OP +``` + +Note that this method should not be confused with the `end()` method. +Unlike the `end()` method, this method does not take care of any existing +buffers and simply discards any buffer contents. +Likewise, this method may also be called after calling `end()` on a +stream in order to stop waiting for the stream to flush its final data. + +```php +$stream->end(); +Loop::addTimer(1.0, function () use ($stream) { + $stream->close(); +}); +``` + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements a `close()` method. +In other words, after calling this method, the stream MUST switch into +non-writable AND non-readable mode, see also `isReadable()`. + +### DuplexStreamInterface + +The `DuplexStreamInterface` is responsible for providing an interface for +duplex streams (both readable and writable). + +It builds on top of the existing interfaces for readable and writable streams +and follows the exact same method and event semantics. +If you're new to this concept, you should look into the +`ReadableStreamInterface` and `WritableStreamInterface` first. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to the same events defined +on the `ReadbleStreamInterface` and `WritableStreamInterface`. + +The event callback functions MUST be a valid `callable` that obeys strict +parameter definitions and MUST accept event parameters exactly as documented. +The event callback functions MUST NOT throw an `Exception`. +The return value of the event callback functions will be ignored and has no +effect, so for performance reasons you're recommended to not return any +excessive data structures. + +Every implementation of this interface MUST follow these event semantics in +order to be considered a well-behaving stream. + +> Note that higher-level implementations of this interface may choose to + define additional events with dedicated semantics not defined as part of + this low-level stream specification. Conformance with these event semantics + is out of scope for this interface, so you may also have to refer to the + documentation of such a higher-level implementation. + +See also [`ReadableStreamInterface`](#readablestreaminterface) and +[`WritableStreamInterface`](#writablestreaminterface) for more details. + +## Creating streams + +ReactPHP uses the concept of "streams" throughout its ecosystem, so that +many higher-level consumers of this package only deal with +[stream usage](#stream-usage). +This implies that stream instances are most often created within some +higher-level components and many consumers never actually have to deal with +creating a stream instance. + +* Use [react/socket](https://github.com/reactphp/socket) + if you want to accept incoming or establish outgoing plaintext TCP/IP or + secure TLS socket connection streams. +* Use [react/http](https://github.com/reactphp/http) + if you want to receive an incoming HTTP request body streams. +* Use [react/child-process](https://github.com/reactphp/child-process) + if you want to communicate with child processes via process pipes such as + STDIN, STDOUT, STDERR etc. +* Use experimental [react/filesystem](https://github.com/reactphp/filesystem) + if you want to read from / write to the filesystem. +* See also the last chapter for [more real-world applications](#more). + +However, if you are writing a lower-level component or want to create a stream +instance from a stream resource, then the following chapter is for you. + +> Note that the following examples use `fopen()` and `stream_socket_client()` + for illustration purposes only. + These functions SHOULD NOT be used in a truly async program because each call + may take several seconds to complete and would block the EventLoop otherwise. + Additionally, the `fopen()` call will return a file handle on some platforms + which may or may not be supported by all EventLoop implementations. + As an alternative, you may want to use higher-level libraries listed above. + +### ReadableResourceStream + +The `ReadableResourceStream` is a concrete implementation of the +[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources. + +This can be used to represent a read-only resource like a file stream opened in +readable mode or a stream such as `STDIN`: + +```php +$stream = new ReadableResourceStream(STDIN); +$stream->on('data', function ($chunk) { + echo $chunk; +}); +$stream->on('end', function () { + echo 'END'; +}); +``` + +See also [`ReadableStreamInterface`](#readablestreaminterface) for more details. + +The first parameter given to the constructor MUST be a valid stream resource +that is opened in reading mode (e.g. `fopen()` mode `r`). +Otherwise, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException +$stream = new ReadableResourceStream(false); +``` + +See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write +stream resources otherwise. + +Internally, this class tries to enable non-blocking mode on the stream resource +which may not be supported for all stream resources. +Most notably, this is not supported by pipes on Windows (STDIN etc.). +If this fails, it will throw a `RuntimeException`: + +```php +// throws RuntimeException on Windows +$stream = new ReadableResourceStream(STDIN); +``` + +Once the constructor is called with a valid stream resource, this class will +take care of the underlying stream resource. +You SHOULD only use its public API and SHOULD NOT interfere with the underlying +stream resource manually. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +This class takes an optional `int|null $readChunkSize` parameter that controls +the maximum buffer size in bytes to read at once from the stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. +This can be a positive number which means that up to X bytes will be read +at once from the underlying stream resource. Note that the actual number +of bytes read may be lower if the stream resource has less than X bytes +currently available. +This can be `-1` which means "read everything available" from the +underlying stream resource. +This should read until the stream resource is not readable anymore +(i.e. underlying buffer drained), note that this does not neccessarily +mean it reached EOF. + +```php +$stream = new ReadableResourceStream(STDIN, null, 8192); +``` + +> PHP bug warning: If the PHP process has explicitly been started without a + `STDIN` stream, then trying to read from `STDIN` may return data from + another stream resource. This does not happen if you start this with an empty + stream like `php test.php < /dev/null` instead of `php test.php <&-`. + See [#81](https://github.com/reactphp/stream/issues/81) for more details. + +> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a + `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). + +### WritableResourceStream + +The `WritableResourceStream` is a concrete implementation of the +[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources. + +This can be used to represent a write-only resource like a file stream opened in +writable mode or a stream such as `STDOUT` or `STDERR`: + +```php +$stream = new WritableResourceStream(STDOUT); +$stream->write('hello!'); +$stream->end(); +``` + +See also [`WritableStreamInterface`](#writablestreaminterface) for more details. + +The first parameter given to the constructor MUST be a valid stream resource +that is opened for writing. +Otherwise, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException +$stream = new WritableResourceStream(false); +``` + +See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write +stream resources otherwise. + +Internally, this class tries to enable non-blocking mode on the stream resource +which may not be supported for all stream resources. +Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.). +If this fails, it will throw a `RuntimeException`: + +```php +// throws RuntimeException on Windows +$stream = new WritableResourceStream(STDOUT); +``` + +Once the constructor is called with a valid stream resource, this class will +take care of the underlying stream resource. +You SHOULD only use its public API and SHOULD NOT interfere with the underlying +stream resource manually. + +Any `write()` calls to this class will not be performed instantly, but will +be performed asynchronously, once the EventLoop reports the stream resource is +ready to accept data. +For this, it uses an in-memory buffer string to collect all outstanding writes. +This buffer has a soft-limit applied which defines how much data it is willing +to accept before the caller SHOULD stop sending further data. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls +this maximum buffer size in bytes. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. + +```php +$stream = new WritableResourceStream(STDOUT, null, 8192); +``` + +This class takes an optional `int|null $writeChunkSize` parameter that controls +this maximum buffer size in bytes to write at once to the stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. +This can be a positive number which means that up to X bytes will be written +at once to the underlying stream resource. Note that the actual number +of bytes written may be lower if the stream resource has less than X bytes +currently available. +This can be `-1` which means "write everything available" to the +underlying stream resource. + +```php +$stream = new WritableResourceStream(STDOUT, null, null, 8192); +``` + +See also [`write()`](#write) for more details. + +> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a + `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). + +### DuplexResourceStream + +The `DuplexResourceStream` is a concrete implementation of the +[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources. + +This can be used to represent a read-and-write resource like a file stream opened +in read and write mode mode or a stream such as a TCP/IP connection: + +```php +$conn = stream_socket_client('tcp://google.com:80'); +$stream = new DuplexResourceStream($conn); +$stream->write('hello!'); +$stream->end(); +``` + +See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details. + +The first parameter given to the constructor MUST be a valid stream resource +that is opened for reading *and* writing. +Otherwise, it will throw an `InvalidArgumentException`: + +```php +// throws InvalidArgumentException +$stream = new DuplexResourceStream(false); +``` + +See also the [`ReadableResourceStream`](#readableresourcestream) for read-only +and the [`WritableResourceStream`](#writableresourcestream) for write-only +stream resources otherwise. + +Internally, this class tries to enable non-blocking mode on the stream resource +which may not be supported for all stream resources. +Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.). +If this fails, it will throw a `RuntimeException`: + +```php +// throws RuntimeException on Windows +$stream = new DuplexResourceStream(STDOUT); +``` + +Once the constructor is called with a valid stream resource, this class will +take care of the underlying stream resource. +You SHOULD only use its public API and SHOULD NOT interfere with the underlying +stream resource manually. + +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + +This class takes an optional `int|null $readChunkSize` parameter that controls +the maximum buffer size in bytes to read at once from the stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. +This can be a positive number which means that up to X bytes will be read +at once from the underlying stream resource. Note that the actual number +of bytes read may be lower if the stream resource has less than X bytes +currently available. +This can be `-1` which means "read everything available" from the +underlying stream resource. +This should read until the stream resource is not readable anymore +(i.e. underlying buffer drained), note that this does not neccessarily +mean it reached EOF. + +```php +$conn = stream_socket_client('tcp://google.com:80'); +$stream = new DuplexResourceStream($conn, null, 8192); +``` + +Any `write()` calls to this class will not be performed instantly, but will +be performed asynchronously, once the EventLoop reports the stream resource is +ready to accept data. +For this, it uses an in-memory buffer string to collect all outstanding writes. +This buffer has a soft-limit applied which defines how much data it is willing +to accept before the caller SHOULD stop sending further data. + +This class takes another optional `WritableStreamInterface|null $buffer` parameter +that controls this write behavior of this stream. +You can use a `null` value here in order to apply its default value. +This value SHOULD NOT be changed unless you know what you're doing. + +If you want to change the write buffer soft limit, you can pass an instance of +[`WritableResourceStream`](#writableresourcestream) like this: + +```php +$conn = stream_socket_client('tcp://google.com:80'); +$buffer = new WritableResourceStream($conn, null, 8192); +$stream = new DuplexResourceStream($conn, null, null, $buffer); +``` + +See also [`WritableResourceStream`](#writableresourcestream) for more details. + +> Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a + `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). + +### ThroughStream + +The `ThroughStream` implements the +[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data +you write to it through to its readable end. + +```php +$through = new ThroughStream(); +$through->on('data', $this->expectCallableOnceWith('hello')); + +$through->write('hello'); +``` + +Similarly, the [`end()` method](#end) will end the stream and emit an +[`end` event](#end-event) and then [`close()`](#close-1) the stream. +The [`close()` method](#close-1) will close the stream and emit a +[`close` event](#close-event). +Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this: + +```php +$through = new ThroughStream(); +$source->pipe($through)->pipe($dest); +``` + +Optionally, its constructor accepts any callable function which will then be +used to *filter* any data written to it. This function receives a single data +argument as passed to the writable side and must return the data as it will be +passed to its readable end: + +```php +$through = new ThroughStream('strtoupper'); +$source->pipe($through)->pipe($dest); +``` + +Note that this class makes no assumptions about any data types. This can be +used to convert data, for example for transforming any structured data into +a newline-delimited JSON (NDJSON) stream like this: + +```php +$through = new ThroughStream(function ($data) { + return json_encode($data) . PHP_EOL; +}); +$through->on('data', $this->expectCallableOnceWith("[2, true]\n")); + +$through->write(array(2, true)); +``` + +The callback function is allowed to throw an `Exception`. In this case, +the stream will emit an `error` event and then [`close()`](#close-1) the stream. + +```php +$through = new ThroughStream(function ($data) { + if (!is_string($data)) { + throw new \UnexpectedValueException('Only strings allowed'); + } + return $data; +}); +$through->on('error', $this->expectCallableOnce())); +$through->on('close', $this->expectCallableOnce())); +$through->on('data', $this->expectCallableNever())); + +$through->write(2); +``` + +### CompositeStream + +The `CompositeStream` implements the +[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a +single duplex stream from two individual streams implementing +[`ReadableStreamInterface`](#readablestreaminterface) and +[`WritableStreamInterface`](#writablestreaminterface) respectively. + +This is useful for some APIs which may require a single +[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often +more convenient to work with a single stream instance like this: + +```php +$stdin = new ReadableResourceStream(STDIN); +$stdout = new WritableResourceStream(STDOUT); + +$stdio = new CompositeStream($stdin, $stdout); + +$stdio->on('data', function ($chunk) use ($stdio) { + $stdio->write('You said: ' . $chunk); +}); +``` + +This is a well-behaving stream which forwards all stream events from the +underlying streams and forwards all streams calls to the underlying streams. + +If you `write()` to the duplex stream, it will simply `write()` to the +writable side and return its status. + +If you `end()` the duplex stream, it will `end()` the writable side and will +`pause()` the readable side. + +If you `close()` the duplex stream, both input streams will be closed. +If either of the two input streams emits a `close` event, the duplex stream +will also close. +If either of the two input streams is already closed while constructing the +duplex stream, it will `close()` the other side and return a closed stream. + +## Usage + +The following example can be used to pipe the contents of a source file into +a destination file without having to ever read the whole file into memory: + +```php +$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r')); +$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w')); + +$source->pipe($dest); +``` + +> Note that this example uses `fopen()` for illustration purposes only. + This should not be used in a truly async program because the filesystem is + inherently blocking and each call could potentially take several seconds. + See also [creating streams](#creating-streams) for more sophisticated + examples. + +## Install + +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This project follows [SemVer](https://semver.org/). +This will install the latest supported version: + +```bash +composer require react/stream:^1.4 +``` + +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. + +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. +It's *highly recommended to use PHP 7+* for this project due to its vast +performance improvements. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +composer install +``` + +To run the test suite, go to the project root and run: + +```bash +vendor/bin/phpunit +``` + +The test suite also contains a number of functional integration tests that rely +on a stable internet connection. +If you do not want to run these, they can simply be skipped like this: + +```bash +vendor/bin/phpunit --exclude-group internet +``` + +## License + +MIT, see [LICENSE file](LICENSE). + +## More + +* See [creating streams](#creating-streams) for more information on how streams + are created in real-world applications. +* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the + [dependents on Packagist](https://packagist.org/packages/react/stream/dependents) + for a list of packages that use streams in real-world applications. diff --git a/vendor/react/stream/composer.json b/vendor/react/stream/composer.json new file mode 100644 index 0000000000..09d8b71e00 --- /dev/null +++ b/vendor/react/stream/composer.json @@ -0,0 +1,47 @@ +{ + "name": "react/stream", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"], + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], + "require": { + "php": ">=5.3.8", + "react/event-loop": "^1.2", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "clue/stream-filter": "~1.2" + }, + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\Stream\\": "tests/" + } + } +} diff --git a/vendor/react/stream/src/CompositeStream.php b/vendor/react/stream/src/CompositeStream.php new file mode 100644 index 0000000000..dde091de00 --- /dev/null +++ b/vendor/react/stream/src/CompositeStream.php @@ -0,0 +1,83 @@ +readable = $readable; + $this->writable = $writable; + + if (!$readable->isReadable() || !$writable->isWritable()) { + $this->close(); + return; + } + + Util::forwardEvents($this->readable, $this, array('data', 'end', 'error')); + Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe')); + + $this->readable->on('close', array($this, 'close')); + $this->writable->on('close', array($this, 'close')); + } + + public function isReadable() + { + return $this->readable->isReadable(); + } + + public function pause() + { + $this->readable->pause(); + } + + public function resume() + { + if (!$this->writable->isWritable()) { + return; + } + + $this->readable->resume(); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + public function isWritable() + { + return $this->writable->isWritable(); + } + + public function write($data) + { + return $this->writable->write($data); + } + + public function end($data = null) + { + $this->readable->pause(); + $this->writable->end($data); + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->closed = true; + $this->readable->close(); + $this->writable->close(); + + $this->emit('close'); + $this->removeAllListeners(); + } +} diff --git a/vendor/react/stream/src/DuplexResourceStream.php b/vendor/react/stream/src/DuplexResourceStream.php new file mode 100644 index 0000000000..d6de55c079 --- /dev/null +++ b/vendor/react/stream/src/DuplexResourceStream.php @@ -0,0 +1,240 @@ +isLegacyPipe($stream)) { + \stream_set_read_buffer($stream, 0); + } + + if ($buffer === null) { + $buffer = new WritableResourceStream($stream, $loop); + } + + $this->stream = $stream; + $this->loop = $loop ?: Loop::get(); + $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; + $this->buffer = $buffer; + + $that = $this; + + $this->buffer->on('error', function ($error) use ($that) { + $that->emit('error', array($error)); + }); + + $this->buffer->on('close', array($this, 'close')); + + $this->buffer->on('drain', function () use ($that) { + $that->emit('drain'); + }); + + $this->resume(); + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function pause() + { + if ($this->listening) { + $this->loop->removeReadStream($this->stream); + $this->listening = false; + } + } + + public function resume() + { + if (!$this->listening && $this->readable) { + $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->listening = true; + } + } + + public function write($data) + { + if (!$this->writable) { + return false; + } + + return $this->buffer->write($data); + } + + public function close() + { + if (!$this->writable && !$this->closing) { + return; + } + + $this->closing = false; + + $this->readable = false; + $this->writable = false; + + $this->emit('close'); + $this->pause(); + $this->buffer->close(); + $this->removeAllListeners(); + + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + } + + public function end($data = null) + { + if (!$this->writable) { + return; + } + + $this->closing = true; + + $this->readable = false; + $this->writable = false; + $this->pause(); + + $this->buffer->end($data); + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + /** @internal */ + public function handleData($stream) + { + $error = null; + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + $error = new \ErrorException( + $errstr, + 0, + $errno, + $errfile, + $errline + ); + }); + + $data = \stream_get_contents($stream, $this->bufferSize); + + \restore_error_handler(); + + if ($error !== null) { + $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); + $this->close(); + return; + } + + if ($data !== '') { + $this->emit('data', array($data)); + } elseif (\feof($this->stream)) { + // no data read => we reached the end and close the stream + $this->emit('end'); + $this->close(); + } + } + + /** + * Returns whether this is a pipe resource in a legacy environment + * + * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+ + * and PHP 5.5.12+ and newer. + * + * @param resource $resource + * @return bool + * @link https://github.com/reactphp/child-process/issues/40 + * + * @codeCoverageIgnore + */ + private function isLegacyPipe($resource) + { + if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) { + $meta = \stream_get_meta_data($resource); + + if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') { + return true; + } + } + return false; + } +} diff --git a/vendor/react/stream/src/DuplexStreamInterface.php b/vendor/react/stream/src/DuplexStreamInterface.php new file mode 100644 index 0000000000..631ce31e82 --- /dev/null +++ b/vendor/react/stream/src/DuplexStreamInterface.php @@ -0,0 +1,39 @@ + Note that higher-level implementations of this interface may choose to + * define additional events with dedicated semantics not defined as part of + * this low-level stream specification. Conformance with these event semantics + * is out of scope for this interface, so you may also have to refer to the + * documentation of such a higher-level implementation. + * + * @see ReadableStreamInterface + * @see WritableStreamInterface + */ +interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface +{ +} diff --git a/vendor/react/stream/src/ReadableResourceStream.php b/vendor/react/stream/src/ReadableResourceStream.php new file mode 100644 index 0000000000..823360a631 --- /dev/null +++ b/vendor/react/stream/src/ReadableResourceStream.php @@ -0,0 +1,188 @@ +isLegacyPipe($stream)) { + \stream_set_read_buffer($stream, 0); + } + + $this->stream = $stream; + $this->loop = $loop ?: Loop::get(); + $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize; + + $this->resume(); + } + + public function isReadable() + { + return !$this->closed; + } + + public function pause() + { + if ($this->listening) { + $this->loop->removeReadStream($this->stream); + $this->listening = false; + } + } + + public function resume() + { + if (!$this->listening && !$this->closed) { + $this->loop->addReadStream($this->stream, array($this, 'handleData')); + $this->listening = true; + } + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->closed = true; + + $this->emit('close'); + $this->pause(); + $this->removeAllListeners(); + + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + } + + /** @internal */ + public function handleData() + { + $error = null; + \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) { + $error = new \ErrorException( + $errstr, + 0, + $errno, + $errfile, + $errline + ); + }); + + $data = \stream_get_contents($this->stream, $this->bufferSize); + + \restore_error_handler(); + + if ($error !== null) { + $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error))); + $this->close(); + return; + } + + if ($data !== '') { + $this->emit('data', array($data)); + } elseif (\feof($this->stream)) { + // no data read => we reached the end and close the stream + $this->emit('end'); + $this->close(); + } + } + + /** + * Returns whether this is a pipe resource in a legacy environment + * + * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+ + * and PHP 5.5.12+ and newer. + * + * @param resource $resource + * @return bool + * @link https://github.com/reactphp/child-process/issues/40 + * + * @codeCoverageIgnore + */ + private function isLegacyPipe($resource) + { + if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) { + $meta = \stream_get_meta_data($resource); + + if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') { + return true; + } + } + return false; + } +} diff --git a/vendor/react/stream/src/ReadableStreamInterface.php b/vendor/react/stream/src/ReadableStreamInterface.php new file mode 100644 index 0000000000..fa3d59cdba --- /dev/null +++ b/vendor/react/stream/src/ReadableStreamInterface.php @@ -0,0 +1,362 @@ +on('data', function ($data) { + * echo $data; + * }); + * ``` + * + * This event MAY be emitted any number of times, which may be zero times if + * this stream does not send any data at all. + * It SHOULD not be emitted after an `end` or `close` event. + * + * The given `$data` argument may be of mixed type, but it's usually + * recommended it SHOULD be a `string` value or MAY use a type that allows + * representation as a `string` for maximum compatibility. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will emit the raw (binary) payload data that is received over the wire as + * chunks of `string` values. + * + * Due to the stream-based nature of this, the sender may send any number + * of chunks with varying sizes. There are no guarantees that these chunks + * will be received with the exact same framing the sender intended to send. + * In other words, many lower-level protocols (such as TCP/IP) transfer the + * data in chunks that may be anywhere between single-byte values to several + * dozens of kilobytes. You may want to apply a higher-level protocol to + * these low-level data chunks in order to achieve proper message framing. + * + * end event: + * The `end` event will be emitted once the source stream has successfully + * reached the end of the stream (EOF). + * + * ```php + * $stream->on('end', function () { + * echo 'END'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * a successful end was detected. + * It SHOULD NOT be emitted after a previous `end` or `close` event. + * It MUST NOT be emitted if the stream closes due to a non-successful + * end, such as after a previous `error` event. + * + * After the stream is ended, it MUST switch to non-readable mode, + * see also `isReadable()`. + * + * This event will only be emitted if the *end* was reached successfully, + * not if the stream was interrupted by an unrecoverable error or explicitly + * closed. Not all streams know this concept of a "successful end". + * Many use-cases involve detecting when the stream closes (terminates) + * instead, in this case you should use the `close` event. + * After the stream emits an `end` event, it SHOULD usually be followed by a + * `close` event. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will emit this event if either the remote side closes the connection or + * a file handle was successfully read until reaching its end (EOF). + * + * Note that this event should not be confused with the `end()` method. + * This event defines a successful end *reading* from a source stream, while + * the `end()` method defines *writing* a successful end to a destination + * stream. + * + * error event: + * The `error` event will be emitted once a fatal error occurs, usually while + * trying to read from this stream. + * The event receives a single `Exception` argument for the error instance. + * + * ```php + * $stream->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event SHOULD be emitted once the stream detects a fatal error, such + * as a fatal transmission error or after an unexpected `data` or premature + * `end` event. + * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event. + * It MUST NOT be emitted if this is not a fatal error condition, such as + * a temporary network issue that did not cause any data to be lost. + * + * After the stream errors, it MUST close the stream and SHOULD thus be + * followed by a `close` event and then switch to non-readable mode, see + * also `close()` and `isReadable()`. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * only deal with data transmission and do not make assumption about data + * boundaries (such as unexpected `data` or premature `end` events). + * In other words, many lower-level protocols (such as TCP/IP) may choose + * to only emit this for a fatal transmission error once and will then + * close (terminate) the stream in response. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements an `error` event. + * In other words, an error may occur while either reading or writing the + * stream which should result in the same error processing. + * + * close event: + * The `close` event will be emitted once the stream closes (terminates). + * + * ```php + * $stream->on('close', function () { + * echo 'CLOSED'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * the stream ever terminates. + * It SHOULD NOT be emitted after a previous `close` event. + * + * After the stream is closed, it MUST switch to non-readable mode, + * see also `isReadable()`. + * + * Unlike the `end` event, this event SHOULD be emitted whenever the stream + * closes, irrespective of whether this happens implicitly due to an + * unrecoverable error or explicitly when either side closes the stream. + * If you only want to detect a *successful* end, you should use the `end` + * event instead. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will likely choose to emit this event after reading a *successful* `end` + * event or after a fatal transmission `error` event. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements a `close` event. + * In other words, after receiving this event, the stream MUST switch into + * non-writable AND non-readable mode, see also `isWritable()`. + * Note that this event should not be confused with the `end` event. + * + * The event callback functions MUST be a valid `callable` that obeys strict + * parameter definitions and MUST accept event parameters exactly as documented. + * The event callback functions MUST NOT throw an `Exception`. + * The return value of the event callback functions will be ignored and has no + * effect, so for performance reasons you're recommended to not return any + * excessive data structures. + * + * Every implementation of this interface MUST follow these event semantics in + * order to be considered a well-behaving stream. + * + * > Note that higher-level implementations of this interface may choose to + * define additional events with dedicated semantics not defined as part of + * this low-level stream specification. Conformance with these event semantics + * is out of scope for this interface, so you may also have to refer to the + * documentation of such a higher-level implementation. + * + * @see EventEmitterInterface + */ +interface ReadableStreamInterface extends EventEmitterInterface +{ + /** + * Checks whether this stream is in a readable state (not closed already). + * + * This method can be used to check if the stream still accepts incoming + * data events or if it is ended or closed already. + * Once the stream is non-readable, no further `data` or `end` events SHOULD + * be emitted. + * + * ```php + * assert($stream->isReadable() === false); + * + * $stream->on('data', assertNeverCalled()); + * $stream->on('end', assertNeverCalled()); + * ``` + * + * A successfully opened stream always MUST start in readable mode. + * + * Once the stream ends or closes, it MUST switch to non-readable mode. + * This can happen any time, explicitly through `close()` or + * implicitly due to a remote close or an unrecoverable transmission error. + * Once a stream has switched to non-readable mode, it MUST NOT transition + * back to readable mode. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements an `isWritable()` + * method. Unless this is a half-open duplex stream, they SHOULD usually + * have the same return value. + * + * @return bool + */ + public function isReadable(); + + /** + * Pauses reading incoming data events. + * + * Removes the data source file descriptor from the event loop. This + * allows you to throttle incoming data. + * + * Unless otherwise noted, a successfully opened stream SHOULD NOT start + * in paused state. + * + * Once the stream is paused, no futher `data` or `end` events SHOULD + * be emitted. + * + * ```php + * $stream->pause(); + * + * $stream->on('data', assertShouldNeverCalled()); + * $stream->on('end', assertShouldNeverCalled()); + * ``` + * + * This method is advisory-only, though generally not recommended, the + * stream MAY continue emitting `data` events. + * + * You can continue processing events by calling `resume()` again. + * + * Note that both methods can be called any number of times, in particular + * calling `pause()` more than once SHOULD NOT have any effect. + * + * @see self::resume() + * @return void + */ + public function pause(); + + /** + * Resumes reading incoming data events. + * + * Re-attach the data source after a previous `pause()`. + * + * ```php + * $stream->pause(); + * + * Loop::addTimer(1.0, function () use ($stream) { + * $stream->resume(); + * }); + * ``` + * + * Note that both methods can be called any number of times, in particular + * calling `resume()` without a prior `pause()` SHOULD NOT have any effect. + * + * @see self::pause() + * @return void + */ + public function resume(); + + /** + * Pipes all the data from this readable source into the given writable destination. + * + * Automatically sends all incoming data to the destination. + * Automatically throttles the source based on what the destination can handle. + * + * ```php + * $source->pipe($dest); + * ``` + * + * Similarly, you can also pipe an instance implementing `DuplexStreamInterface` + * into itself in order to write back all the data that is received. + * This may be a useful feature for a TCP/IP echo service: + * + * ```php + * $connection->pipe($connection); + * ``` + * + * This method returns the destination stream as-is, which can be used to + * set up chains of piped streams: + * + * ```php + * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest); + * ``` + * + * By default, this will call `end()` on the destination stream once the + * source stream emits an `end` event. This can be disabled like this: + * + * ```php + * $source->pipe($dest, array('end' => false)); + * ``` + * + * Note that this only applies to the `end` event. + * If an `error` or explicit `close` event happens on the source stream, + * you'll have to manually close the destination stream: + * + * ```php + * $source->pipe($dest); + * $source->on('close', function () use ($dest) { + * $dest->end('BYE!'); + * }); + * ``` + * + * If the source stream is not readable (closed state), then this is a NO-OP. + * + * ```php + * $source->close(); + * $source->pipe($dest); // NO-OP + * ``` + * + * If the destinantion stream is not writable (closed state), then this will simply + * throttle (pause) the source stream: + * + * ```php + * $dest->close(); + * $source->pipe($dest); // calls $source->pause() + * ``` + * + * Similarly, if the destination stream is closed while the pipe is still + * active, it will also throttle (pause) the source stream: + * + * ```php + * $source->pipe($dest); + * $dest->close(); // calls $source->pause() + * ``` + * + * Once the pipe is set up successfully, the destination stream MUST emit + * a `pipe` event with this source stream an event argument. + * + * @param WritableStreamInterface $dest + * @param array $options + * @return WritableStreamInterface $dest stream as-is + */ + public function pipe(WritableStreamInterface $dest, array $options = array()); + + /** + * Closes the stream (forcefully). + * + * This method can be used to (forcefully) close the stream. + * + * ```php + * $stream->close(); + * ``` + * + * Once the stream is closed, it SHOULD emit a `close` event. + * Note that this event SHOULD NOT be emitted more than once, in particular + * if this method is called multiple times. + * + * After calling this method, the stream MUST switch into a non-readable + * mode, see also `isReadable()`. + * This means that no further `data` or `end` events SHOULD be emitted. + * + * ```php + * $stream->close(); + * assert($stream->isReadable() === false); + * + * $stream->on('data', assertNeverCalled()); + * $stream->on('end', assertNeverCalled()); + * ``` + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements a `close()` method. + * In other words, after calling this method, the stream MUST switch into + * non-writable AND non-readable mode, see also `isWritable()`. + * Note that this method should not be confused with the `end()` method. + * + * @return void + * @see WritableStreamInterface::close() + */ + public function close(); +} diff --git a/vendor/react/stream/src/ThroughStream.php b/vendor/react/stream/src/ThroughStream.php new file mode 100644 index 0000000000..3b4fbb7800 --- /dev/null +++ b/vendor/react/stream/src/ThroughStream.php @@ -0,0 +1,195 @@ +on('data', $this->expectCallableOnceWith('hello')); + * + * $through->write('hello'); + * ``` + * + * Similarly, the [`end()` method](#end) will end the stream and emit an + * [`end` event](#end-event) and then [`close()`](#close-1) the stream. + * The [`close()` method](#close-1) will close the stream and emit a + * [`close` event](#close-event). + * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this: + * + * ```php + * $through = new ThroughStream(); + * $source->pipe($through)->pipe($dest); + * ``` + * + * Optionally, its constructor accepts any callable function which will then be + * used to *filter* any data written to it. This function receives a single data + * argument as passed to the writable side and must return the data as it will be + * passed to its readable end: + * + * ```php + * $through = new ThroughStream('strtoupper'); + * $source->pipe($through)->pipe($dest); + * ``` + * + * Note that this class makes no assumptions about any data types. This can be + * used to convert data, for example for transforming any structured data into + * a newline-delimited JSON (NDJSON) stream like this: + * + * ```php + * $through = new ThroughStream(function ($data) { + * return json_encode($data) . PHP_EOL; + * }); + * $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); + * + * $through->write(array(2, true)); + * ``` + * + * The callback function is allowed to throw an `Exception`. In this case, + * the stream will emit an `error` event and then [`close()`](#close-1) the stream. + * + * ```php + * $through = new ThroughStream(function ($data) { + * if (!is_string($data)) { + * throw new \UnexpectedValueException('Only strings allowed'); + * } + * return $data; + * }); + * $through->on('error', $this->expectCallableOnce())); + * $through->on('close', $this->expectCallableOnce())); + * $through->on('data', $this->expectCallableNever())); + * + * $through->write(2); + * ``` + * + * @see WritableStreamInterface::write() + * @see WritableStreamInterface::end() + * @see DuplexStreamInterface::close() + * @see WritableStreamInterface::pipe() + */ +final class ThroughStream extends EventEmitter implements DuplexStreamInterface +{ + private $readable = true; + private $writable = true; + private $closed = false; + private $paused = false; + private $drain = false; + private $callback; + + public function __construct($callback = null) + { + if ($callback !== null && !\is_callable($callback)) { + throw new InvalidArgumentException('Invalid transformation callback given'); + } + + $this->callback = $callback; + } + + public function pause() + { + // only allow pause if still readable, false otherwise + $this->paused = $this->readable; + } + + public function resume() + { + $this->paused = false; + + // emit drain event if previous write was paused (throttled) + if ($this->drain) { + $this->drain = false; + $this->emit('drain'); + } + } + + public function pipe(WritableStreamInterface $dest, array $options = array()) + { + return Util::pipe($this, $dest, $options); + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function write($data) + { + if (!$this->writable) { + return false; + } + + if ($this->callback !== null) { + try { + $data = \call_user_func($this->callback, $data); + } catch (\Exception $e) { + $this->emit('error', array($e)); + $this->close(); + + return false; + } + } + + $this->emit('data', array($data)); + + // emit drain event on next resume if currently paused (throttled) + if ($this->paused) { + $this->drain = true; + } + + // continue writing if still writable and not paused (throttled), false otherwise + return $this->writable && !$this->paused; + } + + public function end($data = null) + { + if (!$this->writable) { + return; + } + + if (null !== $data) { + $this->write($data); + + // return if write() already caused the stream to close + if (!$this->writable) { + return; + } + } + + $this->readable = false; + $this->writable = false; + $this->paused = false; + $this->drain = false; + + $this->emit('end'); + $this->close(); + } + + public function close() + { + if ($this->closed) { + return; + } + + $this->readable = false; + $this->writable = false; + $this->paused = false; + $this->drain = false; + + $this->closed = true; + $this->callback = null; + + $this->emit('close'); + $this->removeAllListeners(); + } +} diff --git a/vendor/react/stream/src/Util.php b/vendor/react/stream/src/Util.php new file mode 100644 index 0000000000..056b037749 --- /dev/null +++ b/vendor/react/stream/src/Util.php @@ -0,0 +1,75 @@ + NO-OP + if (!$source->isReadable()) { + return $dest; + } + + // destination not writable => just pause() source + if (!$dest->isWritable()) { + $source->pause(); + + return $dest; + } + + $dest->emit('pipe', array($source)); + + // forward all source data events as $dest->write() + $source->on('data', $dataer = function ($data) use ($source, $dest) { + $feedMore = $dest->write($data); + + if (false === $feedMore) { + $source->pause(); + } + }); + $dest->on('close', function () use ($source, $dataer) { + $source->removeListener('data', $dataer); + $source->pause(); + }); + + // forward destination drain as $source->resume() + $dest->on('drain', $drainer = function () use ($source) { + $source->resume(); + }); + $source->on('close', function () use ($dest, $drainer) { + $dest->removeListener('drain', $drainer); + }); + + // forward end event from source as $dest->end() + $end = isset($options['end']) ? $options['end'] : true; + if ($end) { + $source->on('end', $ender = function () use ($dest) { + $dest->end(); + }); + $dest->on('close', function () use ($source, $ender) { + $source->removeListener('end', $ender); + }); + } + + return $dest; + } + + public static function forwardEvents($source, $target, array $events) + { + foreach ($events as $event) { + $source->on($event, function () use ($event, $target) { + $target->emit($event, \func_get_args()); + }); + } + } +} diff --git a/vendor/react/stream/src/WritableResourceStream.php b/vendor/react/stream/src/WritableResourceStream.php new file mode 100644 index 0000000000..e3a7e74da1 --- /dev/null +++ b/vendor/react/stream/src/WritableResourceStream.php @@ -0,0 +1,178 @@ +stream = $stream; + $this->loop = $loop ?: Loop::get(); + $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit; + $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize; + } + + public function isWritable() + { + return $this->writable; + } + + public function write($data) + { + if (!$this->writable) { + return false; + } + + $this->data .= $data; + + if (!$this->listening && $this->data !== '') { + $this->listening = true; + + $this->loop->addWriteStream($this->stream, array($this, 'handleWrite')); + } + + return !isset($this->data[$this->softLimit - 1]); + } + + public function end($data = null) + { + if (null !== $data) { + $this->write($data); + } + + $this->writable = false; + + // close immediately if buffer is already empty + // otherwise wait for buffer to flush first + if ($this->data === '') { + $this->close(); + } + } + + public function close() + { + if ($this->closed) { + return; + } + + if ($this->listening) { + $this->listening = false; + $this->loop->removeWriteStream($this->stream); + } + + $this->closed = true; + $this->writable = false; + $this->data = ''; + + $this->emit('close'); + $this->removeAllListeners(); + + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + } + + /** @internal */ + public function handleWrite() + { + $error = null; + \set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + + if ($this->writeChunkSize === -1) { + $sent = \fwrite($this->stream, $this->data); + } else { + $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize); + } + + \restore_error_handler(); + + // Only report errors if *nothing* could be sent and an error has been raised. + // Ignore non-fatal warnings if *some* data could be sent. + // Any hard (permanent) error will fail to send any data at all. + // Sending excessive amounts of data will only flush *some* data and then + // report a temporary error (EAGAIN) which we do not raise here in order + // to keep the stream open for further tries to write. + // Should this turn out to be a permanent error later, it will eventually + // send *nothing* and we can detect this. + if (($sent === 0 || $sent === false) && $error !== null) { + $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error))); + $this->close(); + + return; + } + + $exceeded = isset($this->data[$this->softLimit - 1]); + $this->data = (string) \substr($this->data, $sent); + + // buffer has been above limit and is now below limit + if ($exceeded && !isset($this->data[$this->softLimit - 1])) { + $this->emit('drain'); + } + + // buffer is now completely empty => stop trying to write + if ($this->data === '') { + // stop waiting for resource to be writable + if ($this->listening) { + $this->loop->removeWriteStream($this->stream); + $this->listening = false; + } + + // buffer is end()ing and now completely empty => close buffer + if (!$this->writable) { + $this->close(); + } + } + } +} diff --git a/vendor/react/stream/src/WritableStreamInterface.php b/vendor/react/stream/src/WritableStreamInterface.php new file mode 100644 index 0000000000..e2625928cc --- /dev/null +++ b/vendor/react/stream/src/WritableStreamInterface.php @@ -0,0 +1,347 @@ +on('drain', function () use ($stream) { + * echo 'Stream is now ready to accept more data'; + * }); + * ``` + * + * This event SHOULD be emitted once every time the buffer became full + * previously and is now ready to accept more data. + * In other words, this event MAY be emitted any number of times, which may + * be zero times if the buffer never became full in the first place. + * This event SHOULD NOT be emitted if the buffer has not become full + * previously. + * + * This event is mostly used internally, see also `write()` for more details. + * + * pipe event: + * The `pipe` event will be emitted whenever a readable stream is `pipe()`d + * into this stream. + * The event receives a single `ReadableStreamInterface` argument for the + * source stream. + * + * ```php + * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + * echo 'Now receiving piped data'; + * + * // explicitly close target if source emits an error + * $source->on('error', function () use ($stream) { + * $stream->close(); + * }); + * }); + * + * $source->pipe($stream); + * ``` + * + * This event MUST be emitted once for each readable stream that is + * successfully piped into this destination stream. + * In other words, this event MAY be emitted any number of times, which may + * be zero times if no stream is ever piped into this stream. + * This event MUST NOT be emitted if either the source is not readable + * (closed already) or this destination is not writable (closed already). + * + * This event is mostly used internally, see also `pipe()` for more details. + * + * error event: + * The `error` event will be emitted once a fatal error occurs, usually while + * trying to write to this stream. + * The event receives a single `Exception` argument for the error instance. + * + * ```php + * $stream->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event SHOULD be emitted once the stream detects a fatal error, such + * as a fatal transmission error. + * It SHOULD NOT be emitted after a previous `error` or `close` event. + * It MUST NOT be emitted if this is not a fatal error condition, such as + * a temporary network issue that did not cause any data to be lost. + * + * After the stream errors, it MUST close the stream and SHOULD thus be + * followed by a `close` event and then switch to non-writable mode, see + * also `close()` and `isWritable()`. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * only deal with data transmission and may choose + * to only emit this for a fatal transmission error once and will then + * close (terminate) the stream in response. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements an `error` event. + * In other words, an error may occur while either reading or writing the + * stream which should result in the same error processing. + * + * close event: + * The `close` event will be emitted once the stream closes (terminates). + * + * ```php + * $stream->on('close', function () { + * echo 'CLOSED'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * the stream ever terminates. + * It SHOULD NOT be emitted after a previous `close` event. + * + * After the stream is closed, it MUST switch to non-writable mode, + * see also `isWritable()`. + * + * This event SHOULD be emitted whenever the stream closes, irrespective of + * whether this happens implicitly due to an unrecoverable error or + * explicitly when either side closes the stream. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will likely choose to emit this event after flushing the buffer from + * the `end()` method, after receiving a *successful* `end` event or after + * a fatal transmission `error` event. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements a `close` event. + * In other words, after receiving this event, the stream MUST switch into + * non-writable AND non-readable mode, see also `isReadable()`. + * Note that this event should not be confused with the `end` event. + * + * The event callback functions MUST be a valid `callable` that obeys strict + * parameter definitions and MUST accept event parameters exactly as documented. + * The event callback functions MUST NOT throw an `Exception`. + * The return value of the event callback functions will be ignored and has no + * effect, so for performance reasons you're recommended to not return any + * excessive data structures. + * + * Every implementation of this interface MUST follow these event semantics in + * order to be considered a well-behaving stream. + * + * > Note that higher-level implementations of this interface may choose to + * define additional events with dedicated semantics not defined as part of + * this low-level stream specification. Conformance with these event semantics + * is out of scope for this interface, so you may also have to refer to the + * documentation of such a higher-level implementation. + * + * @see EventEmitterInterface + * @see DuplexStreamInterface + */ +interface WritableStreamInterface extends EventEmitterInterface +{ + /** + * Checks whether this stream is in a writable state (not closed already). + * + * This method can be used to check if the stream still accepts writing + * any data or if it is ended or closed already. + * Writing any data to a non-writable stream is a NO-OP: + * + * ```php + * assert($stream->isWritable() === false); + * + * $stream->write('end'); // NO-OP + * $stream->end('end'); // NO-OP + * ``` + * + * A successfully opened stream always MUST start in writable mode. + * + * Once the stream ends or closes, it MUST switch to non-writable mode. + * This can happen any time, explicitly through `end()` or `close()` or + * implicitly due to a remote close or an unrecoverable transmission error. + * Once a stream has switched to non-writable mode, it MUST NOT transition + * back to writable mode. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements an `isReadable()` + * method. Unless this is a half-open duplex stream, they SHOULD usually + * have the same return value. + * + * @return bool + */ + public function isWritable(); + + /** + * Write some data into the stream. + * + * A successful write MUST be confirmed with a boolean `true`, which means + * that either the data was written (flushed) immediately or is buffered and + * scheduled for a future write. Note that this interface gives you no + * control over explicitly flushing the buffered data, as finding the + * appropriate time for this is beyond the scope of this interface and left + * up to the implementation of this interface. + * + * Many common streams (such as a TCP/IP connection or file-based stream) + * may choose to buffer all given data and schedule a future flush by using + * an underlying EventLoop to check when the resource is actually writable. + * + * If a stream cannot handle writing (or flushing) the data, it SHOULD emit + * an `error` event and MAY `close()` the stream if it can not recover from + * this error. + * + * If the internal buffer is full after adding `$data`, then `write()` + * SHOULD return `false`, indicating that the caller should stop sending + * data until the buffer drains. + * The stream SHOULD send a `drain` event once the buffer is ready to accept + * more data. + * + * Similarly, if the stream is not writable (already in a closed state) + * it MUST NOT process the given `$data` and SHOULD return `false`, + * indicating that the caller should stop sending data. + * + * The given `$data` argument MAY be of mixed type, but it's usually + * recommended it SHOULD be a `string` value or MAY use a type that allows + * representation as a `string` for maximum compatibility. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will only accept the raw (binary) payload data that is transferred over + * the wire as chunks of `string` values. + * + * Due to the stream-based nature of this, the sender may send any number + * of chunks with varying sizes. There are no guarantees that these chunks + * will be received with the exact same framing the sender intended to send. + * In other words, many lower-level protocols (such as TCP/IP) transfer the + * data in chunks that may be anywhere between single-byte values to several + * dozens of kilobytes. You may want to apply a higher-level protocol to + * these low-level data chunks in order to achieve proper message framing. + * + * @param mixed|string $data + * @return bool + */ + public function write($data); + + /** + * Successfully ends the stream (after optionally sending some final data). + * + * This method can be used to successfully end the stream, i.e. close + * the stream after sending out all data that is currently buffered. + * + * ```php + * $stream->write('hello'); + * $stream->write('world'); + * $stream->end(); + * ``` + * + * If there's no data currently buffered and nothing to be flushed, then + * this method MAY `close()` the stream immediately. + * + * If there's still data in the buffer that needs to be flushed first, then + * this method SHOULD try to write out this data and only then `close()` + * the stream. + * Once the stream is closed, it SHOULD emit a `close` event. + * + * Note that this interface gives you no control over explicitly flushing + * the buffered data, as finding the appropriate time for this is beyond the + * scope of this interface and left up to the implementation of this + * interface. + * + * Many common streams (such as a TCP/IP connection or file-based stream) + * may choose to buffer all given data and schedule a future flush by using + * an underlying EventLoop to check when the resource is actually writable. + * + * You can optionally pass some final data that is written to the stream + * before ending the stream. If a non-`null` value is given as `$data`, then + * this method will behave just like calling `write($data)` before ending + * with no data. + * + * ```php + * // shorter version + * $stream->end('bye'); + * + * // same as longer version + * $stream->write('bye'); + * $stream->end(); + * ``` + * + * After calling this method, the stream MUST switch into a non-writable + * mode, see also `isWritable()`. + * This means that no further writes are possible, so any additional + * `write()` or `end()` calls have no effect. + * + * ```php + * $stream->end(); + * assert($stream->isWritable() === false); + * + * $stream->write('nope'); // NO-OP + * $stream->end(); // NO-OP + * ``` + * + * If this stream is a `DuplexStreamInterface`, calling this method SHOULD + * also end its readable side, unless the stream supports half-open mode. + * In other words, after calling this method, these streams SHOULD switch + * into non-writable AND non-readable mode, see also `isReadable()`. + * This implies that in this case, the stream SHOULD NOT emit any `data` + * or `end` events anymore. + * Streams MAY choose to use the `pause()` method logic for this, but + * special care may have to be taken to ensure a following call to the + * `resume()` method SHOULD NOT continue emitting readable events. + * + * Note that this method should not be confused with the `close()` method. + * + * @param mixed|string|null $data + * @return void + */ + public function end($data = null); + + /** + * Closes the stream (forcefully). + * + * This method can be used to forcefully close the stream, i.e. close + * the stream without waiting for any buffered data to be flushed. + * If there's still data in the buffer, this data SHOULD be discarded. + * + * ```php + * $stream->close(); + * ``` + * + * Once the stream is closed, it SHOULD emit a `close` event. + * Note that this event SHOULD NOT be emitted more than once, in particular + * if this method is called multiple times. + * + * After calling this method, the stream MUST switch into a non-writable + * mode, see also `isWritable()`. + * This means that no further writes are possible, so any additional + * `write()` or `end()` calls have no effect. + * + * ```php + * $stream->close(); + * assert($stream->isWritable() === false); + * + * $stream->write('nope'); // NO-OP + * $stream->end(); // NO-OP + * ``` + * + * Note that this method should not be confused with the `end()` method. + * Unlike the `end()` method, this method does not take care of any existing + * buffers and simply discards any buffer contents. + * Likewise, this method may also be called after calling `end()` on a + * stream in order to stop waiting for the stream to flush its final data. + * + * ```php + * $stream->end(); + * Loop::addTimer(1.0, function () use ($stream) { + * $stream->close(); + * }); + * ``` + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements a `close()` method. + * In other words, after calling this method, the stream MUST switch into + * non-writable AND non-readable mode, see also `isReadable()`. + * + * @return void + * @see ReadableStreamInterface::close() + */ + public function close(); +} diff --git a/vendor/sebastian/diff/ChangeLog.md b/vendor/sebastian/diff/ChangeLog.md new file mode 100644 index 0000000000..e62d8f9577 --- /dev/null +++ b/vendor/sebastian/diff/ChangeLog.md @@ -0,0 +1,103 @@ +# ChangeLog + +All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [4.0.6] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [4.0.5] - 2023-05-07 + +### Changed + +* [#118](https://github.com/sebastianbergmann/diff/pull/118): Improve performance of `MemoryEfficientLongestCommonSubsequenceCalculator` +* [#119](https://github.com/sebastianbergmann/diff/pull/119): Improve performance of `TimeEfficientLongestCommonSubsequenceCalculator` + +## [4.0.4] - 2020-10-26 + +### Fixed + +* `SebastianBergmann\Diff\Exception` now correctly extends `\Throwable` + +## [4.0.3] - 2020-09-28 + +### Changed + +* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` + +## [4.0.2] - 2020-06-30 + +### Added + +* This component is now supported on PHP 8 + +## [4.0.1] - 2020-05-08 + +### Fixed + +* [#99](https://github.com/sebastianbergmann/diff/pull/99): Regression in unified diff output of identical strings + +## [4.0.0] - 2020-02-07 + +### Removed + +* Removed support for PHP 7.1 and PHP 7.2 + +## [3.0.2] - 2019-02-04 + +### Changed + +* `Chunk::setLines()` now ensures that the `$lines` array only contains `Line` objects + +## [3.0.1] - 2018-06-10 + +### Fixed + +* Removed `"minimum-stability": "dev",` from `composer.json` + +## [3.0.0] - 2018-02-01 + +* The `StrictUnifiedDiffOutputBuilder` implementation of the `DiffOutputBuilderInterface` was added + +### Changed + +* The default `DiffOutputBuilderInterface` implementation now generates context lines (unchanged lines) + +### Removed + +* Removed support for PHP 7.0 + +### Fixed + +* [#70](https://github.com/sebastianbergmann/diff/issues/70): Diffing of arrays no longer works + +## [2.0.1] - 2017-08-03 + +### Fixed + +* [#66](https://github.com/sebastianbergmann/diff/pull/66): Restored backwards compatibility for PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 + +## [2.0.0] - 2017-07-11 [YANKED] + +### Added + +* [#64](https://github.com/sebastianbergmann/diff/pull/64): Show line numbers for chunks of a diff + +### Removed + +* This component is no longer supported on PHP 5.6 + +[4.0.6]: https://github.com/sebastianbergmann/diff/compare/4.0.5...4.0.6 +[4.0.5]: https://github.com/sebastianbergmann/diff/compare/4.0.4...4.0.5 +[4.0.4]: https://github.com/sebastianbergmann/diff/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/diff/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/diff/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/diff/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/diff/compare/3.0.2...4.0.0 +[3.0.2]: https://github.com/sebastianbergmann/diff/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/diff/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/diff/compare/2.0...3.0.0 +[2.0.1]: https://github.com/sebastianbergmann/diff/compare/c341c98ce083db77f896a0aa64f5ee7652915970...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/diff/compare/1.4...c341c98ce083db77f896a0aa64f5ee7652915970 diff --git a/vendor/php-cs-fixer/diff/LICENSE b/vendor/sebastian/diff/LICENSE similarity index 94% rename from vendor/php-cs-fixer/diff/LICENSE rename to vendor/sebastian/diff/LICENSE index 93cf008811..f22f31cf0c 100644 --- a/vendor/php-cs-fixer/diff/LICENSE +++ b/vendor/sebastian/diff/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2002-2017, Sebastian Bergmann . +sebastian/diff + +Copyright (c) 2002-2020, Sebastian Bergmann . All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/vendor/sebastian/diff/README.md b/vendor/sebastian/diff/README.md new file mode 100644 index 0000000000..734b852de7 --- /dev/null +++ b/vendor/sebastian/diff/README.md @@ -0,0 +1,202 @@ +# sebastian/diff + +[![CI Status](https://github.com/sebastianbergmann/diff/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/diff/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/diff/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/diff) + +Diff implementation for PHP, factored out of PHPUnit into a stand-alone component. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + +``` +composer require sebastian/diff +``` + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + +``` +composer require --dev sebastian/diff +``` + +### Usage + +#### Generating diff + +The `Differ` class can be used to generate a textual representation of the difference between two strings: + +```php +diff('foo', 'bar'); +``` + +The code above yields the output below: +```diff +--- Original ++++ New +@@ @@ +-foo ++bar +``` + +There are three output builders available in this package: + +#### UnifiedDiffOutputBuilder + +This is default builder, which generates the output close to udiff and is used by PHPUnit. + +```php +diff('foo', 'bar'); +``` + +#### StrictUnifiedDiffOutputBuilder + +Generates (strict) Unified diff's (unidiffs) with hunks, +similar to `diff -u` and compatible with `patch` and `git apply`. + +```php + true, // ranges of length one are rendered with the trailing `,1` + 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) + 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 + 'fromFile' => null, + 'fromFileDate' => null, + 'toFile' => null, + 'toFileDate' => null, +]); + +$differ = new Differ($builder); +print $differ->diff('foo', 'bar'); +``` + +#### DiffOnlyOutputBuilder + +Output only the lines that differ. + +```php +diff('foo', 'bar'); +``` + +#### DiffOutputBuilderInterface + +You can pass any output builder to the `Differ` class as longs as it implements the `DiffOutputBuilderInterface`. + +#### Parsing diff + +The `Parser` class can be used to parse a unified diff into an object graph: + +```php +use SebastianBergmann\Diff\Parser; +use SebastianBergmann\Git; + +$git = new Git('/usr/local/src/money'); + +$diff = $git->getDiff( + '948a1a07768d8edd10dcefa8315c1cbeffb31833', + 'c07a373d2399f3e686234c4f7f088d635eb9641b' +); + +$parser = new Parser; + +print_r($parser->parse($diff)); +``` + +The code above yields the output below: + + Array + ( + [0] => SebastianBergmann\Diff\Diff Object + ( + [from:SebastianBergmann\Diff\Diff:private] => a/tests/MoneyTest.php + [to:SebastianBergmann\Diff\Diff:private] => b/tests/MoneyTest.php + [chunks:SebastianBergmann\Diff\Diff:private] => Array + ( + [0] => SebastianBergmann\Diff\Chunk Object + ( + [start:SebastianBergmann\Diff\Chunk:private] => 87 + [startRange:SebastianBergmann\Diff\Chunk:private] => 7 + [end:SebastianBergmann\Diff\Chunk:private] => 87 + [endRange:SebastianBergmann\Diff\Chunk:private] => 7 + [lines:SebastianBergmann\Diff\Chunk:private] => Array + ( + [0] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::add + ) + + [1] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::newMoney + ) + + [2] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => */ + ) + + [3] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 2 + [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyWithSameCurrencyObjectCanBeAdded() + ) + + [4] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 1 + [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyObjectWithSameCurrencyCanBeAdded() + ) + + [5] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => { + ) + + [6] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => $a = new Money(1, new Currency('EUR')); + ) + + [7] => SebastianBergmann\Diff\Line Object + ( + [type:SebastianBergmann\Diff\Line:private] => 3 + [content:SebastianBergmann\Diff\Line:private] => $b = new Money(2, new Currency('EUR')); + ) + ) + ) + ) + ) + ) diff --git a/vendor/php-cs-fixer/diff/composer.json b/vendor/sebastian/diff/composer.json similarity index 50% rename from vendor/php-cs-fixer/diff/composer.json rename to vendor/sebastian/diff/composer.json index 910267f08e..cf92202ba3 100644 --- a/vendor/php-cs-fixer/diff/composer.json +++ b/vendor/sebastian/diff/composer.json @@ -1,8 +1,8 @@ { - "name": "php-cs-fixer/diff", - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "keywords": ["diff"], - "homepage": "https://github.com/PHP-CS-Fixer", + "name": "sebastian/diff", + "description": "Diff implementation", + "keywords": ["diff", "udiff", "unidiff", "unified diff"], + "homepage": "https://github.com/sebastianbergmann/diff", "license": "BSD-3-Clause", "authors": [ { @@ -14,25 +14,34 @@ "email": "mail@kore-nordmann.de" } ], - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" - }, + "prefer-stable": true, "config": { + "platform": { + "php": "7.3.0" + }, "optimize-autoloader": true, "sort-packages": true }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, "autoload": { "classmap": [ "src/" ] }, "autoload-dev": { - "psr-4": { - "PhpCsFixer\\Diff\\": "tests" + "classmap": [ + "tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" } } } diff --git a/vendor/php-cs-fixer/diff/src/Chunk.php b/vendor/sebastian/diff/src/Chunk.php similarity index 74% rename from vendor/php-cs-fixer/diff/src/Chunk.php rename to vendor/sebastian/diff/src/Chunk.php index 49c77575c7..16ae34f414 100644 --- a/vendor/php-cs-fixer/diff/src/Chunk.php +++ b/vendor/sebastian/diff/src/Chunk.php @@ -1,4 +1,4 @@ -start = $start; $this->startRange = $startRange; @@ -46,22 +45,22 @@ public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1 $this->lines = $lines; } - public function getStart() + public function getStart(): int { return $this->start; } - public function getStartRange() + public function getStartRange(): int { return $this->startRange; } - public function getEnd() + public function getEnd(): int { return $this->end; } - public function getEndRange() + public function getEndRange(): int { return $this->endRange; } @@ -69,7 +68,7 @@ public function getEndRange() /** * @return Line[] */ - public function getLines() + public function getLines(): array { return $this->lines; } @@ -77,7 +76,7 @@ public function getLines() /** * @param Line[] $lines */ - public function setLines(array $lines) + public function setLines(array $lines): void { foreach ($lines as $line) { if (!$line instanceof Line) { diff --git a/vendor/php-cs-fixer/diff/src/Diff.php b/vendor/sebastian/diff/src/Diff.php similarity index 72% rename from vendor/php-cs-fixer/diff/src/Diff.php rename to vendor/sebastian/diff/src/Diff.php index a458d2747d..17b2084f90 100644 --- a/vendor/php-cs-fixer/diff/src/Diff.php +++ b/vendor/sebastian/diff/src/Diff.php @@ -1,4 +1,4 @@ -from = $from; $this->to = $to; $this->chunks = $chunks; } - public function getFrom() + public function getFrom(): string { return $this->from; } - public function getTo() + public function getTo(): string { return $this->to; } @@ -52,7 +49,7 @@ public function getTo() /** * @return Chunk[] */ - public function getChunks() + public function getChunks(): array { return $this->chunks; } @@ -60,7 +57,7 @@ public function getChunks() /** * @param Chunk[] $chunks */ - public function setChunks(array $chunks) + public function setChunks(array $chunks): void { $this->chunks = $chunks; } diff --git a/vendor/php-cs-fixer/diff/src/Differ.php b/vendor/sebastian/diff/src/Differ.php similarity index 67% rename from vendor/php-cs-fixer/diff/src/Differ.php rename to vendor/sebastian/diff/src/Differ.php index d252e397eb..98c7a9b29a 100644 --- a/vendor/php-cs-fixer/diff/src/Differ.php +++ b/vendor/sebastian/diff/src/Differ.php @@ -1,4 +1,4 @@ -outputBuilder = $outputBuilder; } elseif (null === $outputBuilder) { $this->outputBuilder = new UnifiedDiffOutputBuilder; - } elseif (\is_string($outputBuilder)) { + } elseif (is_string($outputBuilder)) { // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 // @deprecated $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); } else { throw new InvalidArgumentException( - \sprintf( + sprintf( 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', - \is_object($outputBuilder) ? 'instance of "' . \get_class($outputBuilder) . '"' : \gettype($outputBuilder) . ' "' . $outputBuilder . '"' + is_object($outputBuilder) ? 'instance of "' . get_class($outputBuilder) . '"' : gettype($outputBuilder) . ' "' . $outputBuilder . '"' ) ); } @@ -58,13 +79,10 @@ public function __construct($outputBuilder = null) /** * Returns the diff between two arrays or strings as string. * - * @param array|string $from - * @param array|string $to - * @param null|LongestCommonSubsequenceCalculator $lcs - * - * @return string + * @param array|string $from + * @param array|string $to */ - public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) + public function diff($from, $to, ?LongestCommonSubsequenceCalculator $lcs = null): string { $diff = $this->diffToArray( $this->normalizeDiffInput($from), @@ -89,59 +107,57 @@ public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null) * @param array|string $from * @param array|string $to * @param LongestCommonSubsequenceCalculator $lcs - * - * @return array */ - public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null) + public function diffToArray($from, $to, ?LongestCommonSubsequenceCalculator $lcs = null): array { - if (\is_string($from)) { + if (is_string($from)) { $from = $this->splitStringByLines($from); - } elseif (!\is_array($from)) { + } elseif (!is_array($from)) { throw new InvalidArgumentException('"from" must be an array or string.'); } - if (\is_string($to)) { + if (is_string($to)) { $to = $this->splitStringByLines($to); - } elseif (!\is_array($to)) { + } elseif (!is_array($to)) { throw new InvalidArgumentException('"to" must be an array or string.'); } - list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to); + [$from, $to, $start, $end] = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } - $common = $lcs->calculate(\array_values($from), \array_values($to)); + $common = $lcs->calculate(array_values($from), array_values($to)); $diff = []; foreach ($start as $token) { $diff[] = [$token, self::OLD]; } - \reset($from); - \reset($to); + reset($from); + reset($to); foreach ($common as $token) { - while (($fromToken = \reset($from)) !== $token) { - $diff[] = [\array_shift($from), self::REMOVED]; + while (($fromToken = reset($from)) !== $token) { + $diff[] = [array_shift($from), self::REMOVED]; } - while (($toToken = \reset($to)) !== $token) { - $diff[] = [\array_shift($to), self::ADDED]; + while (($toToken = reset($to)) !== $token) { + $diff[] = [array_shift($to), self::ADDED]; } $diff[] = [$token, self::OLD]; - \array_shift($from); - \array_shift($to); + array_shift($from); + array_shift($to); } - while (($token = \array_shift($from)) !== null) { + while (($token = array_shift($from)) !== null) { $diff[] = [$token, self::REMOVED]; } - while (($token = \array_shift($to)) !== null) { + while (($token = array_shift($to)) !== null) { $diff[] = [$token, self::ADDED]; } @@ -150,7 +166,7 @@ public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs } if ($this->detectUnmatchedLineEndings($diff)) { - \array_unshift($diff, ["#Warnings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); + array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); } return $diff; @@ -159,13 +175,11 @@ public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs /** * Casts variable to string if it is not a string or array. * - * @param mixed $input - * * @return array|string */ private function normalizeDiffInput($input) { - if (!\is_array($input) && !\is_string($input)) { + if (!is_array($input) && !is_string($input)) { return (string) $input; } @@ -174,23 +188,13 @@ private function normalizeDiffInput($input) /** * Checks if input is string, if so it will split it line-by-line. - * - * @param string $input - * - * @return array */ - private function splitStringByLines($input) + private function splitStringByLines(string $input): array { - return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + return preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } - /** - * @param array $from - * @param array $to - * - * @return LongestCommonSubsequenceCalculator - */ - private function selectLcsImplementation(array $from, array $to) + private function selectLcsImplementation(array $from, array $to): LongestCommonSubsequenceCalculator { // We do not want to use the time-efficient implementation if its memory // footprint will probably exceed this value. Note that the footprint @@ -208,26 +212,19 @@ private function selectLcsImplementation(array $from, array $to) /** * Calculates the estimated memory footprint for the DP-based method. * - * @param array $from - * @param array $to - * * @return float|int */ private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; - return $itemSize * \min(\count($from), \count($to)) ** 2; + return $itemSize * min(count($from), count($to)) ** 2; } /** * Returns true if line ends don't match in a diff. - * - * @param array $diff - * - * @return bool */ - private function detectUnmatchedLineEndings(array $diff) + private function detectUnmatchedLineEndings(array $diff): bool { $newLineBreaks = ['' => true]; $oldLineBreaks = ['' => true]; @@ -265,13 +262,14 @@ private function detectUnmatchedLineEndings(array $diff) return false; } - private function getLinebreak($line) + private function getLinebreak($line): string { - if (!\is_string($line)) { + if (!is_string($line)) { return ''; } - $lc = \substr($line, -1); + $lc = substr($line, -1); + if ("\r" === $lc) { return "\r"; } @@ -280,22 +278,22 @@ private function getLinebreak($line) return ''; } - if ("\r\n" === \substr($line, -2)) { + if ("\r\n" === substr($line, -2)) { return "\r\n"; } return "\n"; } - private static function getArrayDiffParted(array &$from, array &$to) + private static function getArrayDiffParted(array &$from, array &$to): array { $start = []; $end = []; - \reset($to); + reset($to); foreach ($from as $k => $v) { - $toK = \key($to); + $toK = key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; @@ -306,19 +304,19 @@ private static function getArrayDiffParted(array &$from, array &$to) } } - \end($from); - \end($to); + end($from); + end($to); do { - $fromK = \key($from); - $toK = \key($to); + $fromK = key($from); + $toK = key($to); - if (null === $fromK || null === $toK || \current($from) !== \current($to)) { + if (null === $fromK || null === $toK || current($from) !== current($to)) { break; } - \prev($from); - \prev($to); + prev($from); + prev($to); $end = [$fromK => $from[$fromK]] + $end; unset($from[$fromK], $to[$toK]); diff --git a/vendor/php-cs-fixer/diff/src/Exception/ConfigurationException.php b/vendor/sebastian/diff/src/Exception/ConfigurationException.php similarity index 54% rename from vendor/php-cs-fixer/diff/src/Exception/ConfigurationException.php rename to vendor/sebastian/diff/src/Exception/ConfigurationException.php index 0cd0f88954..8847a2e586 100644 --- a/vendor/php-cs-fixer/diff/src/Exception/ConfigurationException.php +++ b/vendor/sebastian/diff/src/Exception/ConfigurationException.php @@ -1,4 +1,4 @@ -' : \gettype($value) . '#' . $value) + is_object($value) ? get_class($value) : (null === $value ? '' : gettype($value) . '#' . $value) ), $code, $previous diff --git a/vendor/php-cs-fixer/diff/src/Exception/Exception.php b/vendor/sebastian/diff/src/Exception/Exception.php similarity index 66% rename from vendor/php-cs-fixer/diff/src/Exception/Exception.php rename to vendor/sebastian/diff/src/Exception/Exception.php index ba4a6097cb..e20d320369 100644 --- a/vendor/php-cs-fixer/diff/src/Exception/Exception.php +++ b/vendor/sebastian/diff/src/Exception/Exception.php @@ -1,4 +1,4 @@ -type = $type; $this->content = $content; } - public function getContent() + public function getContent(): string { return $this->content; } - public function getType() + public function getType(): int { return $this->type; } diff --git a/vendor/php-cs-fixer/diff/src/LongestCommonSubsequenceCalculator.php b/vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php similarity index 67% rename from vendor/php-cs-fixer/diff/src/LongestCommonSubsequenceCalculator.php rename to vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php index d344851c8a..dea8fe1cb8 100644 --- a/vendor/php-cs-fixer/diff/src/LongestCommonSubsequenceCalculator.php +++ b/vendor/sebastian/diff/src/LongestCommonSubsequenceCalculator.php @@ -1,4 +1,4 @@ -length($fromStart, $to); - $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); + $llE = $this->length(array_reverse($fromEnd), array_reverse($to)); $jMax = 0; $max = 0; @@ -49,20 +56,20 @@ public function calculate(array $from, array $to) } } - $toStart = \array_slice($to, 0, $jMax); - $toEnd = \array_slice($to, $jMax); + $toStart = array_slice($to, 0, $jMax); + $toEnd = array_slice($to, $jMax); - return \array_merge( + return array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } - private function length(array $from, array $to) + private function length(array $from, array $to): array { - $current = \array_fill(0, \count($to) + 1, 0); - $cFrom = \count($from); - $cTo = \count($to); + $current = array_fill(0, count($to) + 1, 0); + $cFrom = count($from); + $cTo = count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; @@ -71,7 +78,12 @@ private function length(array $from, array $to) if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { - $current[$j + 1] = \max($current[$j], $prev[$j + 1]); + // don't use max() to avoid function call overhead + if ($current[$j] > $prev[$j + 1]) { + $current[$j + 1] = $current[$j]; + } else { + $current[$j + 1] = $prev[$j + 1]; + } } } } diff --git a/vendor/php-cs-fixer/diff/src/Output/AbstractChunkOutputBuilder.php b/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php similarity index 84% rename from vendor/php-cs-fixer/diff/src/Output/AbstractChunkOutputBuilder.php rename to vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php index 6fec704567..e55757c380 100644 --- a/vendor/php-cs-fixer/diff/src/Output/AbstractChunkOutputBuilder.php +++ b/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php @@ -1,4 +1,4 @@ -header = $header; } - public function getDiff(array $diff) + public function getDiff(array $diff): string { - $buffer = \fopen('php://memory', 'r+b'); + $buffer = fopen('php://memory', 'r+b'); if ('' !== $this->header) { - \fwrite($buffer, $this->header); - if ("\n" !== \substr($this->header, -1, 1)) { - \fwrite($buffer, "\n"); + fwrite($buffer, $this->header); + + if ("\n" !== substr($this->header, -1, 1)) { + fwrite($buffer, "\n"); } } foreach ($diff as $diffEntry) { if ($diffEntry[1] === Differ::ADDED) { - \fwrite($buffer, '+' . $diffEntry[0]); + fwrite($buffer, '+' . $diffEntry[0]); } elseif ($diffEntry[1] === Differ::REMOVED) { - \fwrite($buffer, '-' . $diffEntry[0]); + fwrite($buffer, '-' . $diffEntry[0]); } elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) { - \fwrite($buffer, ' ' . $diffEntry[0]); + fwrite($buffer, ' ' . $diffEntry[0]); continue; // Warnings should not be tested for line break, it will always be there } else { /* Not changed (old) 0 */ continue; // we didn't write the non changs line, so do not add a line break either } - $lc = \substr($diffEntry[0], -1); + $lc = substr($diffEntry[0], -1); + if ($lc !== "\n" && $lc !== "\r") { - \fwrite($buffer, "\n"); // \No newline at end of file + fwrite($buffer, "\n"); // \No newline at end of file } } - $diff = \stream_get_contents($buffer, -1, 0); - \fclose($buffer); + $diff = stream_get_contents($buffer, -1, 0); + fclose($buffer); return $diff; } diff --git a/vendor/php-cs-fixer/diff/src/Output/DiffOutputBuilderInterface.php b/vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php similarity index 76% rename from vendor/php-cs-fixer/diff/src/Output/DiffOutputBuilderInterface.php rename to vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php index d348ed1fbf..0e18f9f2e6 100644 --- a/vendor/php-cs-fixer/diff/src/Output/DiffOutputBuilderInterface.php +++ b/vendor/sebastian/diff/src/Output/DiffOutputBuilderInterface.php @@ -1,4 +1,4 @@ - true, // ranges of length one are rendered with the trailing `,1` + 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) + 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 + 'fromFile' => null, + 'fromFileDate' => null, + 'toFile' => null, + 'toFileDate' => null, + ]; + /** * @var bool */ @@ -45,45 +68,28 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface */ private $contextLines; - private static $default = [ - 'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1` - 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) - 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 - 'fromFile' => null, - 'fromFileDate' => null, - 'toFile' => null, - 'toFileDate' => null, - ]; - public function __construct(array $options = []) { - $options = \array_merge(self::$default, $options); + $options = array_merge(self::$default, $options); - if (!\is_bool($options['collapseRanges'])) { + if (!is_bool($options['collapseRanges'])) { throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); } - if (!\is_int($options['contextLines']) || $options['contextLines'] < 0) { + if (!is_int($options['contextLines']) || $options['contextLines'] < 0) { throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); } - if (!\is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { + if (!is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); } - foreach (['fromFile', 'toFile'] as $option) { - if (!\is_string($options[$option])) { - throw new ConfigurationException($option, 'a string', $options[$option]); - } - } - - foreach (['fromFileDate', 'toFileDate'] as $option) { - if (null !== $options[$option] && !\is_string($options[$option])) { - throw new ConfigurationException($option, 'a string or ', $options[$option]); - } - } + $this->assertString($options, 'fromFile'); + $this->assertString($options, 'toFile'); + $this->assertStringOrNull($options, 'fromFileDate'); + $this->assertStringOrNull($options, 'toFileDate'); - $this->header = \sprintf( + $this->header = sprintf( "--- %s%s\n+++ %s%s\n", $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], @@ -96,63 +102,65 @@ public function __construct(array $options = []) $this->contextLines = $options['contextLines']; } - public function getDiff(array $diff) + public function getDiff(array $diff): string { - if (0 === \count($diff)) { + if (0 === count($diff)) { return ''; } $this->changed = false; - $buffer = \fopen('php://memory', 'r+b'); - \fwrite($buffer, $this->header); + $buffer = fopen('php://memory', 'r+b'); + fwrite($buffer, $this->header); $this->writeDiffHunks($buffer, $diff); if (!$this->changed) { - \fclose($buffer); + fclose($buffer); return ''; } - $diff = \stream_get_contents($buffer, -1, 0); + $diff = stream_get_contents($buffer, -1, 0); - \fclose($buffer); + fclose($buffer); // If the last char is not a linebreak: add it. // This might happen when both the `from` and `to` do not have a trailing linebreak - $last = \substr($diff, -1); + $last = substr($diff, -1); return "\n" !== $last && "\r" !== $last ? $diff . "\n" - : $diff - ; + : $diff; } - private function writeDiffHunks($output, array $diff) + private function writeDiffHunks($output, array $diff): void { // detect "No newline at end of file" and insert into `$diff` if needed - $upperLimit = \count($diff); + $upperLimit = count($diff); if (0 === $diff[$upperLimit - 1][1]) { - $lc = \substr($diff[$upperLimit - 1][0], -1); + $lc = substr($diff[$upperLimit - 1][0], -1); + if ("\n" !== $lc) { - \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } } else { // search back for the last `+` and `-` line, // check if has trailing linebreak, else add under it warning under it $toFind = [1 => true, 2 => true]; + for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); - $lc = \substr($diff[$i][0], -1); + $lc = substr($diff[$i][0], -1); + if ("\n" !== $lc) { - \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } - if (!\count($toFind)) { + if (!count($toFind)) { break; } } @@ -161,11 +169,13 @@ private function writeDiffHunks($output, array $diff) // write hunks to output buffer - $cutOff = \max($this->commonLineThreshold, $this->contextLines); + $cutOff = max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; + $i = 0; + /** @var int $i */ foreach ($diff as $i => $entry) { if (0 === $entry[1]) { // same if (false === $hunkCapture) { @@ -182,8 +192,7 @@ private function writeDiffHunks($output, array $diff) if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 ? $hunkCapture - : $this->contextLines - ; + : $this->contextLines; // note: $contextEndOffset = $this->contextLines; // @@ -247,12 +256,11 @@ private function writeDiffHunks($output, array $diff) $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture - : $this->contextLines - ; + : $this->contextLines; // prevent trying to write out more common lines than there are in the diff _and_ // do not write more than configured through the context lines - $contextEndOffset = \min($sameCount, $this->contextLines); + $contextEndOffset = min($sameCount, $this->contextLines); $fromRange -= $sameCount; $toRange -= $sameCount; @@ -271,39 +279,40 @@ private function writeDiffHunks($output, array $diff) private function writeHunk( array $diff, - $diffStartIndex, - $diffEndIndex, - $fromStart, - $fromRange, - $toStart, - $toRange, + int $diffStartIndex, + int $diffEndIndex, + int $fromStart, + int $fromRange, + int $toStart, + int $toRange, $output - ) { - \fwrite($output, '@@ -' . $fromStart); + ): void { + fwrite($output, '@@ -' . $fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { - \fwrite($output, ',' . $fromRange); + fwrite($output, ',' . $fromRange); } - \fwrite($output, ' +' . $toStart); + fwrite($output, ' +' . $toStart); + if (!$this->collapseRanges || 1 !== $toRange) { - \fwrite($output, ',' . $toRange); + fwrite($output, ',' . $toRange); } - \fwrite($output, " @@\n"); + fwrite($output, " @@\n"); for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === Differ::ADDED) { $this->changed = true; - \fwrite($output, '+' . $diff[$i][0]); + fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { $this->changed = true; - \fwrite($output, '-' . $diff[$i][0]); + fwrite($output, '-' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::OLD) { - \fwrite($output, ' ' . $diff[$i][0]); + fwrite($output, ' ' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { $this->changed = true; - \fwrite($output, $diff[$i][0]); + fwrite($output, $diff[$i][0]); } //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package // skip @@ -312,4 +321,18 @@ private function writeHunk( //} } } + + private function assertString(array $options, string $option): void + { + if (!is_string($options[$option])) { + throw new ConfigurationException($option, 'a string', $options[$option]); + } + } + + private function assertStringOrNull(array $options, string $option): void + { + if (null !== $options[$option] && !is_string($options[$option])) { + throw new ConfigurationException($option, 'a string or ', $options[$option]); + } + } } diff --git a/vendor/php-cs-fixer/diff/src/Output/UnifiedDiffOutputBuilder.php b/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php similarity index 70% rename from vendor/php-cs-fixer/diff/src/Output/UnifiedDiffOutputBuilder.php rename to vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php index 13e6636a41..8aae645043 100644 --- a/vendor/php-cs-fixer/diff/src/Output/UnifiedDiffOutputBuilder.php +++ b/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php @@ -1,4 +1,4 @@ -header = $header; $this->addLineNumbers = $addLineNumbers; } - public function getDiff(array $diff) + public function getDiff(array $diff): string { - $buffer = \fopen('php://memory', 'r+b'); + $buffer = fopen('php://memory', 'r+b'); if ('' !== $this->header) { - \fwrite($buffer, $this->header); - if ("\n" !== \substr($this->header, -1, 1)) { - \fwrite($buffer, "\n"); + fwrite($buffer, $this->header); + + if ("\n" !== substr($this->header, -1, 1)) { + fwrite($buffer, "\n"); } } - if (0 !== \count($diff)) { + if (0 !== count($diff)) { $this->writeDiffHunks($buffer, $diff); } - $diff = \stream_get_contents($buffer, -1, 0); + $diff = stream_get_contents($buffer, -1, 0); - \fclose($buffer); + fclose($buffer); - // If the diff is non-empty and a linebreak: add it. + // If the diff is non-empty and last char is not a linebreak: add it. // This might happen when both the `from` and `to` do not have a trailing linebreak - $last = \substr($diff, -1); + $last = substr($diff, -1); - return 0 !== \strlen($diff) && "\n" !== $last && "\r" !== $last + return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last ? $diff . "\n" - : $diff - ; + : $diff; } - private function writeDiffHunks($output, array $diff) + private function writeDiffHunks($output, array $diff): void { // detect "No newline at end of file" and insert into `$diff` if needed - $upperLimit = \count($diff); + $upperLimit = count($diff); if (0 === $diff[$upperLimit - 1][1]) { - $lc = \substr($diff[$upperLimit - 1][0], -1); + $lc = substr($diff[$upperLimit - 1][0], -1); + if ("\n" !== $lc) { - \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } } else { // search back for the last `+` and `-` line, // check if has trailing linebreak, else add under it warning under it $toFind = [1 => true, 2 => true]; + for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); - $lc = \substr($diff[$i][0], -1); + $lc = substr($diff[$i][0], -1); + if ("\n" !== $lc) { - \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); + array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } - if (!\count($toFind)) { + if (!count($toFind)) { break; } } @@ -109,11 +121,13 @@ private function writeDiffHunks($output, array $diff) // write hunks to output buffer - $cutOff = \max($this->commonLineThreshold, $this->contextLines); + $cutOff = max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; + $i = 0; + /** @var int $i */ foreach ($diff as $i => $entry) { if (0 === $entry[1]) { // same if (false === $hunkCapture) { @@ -130,8 +144,7 @@ private function writeDiffHunks($output, array $diff) if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 ? $hunkCapture - : $this->contextLines - ; + : $this->contextLines; // note: $contextEndOffset = $this->contextLines; // @@ -193,12 +206,11 @@ private function writeDiffHunks($output, array $diff) $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture - : $this->contextLines - ; + : $this->contextLines; // prevent trying to write out more common lines than there are in the diff _and_ // do not write more than configured through the context lines - $contextEndOffset = \min($sameCount, $this->contextLines); + $contextEndOffset = min($sameCount, $this->contextLines); $fromRange -= $sameCount; $toRange -= $sameCount; @@ -217,42 +229,43 @@ private function writeDiffHunks($output, array $diff) private function writeHunk( array $diff, - $diffStartIndex, - $diffEndIndex, - $fromStart, - $fromRange, - $toStart, - $toRange, + int $diffStartIndex, + int $diffEndIndex, + int $fromStart, + int $fromRange, + int $toStart, + int $toRange, $output - ) { + ): void { if ($this->addLineNumbers) { - \fwrite($output, '@@ -' . $fromStart); + fwrite($output, '@@ -' . $fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { - \fwrite($output, ',' . $fromRange); + fwrite($output, ',' . $fromRange); } - \fwrite($output, ' +' . $toStart); + fwrite($output, ' +' . $toStart); + if (!$this->collapseRanges || 1 !== $toRange) { - \fwrite($output, ',' . $toRange); + fwrite($output, ',' . $toRange); } - \fwrite($output, " @@\n"); + fwrite($output, " @@\n"); } else { - \fwrite($output, "@@ @@\n"); + fwrite($output, "@@ @@\n"); } for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === Differ::ADDED) { - \fwrite($output, '+' . $diff[$i][0]); + fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { - \fwrite($output, '-' . $diff[$i][0]); + fwrite($output, '-' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::OLD) { - \fwrite($output, ' ' . $diff[$i][0]); + fwrite($output, ' ' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { - \fwrite($output, "\n"); // $diff[$i][0] + fwrite($output, "\n"); // $diff[$i][0] } else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */ - \fwrite($output, ' ' . $diff[$i][0]); + fwrite($output, ' ' . $diff[$i][0]); } } } diff --git a/vendor/php-cs-fixer/diff/src/Parser.php b/vendor/sebastian/diff/src/Parser.php similarity index 58% rename from vendor/php-cs-fixer/diff/src/Parser.php rename to vendor/sebastian/diff/src/Parser.php index 87afb93f7c..cc9e388712 100644 --- a/vendor/php-cs-fixer/diff/src/Parser.php +++ b/vendor/sebastian/diff/src/Parser.php @@ -1,4 +1,4 @@ -\\S+))', $lines[$i], $fromMatch) && - \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { + if (preg_match('#^---\h+"?(?P[^\\v\\t"]+)#', $lines[$i], $fromMatch) && + preg_match('#^\\+\\+\\+\\h+"?(?P[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); @@ -47,7 +50,7 @@ public function parse($string) ++$i; } else { - if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { + if (preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } @@ -55,7 +58,7 @@ public function parse($string) } } - if ($diff !== null && \count($collected)) { + if ($diff !== null && count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; @@ -64,18 +67,19 @@ public function parse($string) return $diffs; } - private function parseFileDiff(Diff $diff, array $lines) + private function parseFileDiff(Diff $diff, array $lines): void { - $chunks = []; - $chunk = null; + $chunks = []; + $chunk = null; + $diffLines = []; foreach ($lines as $line) { - if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { + if (preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( (int) $match['start'], - isset($match['startrange']) ? \max(1, (int) $match['startrange']) : 1, + isset($match['startrange']) ? max(1, (int) $match['startrange']) : 1, (int) $match['end'], - isset($match['endrange']) ? \max(1, (int) $match['endrange']) : 1 + isset($match['endrange']) ? max(1, (int) $match['endrange']) : 1 ); $chunks[] = $chunk; @@ -84,7 +88,7 @@ private function parseFileDiff(Diff $diff, array $lines) continue; } - if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { + if (preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { diff --git a/vendor/php-cs-fixer/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php b/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php similarity index 51% rename from vendor/php-cs-fixer/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php rename to vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php index 5e4d51d5df..4e8d951d4b 100644 --- a/vendor/php-cs-fixer/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php +++ b/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php @@ -1,4 +1,4 @@ - $matrix[$o - $width]) { + if ($firstOrLast > $matrix[$o - 1]) { + $matrix[$o] = $firstOrLast; + } else { + $matrix[$o] = $matrix[$o - 1]; + } + } else { + if ($firstOrLast > $matrix[$o - $width]) { + $matrix[$o] = $firstOrLast; + } else { + $matrix[$o] = $matrix[$o - $width]; + } + } } } @@ -61,6 +77,6 @@ public function calculate(array $from, array $to) } } - return \array_reverse($common); + return array_reverse($common); } } diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE index 4cd8bdd300..6e3afce692 100644 --- a/vendor/symfony/polyfill-mbstring/LICENSE +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2019 Fabien Potencier +Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php index b65c54a6b5..1ad33a86b7 100644 --- a/vendor/symfony/polyfill-mbstring/Mbstring.php +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -48,6 +48,8 @@ * - mb_strstr - Finds first occurrence of a string within another * - mb_strwidth - Return width of string * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase * * Not implemented: * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) @@ -69,7 +71,7 @@ final class Mbstring { public const MB_CASE_FOLD = \PHP_INT_MAX; - private const CASE_FOLD = [ + private const SIMPLE_CASE_FOLD = [ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], ]; @@ -80,7 +82,22 @@ final class Mbstring public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { - if (\is_array($fromEncoding) || ($fromEncoding !== null && false !== strpos($fromEncoding, ','))) { + if (\is_array($s)) { + if (PHP_VERSION_ID < 70200) { + trigger_error('mb_convert_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); + + return null; + } + + $r = []; + foreach ($s as $str) { + $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); + } + + return $r; + } + + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); } else { $fromEncoding = self::getEncoding($fromEncoding); @@ -102,7 +119,7 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null $fromEncoding = 'Windows-1252'; } if ('UTF-8' !== $fromEncoding) { - $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); } return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); @@ -113,7 +130,7 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null $fromEncoding = 'UTF-8'; } - return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); } public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) @@ -130,7 +147,7 @@ public static function mb_convert_variables($toEncoding, $fromEncoding, &...$var public static function mb_decode_mimeheader($s) { - return \iconv_mime_decode($s, 2, self::$internalEncoding); + return iconv_mime_decode($s, 2, self::$internalEncoding); } public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) @@ -140,7 +157,7 @@ public static function mb_encode_mimeheader($s, $charset = null, $transferEncodi public static function mb_decode_numericentity($s, $convmap, $encoding = null) { - if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; @@ -150,7 +167,7 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) return false; } - if (null !== $encoding && !is_scalar($encoding)) { + if (null !== $encoding && !\is_scalar($encoding)) { trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return ''; // Instead of null (cf. mb_encode_numericentity). @@ -166,10 +183,10 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + $s = iconv($encoding, 'UTF-8//IGNORE', $s); } $cnt = floor(\count($convmap) / 4) * 4; @@ -195,12 +212,12 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null) return $s; } - return \iconv('UTF-8', $encoding.'//IGNORE', $s); + return iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) { - if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; @@ -210,13 +227,13 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ return false; } - if (null !== $encoding && !is_scalar($encoding)) { + if (null !== $encoding && !\is_scalar($encoding)) { trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; // Instead of '' (cf. mb_decode_numericentity). } - if (null !== $is_hex && !is_scalar($is_hex)) { + if (null !== $is_hex && !\is_scalar($is_hex)) { trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); return null; @@ -232,10 +249,10 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + $s = iconv($encoding, 'UTF-8//IGNORE', $s); } static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; @@ -265,7 +282,7 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $ return $result; } - return \iconv('UTF-8', $encoding.'//IGNORE', $result); + return iconv('UTF-8', $encoding.'//IGNORE', $result); } public static function mb_convert_case($s, $mode, $encoding = null) @@ -280,10 +297,10 @@ public static function mb_convert_case($s, $mode, $encoding = null) if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + $s = iconv($encoding, 'UTF-8//IGNORE', $s); } if (\MB_CASE_TITLE == $mode) { @@ -301,7 +318,11 @@ public static function mb_convert_case($s, $mode, $encoding = null) $map = $upper; } else { if (self::MB_CASE_FOLD === $mode) { - $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); } static $lower = null; @@ -343,7 +364,7 @@ public static function mb_convert_case($s, $mode, $encoding = null) return $s; } - return \iconv('UTF-8', $encoding.'//IGNORE', $s); + return iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_internal_encoding($encoding = null) @@ -354,7 +375,7 @@ public static function mb_internal_encoding($encoding = null) $normalizedEncoding = self::getEncoding($encoding); - if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { self::$internalEncoding = $normalizedEncoding; return true; @@ -406,6 +427,12 @@ public static function mb_encoding_aliases($encoding) public static function mb_check_encoding($var = null, $encoding = null) { + if (\PHP_VERSION_ID < 70200 && \is_array($var)) { + trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); + + return null; + } + if (null === $encoding) { if (null === $var) { return false; @@ -413,7 +440,20 @@ public static function mb_check_encoding($var = null, $encoding = null) $encoding = self::$internalEncoding; } - return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var); + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; } public static function mb_detect_encoding($str, $encodingList = null, $strict = false) @@ -488,7 +528,7 @@ public static function mb_strlen($s, $encoding = null) return \strlen($s); } - return @\iconv_strlen($s, $encoding); + return @iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) @@ -509,7 +549,7 @@ public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = nu return 0; } - return \iconv_strpos($haystack, $needle, $offset, $encoding); + return iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) @@ -533,7 +573,7 @@ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = n } $pos = '' !== $needle || 80000 > \PHP_VERSION_ID - ? \iconv_strrpos($haystack, $needle, $encoding) + ? iconv_strrpos($haystack, $needle, $encoding) : self::mb_strlen($haystack, $encoding); return false !== $pos ? $offset + $pos : false; @@ -541,7 +581,7 @@ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = n public static function mb_str_split($string, $split_length = 1, $encoding = null) { - if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); return null; @@ -550,6 +590,7 @@ public static function mb_str_split($string, $split_length = 1, $encoding = null if (1 > $split_length = (int) $split_length) { if (80000 > \PHP_VERSION_ID) { trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; } @@ -568,7 +609,7 @@ public static function mb_str_split($string, $split_length = 1, $encoding = null } $rx .= '.{'.$split_length.'})/us'; - return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } $result = []; @@ -617,7 +658,7 @@ public static function mb_substr($s, $start, $length = null, $encoding = null) } if ($start < 0) { - $start = \iconv_strlen($s, $encoding) + $start; + $start = iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } @@ -626,19 +667,21 @@ public static function mb_substr($s, $start, $length = null, $encoding = null) if (null === $length) { $length = 2147483647; } elseif ($length < 0) { - $length = \iconv_strlen($s, $encoding) + $length - $start; + $length = iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } - return (string) \iconv_substr($s, $start, $length, $encoding); + return (string) iconv_substr($s, $start, $length, $encoding); } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { - $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); - $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); return self::mb_strpos($haystack, $needle, $offset, $encoding); } @@ -657,7 +700,7 @@ public static function mb_strrchr($haystack, $needle, $part = false, $encoding = $pos = strrpos($haystack, $needle); } else { $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = \iconv_strrpos($haystack, $needle, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); } return self::getSubpart($pos, $part, $haystack, $encoding); @@ -673,8 +716,11 @@ public static function mb_strrichr($haystack, $needle, $part = false, $encoding public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { - $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); - $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); return self::mb_strrpos($haystack, $needle, $offset, $encoding); } @@ -736,12 +782,12 @@ public static function mb_strwidth($s, $encoding = null) $encoding = self::getEncoding($encoding); if ('UTF-8' !== $encoding) { - $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + $s = iconv($encoding, 'UTF-8//IGNORE', $s); } $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); - return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) @@ -797,6 +843,69 @@ public static function mb_ord($s, $encoding = null) return $code; } + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given'); + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + public static function mb_ucfirst(string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given'); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + public static function mb_lcfirst(string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } else { + self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given'); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + private static function getSubpart($pos, $part, $haystack, $encoding) { if (false === $pos) { @@ -870,4 +979,18 @@ private static function getEncoding($encoding) return $encoding; } + + private static function assertEncoding(string $encoding, string $errorFormat): void + { + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + } } diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md index 4efb599d81..478b40da25 100644 --- a/vendor/symfony/polyfill-mbstring/README.md +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -5,7 +5,7 @@ This component provides a partial, native PHP implementation for the [Mbstring](https://php.net/mbstring) extension. More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 0000000000..512bba0bfd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'ſ' => 's', + 'ͅ' => 'ι', + 'ς' => 'σ', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'ι' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'ΐ' => 'ΐ', + 'ΰ' => 'ΰ', + 'և' => 'եւ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', + 'ὔ' => 'ὔ', + 'ὖ' => 'ὖ', + 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'ἁι', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'ᾍ' => 'ἅι', + 'ᾎ' => 'ἆι', + 'ᾏ' => 'ἇι', + 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', + 'ᾒ' => 'ἢι', + 'ᾓ' => 'ἣι', + 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', + 'ᾖ' => 'ἦι', + 'ᾗ' => 'ἧι', + 'ᾘ' => 'ἠι', + 'ᾙ' => 'ἡι', + 'ᾚ' => 'ἢι', + 'ᾛ' => 'ἣι', + 'ᾜ' => 'ἤι', + 'ᾝ' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'ᾢ' => 'ὢι', + 'ᾣ' => 'ὣι', + 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', + 'ᾦ' => 'ὦι', + 'ᾧ' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'ᾭ' => 'ὥι', + 'ᾮ' => 'ὦι', + 'ᾯ' => 'ὧι', + 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', + 'ᾴ' => 'άι', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾶι', + 'ᾼ' => 'αι', + 'ῂ' => 'ὴι', + 'ῃ' => 'ηι', + 'ῄ' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'ῴ' => 'ώι', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', +]; diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php index 1fedd1f7c8..6e4b5fce84 100644 --- a/vendor/symfony/polyfill-mbstring/bootstrap.php +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -132,6 +132,18 @@ function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } } +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + if (extension_loaded('mbstring')) { return; } diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php index 82f5ac4d0f..ec2ae42765 100644 --- a/vendor/symfony/polyfill-mbstring/bootstrap80.php +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -128,6 +128,18 @@ function mb_scrub(?string $string, ?string $encoding = null): string { $encoding function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } } +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + if (extension_loaded('mbstring')) { return; } diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json index 1fa21ca16c..bd99d4b9d9 100644 --- a/vendor/symfony/polyfill-mbstring/composer.json +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -30,9 +30,6 @@ }, "minimum-stability": "dev", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE index 5593b1d84f..0ed3a24655 100644 --- a/vendor/symfony/polyfill-php80/LICENSE +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2020 Fabien Potencier +Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md index 10b8ee49a8..3816c559d5 100644 --- a/vendor/symfony/polyfill-php80/README.md +++ b/vendor/symfony/polyfill-php80/README.md @@ -3,12 +3,13 @@ Symfony Polyfill / Php80 This component provides features added to PHP 8.0 core: -- `Stringable` interface +- [`Stringable`](https://php.net/stringable) interface - [`fdiv`](https://php.net/fdiv) -- `ValueError` class -- `UnhandledMatchError` class +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class - `FILTER_VALIDATE_BOOL` constant - [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class - [`preg_last_error_msg`](https://php.net/preg_last_error_msg) - [`str_contains`](https://php.net/str_contains) - [`str_starts_with`](https://php.net/str_starts_with) diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php index 7ea6d2772d..2b955423fc 100644 --- a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + #[Attribute(Attribute::TARGET_CLASS)] final class Attribute { diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php index 72f10812b3..bd1212f6e4 100644 --- a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -1,6 +1,15 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { class PhpToken extends Symfony\Polyfill\Php80\PhpToken { } diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php index 77e037cb58..7c62d7508b 100644 --- a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + if (\PHP_VERSION_ID < 80000) { interface Stringable { diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php index 37937cbfae..01c6c6c8ab 100644 --- a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + if (\PHP_VERSION_ID < 80000) { class UnhandledMatchError extends Error { diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php index a3a9b88b09..783dbc28c7 100644 --- a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + if (\PHP_VERSION_ID < 80000) { class ValueError extends Error { diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json index 5fe679db39..46ccde2036 100644 --- a/vendor/symfony/polyfill-php80/composer.json +++ b/vendor/symfony/polyfill-php80/composer.json @@ -29,9 +29,6 @@ }, "minimum-stability": "dev", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" diff --git a/vendor/symfony/polyfill-php81/LICENSE b/vendor/symfony/polyfill-php81/LICENSE index efb17f98e7..99c6bdf356 100644 --- a/vendor/symfony/polyfill-php81/LICENSE +++ b/vendor/symfony/polyfill-php81/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 Fabien Potencier +Copyright (c) 2021-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/polyfill-php81/README.md b/vendor/symfony/polyfill-php81/README.md index 5ef61be6a8..c07ef78202 100644 --- a/vendor/symfony/polyfill-php81/README.md +++ b/vendor/symfony/polyfill-php81/README.md @@ -4,11 +4,13 @@ Symfony Polyfill / Php81 This component provides features added to PHP 8.1 core: - [`array_is_list`](https://php.net/array_is_list) -- [`MYSQLI_REFRESH_REPLICA`](https://www.php.net/manual/en/mysqli.constants.php#constantmysqli-refresh-replica) constant +- [`enum_exists`](https://php.net/enum-exists) +- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant - [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) +- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used) More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= diff --git a/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php b/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php new file mode 100644 index 0000000000..5ff93fcaf2 --- /dev/null +++ b/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) { + /** + * @property string $data + */ + class CURLStringFile extends CURLFile + { + private $data; + + public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') + { + $this->data = $data; + parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); + } + + public function __set(string $name, $value): void + { + if ('data' !== $name) { + $this->$name = $value; + + return; + } + + if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { + throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); + } + + $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); + } + + public function __isset(string $name): bool + { + return isset($this->$name); + } + + public function &__get(string $name) + { + return $this->$name; + } + } +} diff --git a/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php b/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php index f4cad34f64..cb7720a8da 100644 --- a/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php +++ b/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + if (\PHP_VERSION_ID < 80100) { #[Attribute(Attribute::TARGET_METHOD)] final class ReturnTypeWillChange diff --git a/vendor/symfony/polyfill-php81/composer.json b/vendor/symfony/polyfill-php81/composer.json index c39ccf4778..381af79ace 100644 --- a/vendor/symfony/polyfill-php81/composer.json +++ b/vendor/symfony/polyfill-php81/composer.json @@ -25,9 +25,6 @@ }, "minimum-stability": "dev", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill"