diff --git a/.appveyor.yml b/.appveyor.yml index 71e688209c7..768872e5047 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,8 +5,8 @@ clone_folder: C:\projects\php-cs-fixer environment: matrix: - - php_ver: 7.1.2 - - php_ver: 5.6.30 + - php_ver: 7.3.1 + - php_ver: 5.6.40 cache: - '%APPDATA%\Composer' @@ -28,13 +28,14 @@ install: - echo extension=php_curl.dll >> php.ini - echo extension=php_openssl.dll >> php.ini - echo extension=php_mbstring.dll >> php.ini - - IF NOT EXIST C:\tools\composer.phar (cd C:\tools && appveyor DownloadFile https://getcomposer.org/download/1.4.1/composer.phar) + - IF NOT EXIST C:\tools\composer.phar (cd C:\tools && appveyor DownloadFile https://getcomposer.org/composer.phar) - cd C:\projects\php-cs-fixer - php C:\tools\composer.phar global show hirak/prestissimo -q || php C:\tools\composer.phar global require hirak/prestissimo before_test: - cd C:\projects\php-cs-fixer - php C:\tools\composer.phar update --optimize-autoloader --no-interaction --no-progress --prefer-stable --no-ansi + - php C:\tools\composer.phar info -D | sort test_script: - cd C:\projects\php-cs-fixer diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..426285b3374 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +version: 2 + +jobs: + build: + macos: + xcode: '10.1.0' + steps: + - checkout + + - restore_cache: + keys: + - cache-{{ checksum "composer.json" }} + + - run: brew update + - run: brew install php@7.2 + - run: brew link --force --overwrite php@7.2 + - run: php --version + - run: echo "memory_limit = 512M" > $(brew --prefix)/etc/php/7.2/conf.d/memory.ini + - run: curl -sS https://getcomposer.org/installer | php + - run: php composer.phar global show hirak/prestissimo -q || php composer.phar global require --no-interaction --no-progress --optimize-autoloader hirak/prestissimo + - run: php composer.phar install --optimize-autoloader --no-interaction --no-progress --no-suggest + - run: php composer.phar info -D | sort + + - save_cache: + key: cache-{{ checksum "composer.json" }} + paths: + - ~/.composer + - ~/Library/Caches/Homebrew + + - run: vendor/bin/phpunit + - run: PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer --diff --dry-run -v fix diff --git a/.gitattributes b/.gitattributes index a498fc2dde3..6b8e4eea1e9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ tests/Test/AbstractFixerWithAliasedOptionsTestCase.php export-ignore tests/Test/AbstractTransformerTestCase.php export-ignore .appveyor.yml export-ignore +.circleci/ export-ignore .composer-require-checker.json export-ignore .editorconfig export-ignore .gitattributes export-ignore diff --git a/.php_cs.dist b/.php_cs.dist index 50c26e27103..ebf6f21bc92 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -13,6 +13,7 @@ EOF; $finder = PhpCsFixer\Finder::create() ->exclude('tests/Fixtures') ->in(__DIR__) + ->append([__DIR__.'/php-cs-fixer']) ; $config = PhpCsFixer\Config::create() @@ -20,62 +21,10 @@ $config = PhpCsFixer\Config::create() ->setRules([ '@PHP56Migration' => true, '@PHPUnit60Migration:risky' => true, - '@Symfony' => true, - '@Symfony:risky' => true, - 'align_multiline_comment' => true, - 'array_indentation' => true, - 'array_syntax' => ['syntax' => 'short'], - 'blank_line_before_statement' => true, - 'combine_consecutive_issets' => true, - 'combine_consecutive_unsets' => true, - 'comment_to_phpdoc' => true, - 'compact_nullable_typehint' => true, - 'escape_implicit_backslashes' => true, - 'explicit_indirect_variable' => true, - 'explicit_string_variable' => true, - 'final_internal_class' => true, - 'fully_qualified_strict_types' => true, - 'function_to_constant' => true, + '@PhpCsFixer' => true, + '@PhpCsFixer:risky' => true, 'header_comment' => ['header' => $header], - 'heredoc_to_nowdoc' => true, 'list_syntax' => ['syntax' => 'long'], - 'logical_operators' => true, - 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], - 'method_chaining_indentation' => true, - 'multiline_comment_opening_closing' => true, - 'native_function_invocation' => false, - 'no_alternative_syntax' => true, - 'no_binary_string' => true, - 'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']], - 'no_null_property_initialization' => true, - 'no_short_echo_tag' => true, - 'no_superfluous_elseif' => true, - 'no_unneeded_curly_braces' => true, - 'no_unneeded_final_method' => true, - 'no_unreachable_default_argument_value' => true, - 'no_unset_on_property' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'ordered_class_elements' => true, - 'ordered_imports' => true, - 'php_unit_internal_class' => true, - 'php_unit_ordered_covers' => true, - 'php_unit_set_up_tear_down_visibility' => true, - 'php_unit_strict' => true, - 'php_unit_test_annotation' => true, - 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], - 'php_unit_test_class_requires_covers' => true, - 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_order' => true, - 'phpdoc_trim_consecutive_blank_line_separation' => true, - 'phpdoc_types_order' => true, - 'return_assignment' => true, - 'semicolon_after_instruction' => true, - 'single_line_comment_style' => true, - 'strict_comparison' => true, - 'strict_param' => true, - 'string_line_ending' => true, - 'yoda_style' => true, ]) ->setFinder($finder) ; diff --git a/.travis.yml b/.travis.yml index 5914eb29229..97fc41d4918 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: php -sudo: false - git: depth: 1 @@ -66,8 +64,8 @@ jobs: php: 7.0 install: # Composer: enforce given Symfony components version - - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/force-lowest:$SYMFONY_VERSION; fi - - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/lts:$(echo $SYMFONY_VERSION | grep -o 'v[0-9]\+') || true; fi + - if [ "$SYMFONY_VERSION" != "" ]; then composer global show symfony/flex -q || travis_retry composer global require $DEFAULT_COMPOSER_FLAGS symfony/flex; fi + - if [ "$SYMFONY_VERSION" != "" ]; then composer config extra.symfony.require $SYMFONY_VERSION || true; fi - travis_retry composer update $DEFAULT_COMPOSER_FLAGS $COMPOSER_FLAGS - composer info -D | sort @@ -85,7 +83,7 @@ jobs: <<: *STANDARD_TEST_JOB stage: Test php: 7.1 - env: SYMFONY_DEPRECATIONS_HELPER=weak SYMFONY_VERSION="v4.0" + env: SYMFONY_DEPRECATIONS_HELPER=weak SYMFONY_VERSION="~4.1.0" - <<: *STANDARD_TEST_JOB @@ -95,7 +93,14 @@ jobs: - <<: *STANDARD_TEST_JOB stage: Test - php: 7.2 + php: 7.3 + before_script: + - php php-cs-fixer fix --rules @PHP71Migration,@PHP71Migration:risky,blank_line_after_opening_tag -q || travis_terminate 1 + + - + <<: *STANDARD_TEST_JOB + stage: Test + php: 7.3 env: COLLECT_COVERAGE=1 before_install: # check phpdbg @@ -122,18 +127,17 @@ jobs: stage: Test php: nightly env: COMPOSER_FLAGS="--ignore-platform-reqs" PHP_CS_FIXER_IGNORE_ENV=1 SYMFONY_DEPRECATIONS_HELPER=weak - script: - - php php-cs-fixer fix --rules @PHP71Migration,@PHP71Migration:risky,native_function_invocation -q || travis_terminate 1 - - vendor/bin/phpunit || travis_terminate 1 - - git checkout . -q - - PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer --diff --dry-run -v fix - stage: Deployment php: 7.1 install: ./dev-tools/build.sh script: - - PHP_CS_FIXER_TEST_ALLOW_SKIPPING_PHAR_TESTS=0 vendor/bin/phpunit tests/Smoke/ + - PHP_CS_FIXER_TEST_ALLOW_SKIPPING_SMOKE_TESTS=0 vendor/bin/phpunit tests/Smoke/ + + before_deploy: + # ensure that deployment is happening only if tag matches version of PHP CS Fixer + - test $(php dev-tools/info-extractor.php | jq .version.vnumber) == "\"$TRAVIS_TAG\"" deploy: provider: releases api_key: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a33603ef68..71687d98ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,358 @@ CHANGELOG for PHP CS Fixer This file contains changelogs for stable releases only. +Changelog for v2.14.2 +--------------------- + +* minor #4306 DX: Drop HHVM conflict on Composer level to help Composer with HHVM compatibility, we still prevent HHVM on runtime (keradus) + +Changelog for v2.14.1 +--------------------- + +* bug #4240 ModernizeTypesCastingFixer - fix for operators with higher precedence (kubawerlos) +* bug #4254 PhpUnitDedicateAssertFixer - fix for count with additional operations (kubawerlos) +* bug #4260 Psr0Fixer and Psr4Fixer - fix for multiple classes in file with anonymous class (kubawerlos) +* bug #4262 FixCommand - fix help (keradus) +* bug #4276 MethodChainingIndentationFixer, ArrayIndentationFixer - Fix priority issue (dmvdbrugge) +* bug #4280 MethodArgumentSpaceFixer - Fix method argument alignment (Billz95) +* bug #4286 IncrementStyleFixer - fix for static statement (kubawerlos) +* bug #4291 ArrayIndentationFixer - Fix indentation after trailing spaces (julienfalque, keradus) +* bug #4292 NoSuperfluousPhpdocTagsFixer - Make null only type not considered superfluous (julienfalque) +* minor #4204 DX: Tokens - do not unregister/register found tokens when collection is not changing (kubawerlos) +* minor #4235 DX: more specific @param types (kubawerlos) +* minor #4263 DX: AppVeyor - bump PHP version (keradus) +* minor #4293 Add official support for PHP 7.3 (keradus) +* minor #4295 DX: MethodArgumentSpaceFixerTest - fix edge case for handling different line ending when only expected code is provided (keradus) +* minor #4296 DX: cleanup testing with fixer config (keradus) +* minor #4299 NativeFunctionInvocationFixer - add array_key_exists (deguif, keradus) +* minor #4300 DX: cleanup testing with fixer config (keradus) + +Changelog for v2.14.0 +--------------------- + +* bug #4220 NativeFunctionInvocationFixer - namespaced strict to remove backslash (kubawerlos) +* feature #3881 Add PhpdocVarAnnotationCorrectOrderFixer (kubawerlos) +* feature #3915 Add HeredocIndentationFixer (gharlan) +* feature #4002 NoSuperfluousPhpdocTagsFixer - Allow `mixed` in superfluous PHPDoc by configuration (MortalFlesh) +* feature #4030 Add get_required_files and user_error aliases (ntzm) +* feature #4043 NativeFunctionInvocationFixer - add option to remove redundant backslashes (kubawerlos) +* feature #4102 Add NoUnsetCastFixer (SpacePossum) +* minor #4025 Add phpdoc_types_order rule to Symfony's ruleset (carusogabriel) +* minor #4213 [7.3] PHP7.3 integration tests (SpacePossum) +* minor #4233 Add official support for PHP 7.3 (keradus) + +Changelog for v2.13.3 +--------------------- + +* bug #4216 Psr4Fixer - fix for multiple classy elements in file (keradus, kubawerlos) +* bug #4217 Psr0Fixer - class with anonymous class (kubawerlos) +* bug #4219 NativeFunctionCasingFixer - handle T_RETURN_REF (kubawerlos) +* bug #4224 FunctionToConstantFixer - handle T_RETURN_REF (SpacePossum) +* bug #4229 IsNullFixer - fix parenthesis not closed (guilliamxavier) +* minor #4193 [7.3] CombineNestedDirnameFixer - support PHP 7.3 (kubawerlos) +* minor #4198 [7.3] PowToExponentiationFixer - adding to PHP7.3 integration test (kubawerlos) +* minor #4199 [7.3] MethodChainingIndentationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4200 [7.3] ModernizeTypesCastingFixer - support PHP 7.3 (kubawerlos) +* minor #4201 [7.3] MultilineWhitespaceBeforeSemicolonsFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4202 [7.3] ErrorSuppressionFixer - support PHP 7.3 (kubawerlos) +* minor #4205 DX: PhpdocAlignFixer - refactor to use DocBlock (kubawerlos) +* minor #4206 DX: enable multiline_whitespace_before_semicolons (keradus) +* minor #4207 [7.3] RandomApiMigrationFixerTest - tests for 7.3 (SpacePossum) +* minor #4208 [7.3] NativeFunctionCasingFixerTest - tests for 7.3 (SpacePossum) +* minor #4209 [7.3] PhpUnitStrictFixerTest - tests for 7.3 (SpacePossum) +* minor #4210 [7.3] PhpUnitConstructFixer - add test for PHP 7.3 (kubawerlos) +* minor #4211 [7.3] PhpUnitDedicateAssertFixer - support PHP 7.3 (kubawerlos) +* minor #4214 [7.3] NoUnsetOnPropertyFixerTest - tests for 7.3 (SpacePossum) +* minor #4222 [7.3] PhpUnitExpectationFixer - support PHP 7.3 (kubawerlos) +* minor #4223 [7.3] PhpUnitMockFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4230 [7.3] IsNullFixer - fix trailing comma (guilliamxavier) +* minor #4232 DX: remove Utils::splitLines (kubawerlos) +* minor #4234 [7.3] Test that "LITERAL instanceof X" is valid (guilliamxavier) + +Changelog for v2.13.2 +--------------------- + +* bug #3968 SelfAccessorFixer - support FQCN (kubawerlos) +* bug #3974 Psr4Fixer - class with anonymous class (kubawerlos) +* bug #3987 Run HeaderCommentFixer after NoBlankLinesAfterPhpdocFixer (StanAngeloff) +* bug #4009 TypeAlternationTransformer - Fix pipes in function call with constants being classified incorrectly (ntzm, SpacePossum) +* bug #4022 NoUnsetOnPropertyFixer - refactor and bugfixes (kubawerlos) +* bug #4036 ExplicitStringVariableFixer - fixes for backticks and for 2 variables next to each other (kubawerlos, Slamdunk) +* bug #4038 CommentToPhpdocFixer - handling nested PHPDoc (kubawerlos) +* bug #4064 Ignore invalid mode strings, add option to remove the "b" flag. (SpacePossum) +* bug #4071 DX: do not insert Token when calling removeLeadingWhitespace/removeTrailingWhitespace from Tokens (kubawerlos) +* bug #4073 IsNullFixer - fix function detection (kubawerlos) +* bug #4074 FileFilterIterator - do not filter out files that need fixing (SpacePossum) +* bug #4076 EregToPregFixer - fix function detection (kubawerlos) +* bug #4084 MethodChainingIndentation - fix priority with Braces (dmvdbrugge) +* bug #4099 HeaderCommentFixer - throw exception on invalid header configuration (SpacePossum) +* bug #4100 PhpdocAddMissingParamAnnotationFixer - Handle variable number of arguments and pass by reference cases (SpacePossum) +* bug #4101 ReturnAssignmentFixer - do not touch invalid code (SpacePossum) +* bug #4104 Change transformers order, fixing untransformed T_USE (dmvdbrugge) +* bug #4107 Preg::split - fix for non-UTF8 subject (ostrolucky, kubawerlos) +* bug #4109 NoBlankLines*: fix removing lines consisting only of spaces (kubawerlos, keradus) +* bug #4114 VisibilityRequiredFixer - don't remove comments (kubawerlos) +* bug #4116 OrderedImportsFixer - fix sorting without any grouping (SpacePossum) +* bug #4119 PhpUnitNoExpectationAnnotationFixer - fix extracting content from annotation (kubawerlos) +* bug #4127 LowercaseConstantsFixer - Fix case with properties using constants as their name (srathbone) +* bug #4134 [7.3] SquareBraceTransformer - nested array destructuring not handled correctly (SpacePossum) +* bug #4153 PhpUnitFqcnAnnotationFixer - handle only PhpUnit classes (kubawerlos) +* bug #4169 DirConstantFixer - Fixes for PHP7.3 syntax (SpacePossum) +* bug #4181 MultilineCommentOpeningClosingFixer - fix handling empty comment (kubawerlos) +* bug #4186 Tokens - fix removal of leading/trailing whitespace with empty token in collection (kubawerlos) +* minor #3436 Add a handful of integration tests (BackEndTea) +* minor #3774 PhpUnitTestClassRequiresCoversFixer - Remove unneeded loop and use phpunit indicator class (BackEndTea, SpacePossum) +* minor #3778 DX: Throw an exception if FileReader::read fails (ntzm) +* minor #3916 New ruleset "@PhpCsFixer" (gharlan) +* minor #4007 Fixes cookbook for fixers (greeflas) +* minor #4031 Correct FixerOptionBuilder::getOption return type (ntzm) +* minor #4046 Token - Added fast isset() path to token->equals() (staabm) +* minor #4047 Token - inline $other->getPrototype() to speedup equals() (staabm, keradus) +* minor #4048 Tokens - inlined extractTokenKind() call on the hot path (staabm) +* minor #4069 DX: Add dev-tools directory to gitattributes as export-ignore (alexmanno) +* minor #4070 Docs: Add link to a VS Code extension in readme (jakebathman) +* minor #4077 DX: cleanup - NoAliasFunctionsFixer - use FunctionsAnalyzer (kubawerlos) +* minor #4088 Add Travis test with strict types (kubawerlos) +* minor #4091 Adjust misleading sentence in CONTRIBUTING.md (ostrolucky) +* minor #4092 UseTransformer - simplify/optimize (SpacePossum) +* minor #4095 DX: Use ::class (keradus) +* minor #4096 DX: fixing typo (kubawerlos) +* minor #4097 DX: namespace casing (kubawerlos) +* minor #4110 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4115 Changes for upcoming Travis' infra migration (sergeyklay) +* minor #4122 DX: AppVeyor - Update Composer download link (SpacePossum) +* minor #4128 DX: cleanup - AbstractFunctionReferenceFixer - use FunctionsAnalyzer (SpacePossum, kubawerlos) +* minor #4129 Fix: Symfony 4.2 deprecations (kubawerlos) +* minor #4139 DX: Fix CircleCI (kubawerlos) +* minor #4142 [7.3] NoAliasFunctionsFixer - mbregex_encoding' => 'mb_regex_encoding (SpacePossum) +* minor #4143 PhpUnitTestCaseStaticMethodCallsFixer - Add PHPUnit 7.5 new assertions (Slamdunk) +* minor #4149 [7.3] ArgumentsAnalyzer - PHP7.3 support (SpacePossum) +* minor #4161 DX: CI - show packages installed via Composer (keradus) +* minor #4162 DX: Drop symfony/lts (keradus) +* minor #4166 DX: do not use AbstractFunctionReferenceFixer when no need to (kubawerlos) +* minor #4168 DX: FopenFlagsFixer - remove useless proxy method (SpacePossum) +* minor #4171 Fix CircleCI cache (kubawerlos) +* minor #4173 [7.3] PowToExponentiationFixer - add support for PHP7.3 (SpacePossum) +* minor #4175 Fixing typo (kubawerlos) +* minor #4177 CI: Check that tag is matching version of PHP CS Fixer during deployment (keradus) +* minor #4180 Fixing typo (kubawerlos) +* minor #4182 DX: update php-cs-fixer file style (kubawerlos) +* minor #4185 [7.3] ImplodeCallFixer - add tests for PHP7.3 (kubawerlos) +* minor #4187 [7.3] IsNullFixer - support PHP 7.3 (kubawerlos) +* minor #4188 DX: cleanup (keradus) +* minor #4189 Travis - add PHP 7.3 job (keradus) +* minor #4190 Travis CI - fix config (kubawerlos) +* minor #4192 [7.3] MagicMethodCasingFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4194 [7.3] NativeFunctionInvocationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4195 [7.3] SetTypeToCastFixer - support PHP 7.3 (kubawerlos) +* minor #4196 Update website (keradus) +* minor #4197 [7.3] StrictParamFixer - support PHP 7.3 (kubawerlos) + +Changelog for v2.13.1 +--------------------- + +* bug #3977 NoSuperfluousPhpdocTagsFixer - Fix handling of description with variable (julienfalque) +* bug #4027 PhpdocAnnotationWithoutDotFixer - add failing cases (keradus) +* bug #4028 PhpdocNoEmptyReturnFixer - handle single line PHPDoc (kubawerlos) +* bug #4034 PhpUnitTestCaseIndicator - handle anonymous class (kubawerlos) +* bug #4037 NativeFunctionInvocationFixer - fix function detection (kubawerlos) +* feature #4019 PhpdocTypesFixer - allow for configuration (keradus) +* minor #3980 Clarifies allow-risky usage (josephzidell) +* minor #4016 Bump console component due to it's bug (keradus) +* minor #4023 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4049 use parent::offset*() methods when moving items around in insertAt() (staabm) + +Changelog for v2.13.0 +--------------------- + +* feature #3739 Add MagicMethodCasingFixer (SpacePossum) +* 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 #3876 NativeConstantInvocationFixer - add the scope option (stof, keradus) +* feature #3886 Add PhpUnitMethodCasingFixer (Slamdunk) +* feature #3907 Add ImplodeCallFixer (kubawerlos) +* feature #3914 NoUnreachableDefaultArgumentValueFixer - remove `null` for nullable typehints (gharlan, keradus) +* minor #3813 PhpUnitDedicateAssertFixer - fix "sizeOf" same as "count". (SpacePossum) +* minor #3873 Add the native_function_invocation fixer in the Symfony:risky ruleset (stof) +* minor #3979 DX: enable php_unit_method_casing (keradus) + +Changelog for v2.12.8 +--------------------- + +* minor #4306 DX: Drop HHVM conflict on Composer level to help Composer with HHVM compatibility, we still prevent HHVM on runtime (keradus) + +Changelog for v2.12.7 +--------------------- + +* bug #4240 ModernizeTypesCastingFixer - fix for operators with higher precedence (kubawerlos) +* bug #4254 PhpUnitDedicateAssertFixer - fix for count with additional operations (kubawerlos) +* bug #4260 Psr0Fixer and Psr4Fixer - fix for multiple classes in file with anonymous class (kubawerlos) +* bug #4262 FixCommand - fix help (keradus) +* bug #4276 MethodChainingIndentationFixer, ArrayIndentationFixer - Fix priority issue (dmvdbrugge) +* bug #4280 MethodArgumentSpaceFixer - Fix method argument alignment (Billz95) +* bug #4286 IncrementStyleFixer - fix for static statement (kubawerlos) +* bug #4291 ArrayIndentationFixer - Fix indentation after trailing spaces (julienfalque, keradus) +* bug #4292 NoSuperfluousPhpdocTagsFixer - Make null only type not considered superfluous (julienfalque) +* minor #4204 DX: Tokens - do not unregister/register found tokens when collection is not changing (kubawerlos) +* minor #4235 DX: more specific @param types (kubawerlos) +* minor #4263 DX: AppVeyor - bump PHP version (keradus) +* minor #4293 Add official support for PHP 7.3 (keradus) +* minor #4295 DX: MethodArgumentSpaceFixerTest - fix edge case for handling different line ending when only expected code is provided (keradus) +* minor #4296 DX: cleanup testing with fixer config (keradus) +* minor #4299 NativeFunctionInvocationFixer - add array_key_exists (deguif, keradus) + +Changelog for v2.12.6 +--------------------- + +* bug #4216 Psr4Fixer - fix for multiple classy elements in file (keradus, kubawerlos) +* bug #4217 Psr0Fixer - class with anonymous class (kubawerlos) +* bug #4219 NativeFunctionCasingFixer - handle T_RETURN_REF (kubawerlos) +* bug #4224 FunctionToConstantFixer - handle T_RETURN_REF (SpacePossum) +* bug #4229 IsNullFixer - fix parenthesis not closed (guilliamxavier) +* minor #4198 [7.3] PowToExponentiationFixer - adding to PHP7.3 integration test (kubawerlos) +* minor #4199 [7.3] MethodChainingIndentationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4200 [7.3] ModernizeTypesCastingFixer - support PHP 7.3 (kubawerlos) +* minor #4201 [7.3] MultilineWhitespaceBeforeSemicolonsFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4202 [7.3] ErrorSuppressionFixer - support PHP 7.3 (kubawerlos) +* minor #4205 DX: PhpdocAlignFixer - refactor to use DocBlock (kubawerlos) +* minor #4206 DX: enable multiline_whitespace_before_semicolons (keradus) +* minor #4207 [7.3] RandomApiMigrationFixerTest - tests for 7.3 (SpacePossum) +* minor #4208 [7.3] NativeFunctionCasingFixerTest - tests for 7.3 (SpacePossum) +* minor #4209 [7.3] PhpUnitStrictFixerTest - tests for 7.3 (SpacePossum) +* minor #4210 [7.3] PhpUnitConstructFixer - add test for PHP 7.3 (kubawerlos) +* minor #4211 [7.3] PhpUnitDedicateAssertFixer - support PHP 7.3 (kubawerlos) +* minor #4214 [7.3] NoUnsetOnPropertyFixerTest - tests for 7.3 (SpacePossum) +* minor #4222 [7.3] PhpUnitExpectationFixer - support PHP 7.3 (kubawerlos) +* minor #4223 [7.3] PhpUnitMockFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4230 [7.3] IsNullFixer - fix trailing comma (guilliamxavier) +* minor #4232 DX: remove Utils::splitLines (kubawerlos) +* minor #4234 [7.3] Test that "LITERAL instanceof X" is valid (guilliamxavier) + +Changelog for v2.12.5 +--------------------- + +* bug #3968 SelfAccessorFixer - support FQCN (kubawerlos) +* bug #3974 Psr4Fixer - class with anonymous class (kubawerlos) +* bug #3987 Run HeaderCommentFixer after NoBlankLinesAfterPhpdocFixer (StanAngeloff) +* bug #4009 TypeAlternationTransformer - Fix pipes in function call with constants being classified incorrectly (ntzm, SpacePossum) +* bug #4022 NoUnsetOnPropertyFixer - refactor and bugfixes (kubawerlos) +* bug #4036 ExplicitStringVariableFixer - fixes for backticks and for 2 variables next to each other (kubawerlos, Slamdunk) +* bug #4038 CommentToPhpdocFixer - handling nested PHPDoc (kubawerlos) +* bug #4071 DX: do not insert Token when calling removeLeadingWhitespace/removeTrailingWhitespace from Tokens (kubawerlos) +* bug #4073 IsNullFixer - fix function detection (kubawerlos) +* bug #4074 FileFilterIterator - do not filter out files that need fixing (SpacePossum) +* bug #4076 EregToPregFixer - fix function detection (kubawerlos) +* bug #4084 MethodChainingIndentation - fix priority with Braces (dmvdbrugge) +* bug #4099 HeaderCommentFixer - throw exception on invalid header configuration (SpacePossum) +* bug #4100 PhpdocAddMissingParamAnnotationFixer - Handle variable number of arguments and pass by reference cases (SpacePossum) +* bug #4101 ReturnAssignmentFixer - do not touch invalid code (SpacePossum) +* bug #4104 Change transformers order, fixing untransformed T_USE (dmvdbrugge) +* bug #4107 Preg::split - fix for non-UTF8 subject (ostrolucky, kubawerlos) +* bug #4109 NoBlankLines*: fix removing lines consisting only of spaces (kubawerlos, keradus) +* bug #4114 VisibilityRequiredFixer - don't remove comments (kubawerlos) +* bug #4116 OrderedImportsFixer - fix sorting without any grouping (SpacePossum) +* bug #4119 PhpUnitNoExpectationAnnotationFixer - fix extracting content from annotation (kubawerlos) +* bug #4127 LowercaseConstantsFixer - Fix case with properties using constants as their name (srathbone) +* bug #4134 [7.3] SquareBraceTransformer - nested array destructuring not handled correctly (SpacePossum) +* bug #4153 PhpUnitFqcnAnnotationFixer - handle only PhpUnit classes (kubawerlos) +* bug #4169 DirConstantFixer - Fixes for PHP7.3 syntax (SpacePossum) +* bug #4181 MultilineCommentOpeningClosingFixer - fix handling empty comment (kubawerlos) +* bug #4186 Tokens - fix removal of leading/trailing whitespace with empty token in collection (kubawerlos) +* minor #3436 Add a handful of integration tests (BackEndTea) +* minor #3774 PhpUnitTestClassRequiresCoversFixer - Remove unneeded loop and use phpunit indicator class (BackEndTea, SpacePossum) +* minor #3778 DX: Throw an exception if FileReader::read fails (ntzm) +* minor #3916 New ruleset "@PhpCsFixer" (gharlan) +* minor #4007 Fixes cookbook for fixers (greeflas) +* minor #4031 Correct FixerOptionBuilder::getOption return type (ntzm) +* minor #4046 Token - Added fast isset() path to token->equals() (staabm) +* minor #4047 Token - inline $other->getPrototype() to speedup equals() (staabm, keradus) +* minor #4048 Tokens - inlined extractTokenKind() call on the hot path (staabm) +* minor #4069 DX: Add dev-tools directory to gitattributes as export-ignore (alexmanno) +* minor #4070 Docs: Add link to a VS Code extension in readme (jakebathman) +* minor #4077 DX: cleanup - NoAliasFunctionsFixer - use FunctionsAnalyzer (kubawerlos) +* minor #4088 Add Travis test with strict types (kubawerlos) +* minor #4091 Adjust misleading sentence in CONTRIBUTING.md (ostrolucky) +* minor #4092 UseTransformer - simplify/optimize (SpacePossum) +* minor #4095 DX: Use ::class (keradus) +* minor #4097 DX: namespace casing (kubawerlos) +* minor #4110 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4115 Changes for upcoming Travis' infra migration (sergeyklay) +* minor #4122 DX: AppVeyor - Update Composer download link (SpacePossum) +* minor #4128 DX: cleanup - AbstractFunctionReferenceFixer - use FunctionsAnalyzer (SpacePossum, kubawerlos) +* minor #4129 Fix: Symfony 4.2 deprecations (kubawerlos) +* minor #4139 DX: Fix CircleCI (kubawerlos) +* minor #4143 PhpUnitTestCaseStaticMethodCallsFixer - Add PHPUnit 7.5 new assertions (Slamdunk) +* minor #4149 [7.3] ArgumentsAnalyzer - PHP7.3 support (SpacePossum) +* minor #4161 DX: CI - show packages installed via Composer (keradus) +* minor #4162 DX: Drop symfony/lts (keradus) +* minor #4166 DX: do not use AbstractFunctionReferenceFixer when no need to (kubawerlos) +* minor #4171 Fix CircleCI cache (kubawerlos) +* minor #4173 [7.3] PowToExponentiationFixer - add support for PHP7.3 (SpacePossum) +* minor #4175 Fixing typo (kubawerlos) +* minor #4177 CI: Check that tag is matching version of PHP CS Fixer during deployment (keradus) +* minor #4182 DX: update php-cs-fixer file style (kubawerlos) +* minor #4187 [7.3] IsNullFixer - support PHP 7.3 (kubawerlos) +* minor #4188 DX: cleanup (keradus) +* minor #4189 Travis - add PHP 7.3 job (keradus) +* minor #4190 Travis CI - fix config (kubawerlos) +* minor #4194 [7.3] NativeFunctionInvocationFixer - add tests for PHP 7.3 (kubawerlos) +* minor #4195 [7.3] SetTypeToCastFixer - support PHP 7.3 (kubawerlos) +* minor #4196 Update website (keradus) +* minor #4197 [7.3] StrictParamFixer - support PHP 7.3 (kubawerlos) + +Changelog for v2.12.4 +--------------------- + +* bug #3977 NoSuperfluousPhpdocTagsFixer - Fix handling of description with variable (julienfalque) +* bug #4027 PhpdocAnnotationWithoutDotFixer - add failing cases (keradus) +* bug #4028 PhpdocNoEmptyReturnFixer - handle single line PHPDoc (kubawerlos) +* bug #4034 PhpUnitTestCaseIndicator - handle anonymous class (kubawerlos) +* bug #4037 NativeFunctionInvocationFixer - fix function detection (kubawerlos) +* feature #4019 PhpdocTypesFixer - allow for configuration (keradus) +* minor #3980 Clarifies allow-risky usage (josephzidell) +* minor #4016 Bump console component due to it's bug (keradus) +* minor #4023 Enhancement: Update localheinz/composer-normalize (localheinz) +* minor #4049 use parent::offset*() methods when moving items around in insertAt() (staabm) + +Changelog for v2.12.3 +--------------------- + +* bug #3867 PhpdocAnnotationWithoutDotFixer - Handle trailing whitespaces (kubawerlos) +* bug #3884 NoSuperfluousPhpdocTagsFixer - handle null in every position (dmvdbrugge, julienfalque) +* bug #3885 AlignMultilineCommentFixer - ArrayIndentationFixer - Priority (dmvdbrugge) +* bug #3887 ArrayIndentFixer - Don't indent empty lines (dmvdbrugge) +* bug #3888 NoExtraBlankLinesFixer - remove blank lines after open tag (kubawerlos) +* bug #3890 StrictParamFixer - make it case-insensitive (kubawerlos) +* bug #3895 FunctionsAnalyzer - false positive for constant and function definition (kubawerlos) +* bug #3908 StrictParamFixer - fix edge case (kubawerlos) +* bug #3910 FunctionsAnalyzer - fix isGlobalFunctionCall (gharlan) +* bug #3912 FullyQualifiedStrictTypesFixer - NoSuperfluousPhpdocTagsFixer - adjust priority (dmvdbrugge) +* bug #3913 TokensAnalyzer - fix isConstantInvocation (gharlan, keradus) +* bug #3921 TypeAnalysis - Fix iterable not being detected as a reserved type (ntzm) +* bug #3924 FullyQualifiedStrictTypesFixer - space bug (dmvdbrugge) +* bug #3937 LowercaseStaticReferenceFixer - Fix "Parent" word in namespace (kubawerlos) +* bug #3944 ExplicitStringVariableFixer - fix array handling (gharlan) +* bug #3951 NoSuperfluousPhpdocTagsFixer - do not call strtolower with null (SpacePossum) +* bug #3954 NoSuperfluousPhpdocTagsFixer - Index invalid or out of range (kubawerlos) +* bug #3957 NoTrailingWhitespaceFixer - trim space after opening tag (kubawerlos) +* minor #3798 DX: enable native_function_invocation (keradus) +* minor #3882 PhpdocAnnotationWithoutDotFixer - Handle empty line in comment (kubawerlos) +* minor #3889 DX: Cleanup - remove unused variables (kubawerlos, SpacePossum) +* minor #3891 PhpdocNoEmptyReturnFixer - account for null[] (dmvdbrugge) +* minor #3892 PhpdocNoEmptyReturnFixer - fix docs (keradus) +* minor #3897 DX: FunctionsAnalyzer - simplifying return expression (kubawerlos) +* minor #3903 DX: cleanup - remove special treatment for PHP <5.6 (kubawerlos) +* minor #3905 DX: Upgrade composer-require-checker to stable version (keradus) +* minor #3919 Simplify single uses of Token::isGivenKind (ntzm) +* minor #3920 Docs: Fix typo (ntzm) +* minor #3940 DX: fix phpdoc parameter type (malukenho) +* minor #3948 DX: cleanup - remove redundant @param annotations (kubawerlos) +* minor #3950 Circle CI v2 yml (siad007) +* minor #3952 DX: AbstractFixerTestCase - drop testing method already provided by trait (keradus) +* minor #3973 Bump xdebug-handler (keradus) + Changelog for v2.12.2 --------------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a6f9612aca..4b4f5e3919d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ If you need any help, don't hesitate to ask the community on [Gitter](https://gi * [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 maintained branch where the changes can be applied. Once your Pull Request is accepted, the changes will get merged up to highest branches. + * 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`. diff --git a/LICENSE b/LICENSE index f372999822f..be8319f1e86 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2018 Fabien Potencier +Copyright (c) 2012-2019 Fabien Potencier Dariusz Rumiński Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.rst b/README.rst index f1906e4f019..131b36eca7a 100644 --- a/README.rst +++ b/README.rst @@ -40,19 +40,19 @@ your system: .. code-block:: bash - $ wget https://cs.sensiolabs.org/download/php-cs-fixer-v2.phar -O php-cs-fixer + $ wget https://cs.symfony.com/download/php-cs-fixer-v2.phar -O php-cs-fixer or with specified version: .. code-block:: bash - $ wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.12.2/php-cs-fixer.phar -O php-cs-fixer + $ wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.14.2/php-cs-fixer.phar -O php-cs-fixer or with curl: .. code-block:: bash - $ curl -L https://cs.sensiolabs.org/download/php-cs-fixer-v2.phar -o php-cs-fixer + $ curl -L https://cs.symfony.com/download/php-cs-fixer-v2.phar -o php-cs-fixer then: @@ -245,7 +245,7 @@ would be default in next MAJOR release (unified differ, dots, full-width progres Choose from the list of available rules: -* **align_multiline_comment** +* **align_multiline_comment** [@PhpCsFixer] Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. @@ -257,11 +257,11 @@ Choose from the list of available rules: whose lines all start with an asterisk (``phpdocs_like``) or any multi-line comment (``all_multiline``); defaults to ``'phpdocs_only'`` -* **array_indentation** +* **array_indentation** [@PhpCsFixer] Each element of an array must be indented exactly once. -* **array_syntax** +* **array_syntax** [@PhpCsFixer] PHP arrays should be declared using the configured syntax. @@ -274,7 +274,7 @@ Choose from the list of available rules: Converts backtick operators to ``shell_exec`` calls. -* **binary_operator_spaces** [@Symfony] +* **binary_operator_spaces** [@Symfony, @PhpCsFixer] Binary operators should be surrounded by space as configured. @@ -286,16 +286,16 @@ Choose from the list of available rules: - ``operators`` (``array``): dictionary of ``binary operator`` => ``fix strategy`` values that differ from the default strategy; defaults to ``[]`` -* **blank_line_after_namespace** [@PSR2, @Symfony] +* **blank_line_after_namespace** [@PSR2, @Symfony, @PhpCsFixer] There MUST be one blank line after the namespace declaration. -* **blank_line_after_opening_tag** [@Symfony] +* **blank_line_after_opening_tag** [@Symfony, @PhpCsFixer] Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line. -* **blank_line_before_statement** [@Symfony] +* **blank_line_before_statement** [@Symfony, @PhpCsFixer] An empty line feed must precede any configured statement. @@ -308,7 +308,7 @@ Choose from the list of available rules: must be preceded by an empty line; defaults to ``['break', 'continue', 'declare', 'return', 'throw', 'try']`` -* **braces** [@PSR2, @Symfony] +* **braces** [@PSR2, @Symfony, @PhpCsFixer] The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented. @@ -328,7 +328,7 @@ Choose from the list of available rules: classy constructs (non-anonymous classes, interfaces, traits, methods and non-lambda functions); defaults to ``'next'`` -* **cast_spaces** [@Symfony] +* **cast_spaces** [@Symfony, @PhpCsFixer] A single space or none should be between cast and variable. @@ -337,7 +337,7 @@ Choose from the list of available rules: - ``space`` (``'none'``, ``'single'``): spacing to apply between cast and variable; defaults to ``'single'`` -* **class_attributes_separation** [@Symfony] +* **class_attributes_separation** [@Symfony, @PhpCsFixer] Class, trait and interface elements must be separated with one blank line. @@ -348,7 +348,7 @@ Choose from the list of available rules: elements; 'const', 'method', 'property'; defaults to ``['const', 'method', 'property']`` -* **class_definition** [@PSR2, @Symfony] +* **class_definition** [@PSR2, @Symfony, @PhpCsFixer] Whitespace around the keywords of a class, trait or interfaces definition should be one space. @@ -368,26 +368,33 @@ Choose from the list of available rules: Converts ``::class`` keywords to FQCN strings. -* **combine_consecutive_issets** +* **combine_consecutive_issets** [@PhpCsFixer] Using ``isset($var) &&`` multiple times should be done in one call. -* **combine_consecutive_unsets** +* **combine_consecutive_unsets** [@PhpCsFixer] Calling ``unset`` on multiple items should be done in one call. -* **comment_to_phpdoc** +* **combine_nested_dirname** [@PHP70Migration:risky, @PHP71Migration:risky] + + Replace multiple nested calls of ``dirname`` by only one call with second + ``$level`` parameter. Requires PHP >= 7.0. + + *Risky rule: risky when the function ``dirname`` is overridden.* + +* **comment_to_phpdoc** [@PhpCsFixer:risky] Comments with annotation should be docblock when used on structural elements. *Risky rule: risky as new docblocks might mean more, e.g. a Doctrine entity might have a new column in database.* -* **compact_nullable_typehint** +* **compact_nullable_typehint** [@PhpCsFixer] Remove extra spaces in a nullable typehint. -* **concat_space** [@Symfony] +* **concat_space** [@Symfony, @PhpCsFixer] Concatenation should be spaced according configuration. @@ -402,7 +409,7 @@ Choose from the list of available rules: *Risky rule: risky when the code relies on modifying ``DateTime`` objects or if any of the ``date_create*`` functions are overridden.* -* **declare_equal_normalize** [@Symfony] +* **declare_equal_normalize** [@Symfony, @PhpCsFixer] Equal sign in declare statement should be surrounded by spaces or not following configuration. @@ -418,7 +425,7 @@ Choose from the list of available rules: *Risky rule: forcing strict types will stop non strict code from working.* -* **dir_constant** [@Symfony:risky] +* **dir_constant** [@Symfony:risky, @PhpCsFixer:risky] Replaces ``dirname(__FILE__)`` expression with equivalent ``__DIR__`` constant. @@ -555,22 +562,22 @@ Choose from the list of available rules: 'SuppressWarnings', 'noinspection', 'package_version', 'enduml', 'startuml', 'fix', 'FIXME', 'fixme', 'override']`` -* **elseif** [@PSR2, @Symfony] +* **elseif** [@PSR2, @Symfony, @PhpCsFixer] The keyword ``elseif`` should be used instead of ``else if`` so that all control keywords look like single words. -* **encoding** [@PSR1, @PSR2, @Symfony] +* **encoding** [@PSR1, @PSR2, @Symfony, @PhpCsFixer] PHP code MUST use only UTF-8 without BOM (remove BOM). -* **ereg_to_preg** [@Symfony:risky] +* **ereg_to_preg** [@Symfony:risky, @PhpCsFixer:risky] Replace deprecated ``ereg`` regular expression functions with ``preg``. *Risky rule: risky if the ``ereg`` function is overridden.* -* **error_suppression** [@Symfony:risky] +* **error_suppression** [@Symfony:risky, @PhpCsFixer:risky] Error control operator should be added to deprecation notices and/or removed from other cases. @@ -586,7 +593,7 @@ Choose from the list of available rules: - ``noise_remaining_usages_exclude`` (``array``): list of global functions to exclude from removing ``@``; defaults to ``[]`` -* **escape_implicit_backslashes** +* **escape_implicit_backslashes** [@PhpCsFixer] Escape implicit backslashes in strings and heredocs to ease the understanding of which are special chars interpreted by PHP and which @@ -600,17 +607,17 @@ Choose from the list of available rules: - ``single_quoted`` (``bool``): whether to fix single-quoted strings; defaults to ``false`` -* **explicit_indirect_variable** +* **explicit_indirect_variable** [@PhpCsFixer] Add curly braces to indirect variables to make them clear to understand. Requires PHP >= 7.0. -* **explicit_string_variable** +* **explicit_string_variable** [@PhpCsFixer] Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax. -* **final_internal_class** +* **final_internal_class** [@PhpCsFixer:risky] Internal classes should be ``final``. @@ -625,17 +632,35 @@ Choose from the list of available rules: set in order to fix the class. (case insensitive); defaults to ``['@internal']`` -* **full_opening_tag** [@PSR1, @PSR2, @Symfony] +* **fopen_flag_order** [@Symfony:risky, @PhpCsFixer:risky] + + Order the flags in ``fopen`` calls, ``b`` and ``t`` must be last. + + *Risky rule: risky when the function ``fopen`` is overridden.* + +* **fopen_flags** [@Symfony:risky, @PhpCsFixer:risky] + + The flags in ``fopen`` calls must omit ``t``, and ``b`` must be omitted or + included consistently. + + *Risky rule: risky when the function ``fopen`` is overridden.* + + Configuration options: + + - ``b_mode`` (``bool``): the ``b`` flag must be used (``true``) or omitted (``false``); + defaults to ``true`` + +* **full_opening_tag** [@PSR1, @PSR2, @Symfony, @PhpCsFixer] PHP code must use the long ``= 7.3. + +* **heredoc_to_nowdoc** [@PhpCsFixer] Convert ``heredoc`` to ``nowdoc`` where possible. -* **include** [@Symfony] +* **implode_call** [@Symfony:risky, @PhpCsFixer:risky] + + Function ``implode`` must be called with 2 arguments in the documented + order. + + *Risky rule: risky when the function ``implode`` is overridden.* + +* **include** [@Symfony, @PhpCsFixer] Include/Require and file path should be divided with a single space. File path should not be placed under brackets. -* **increment_style** [@Symfony] +* **increment_style** [@Symfony, @PhpCsFixer] Pre- or post-increment and decrement operators should be used if possible. @@ -703,17 +739,17 @@ Choose from the list of available rules: - ``style`` (``'post'``, ``'pre'``): whether to use pre- or post-increment and decrement operators; defaults to ``'pre'`` -* **indentation_type** [@PSR2, @Symfony] +* **indentation_type** [@PSR2, @Symfony, @PhpCsFixer] Code MUST use configured indentation type. -* **is_null** [@Symfony:risky] +* **is_null** [@Symfony:risky, @PhpCsFixer:risky] Replaces ``is_null($var)`` expression with ``null === $var``. *Risky rule: risky when the function ``is_null`` is overridden.* -* **line_ending** [@PSR2, @Symfony] +* **line_ending** [@PSR2, @Symfony, @PhpCsFixer] All PHP files must use same line ending. @@ -731,40 +767,44 @@ Choose from the list of available rules: - ``syntax`` (``'long'``, ``'short'``): whether to use the ``long`` or ``short`` ``list`` syntax; defaults to ``'long'`` -* **logical_operators** +* **logical_operators** [@PhpCsFixer:risky] Use ``&&`` and ``||`` logical operators instead of ``and`` and ``or``. *Risky rule: risky, because you must double-check if using and/or with lower precedence was intentional.* -* **lowercase_cast** [@Symfony] +* **lowercase_cast** [@Symfony, @PhpCsFixer] Cast should be written in lower case. -* **lowercase_constants** [@PSR2, @Symfony] +* **lowercase_constants** [@PSR2, @Symfony, @PhpCsFixer] The PHP constants ``true``, ``false``, and ``null`` MUST be in lower case. -* **lowercase_keywords** [@PSR2, @Symfony] +* **lowercase_keywords** [@PSR2, @Symfony, @PhpCsFixer] PHP keywords MUST be in lower case. -* **lowercase_static_reference** [@Symfony] +* **lowercase_static_reference** [@Symfony, @PhpCsFixer] Class static references ``self``, ``static`` and ``parent`` MUST be in lower case. -* **magic_constant_casing** [@Symfony] +* **magic_constant_casing** [@Symfony, @PhpCsFixer] Magic constants should be referred to using the correct casing. +* **magic_method_casing** [@Symfony, @PhpCsFixer] + + Magic method definitions and calls must be using the correct casing. + * **mb_str_functions** Replace non multibyte-safe functions with corresponding mb function. *Risky rule: risky when any of the functions are overridden.* -* **method_argument_space** [@PSR2, @Symfony] +* **method_argument_space** [@PSR2, @Symfony, @PhpCsFixer] In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists @@ -783,25 +823,25 @@ Choose from the list of available rules: defines how to handle function arguments lists that contain newlines; defaults to ``'ensure_fully_multiline'`` -* **method_chaining_indentation** +* **method_chaining_indentation** [@PhpCsFixer] Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported. -* **modernize_types_casting** [@Symfony:risky] +* **modernize_types_casting** [@Symfony:risky, @PhpCsFixer:risky] Replaces ``intval``, ``floatval``, ``doubleval``, ``strval`` and ``boolval`` function calls with according type casting operator. *Risky rule: risky if any of the functions ``intval``, ``floatval``, ``doubleval``, ``strval`` or ``boolval`` are overridden.* -* **multiline_comment_opening_closing** +* **multiline_comment_opening_closing** [@PhpCsFixer] DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash. -* **multiline_whitespace_before_semicolons** +* **multiline_whitespace_before_semicolons** [@PhpCsFixer] Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls. @@ -812,7 +852,7 @@ Choose from the list of available rules: multi-line whitespace or move the semicolon to the new line for chained calls; defaults to ``'no_multi_line'`` -* **native_constant_invocation** [@Symfony:risky] +* **native_constant_invocation** [@Symfony:risky, @PhpCsFixer:risky] Add leading ``\`` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for ``null``, @@ -828,12 +868,14 @@ Choose from the list of available rules: ``get_defined_constants``. User constants are not accounted in this list and must be specified in the include one; defaults to ``true`` - ``include`` (``array``): list of additional constants to fix; defaults to ``[]`` + - ``scope`` (``'all'``, ``'namespaced'``): only fix constant invocations that are made + within a namespace or fix all; defaults to ``'all'`` -* **native_function_casing** [@Symfony] +* **native_function_casing** [@Symfony, @PhpCsFixer] Function defined by PHP should be called using the correct casing. -* **native_function_invocation** [@Symfony:risky] +* **native_function_invocation** [@Symfony:risky, @PhpCsFixer:risky] Add leading ``\`` before function invocation to speed up resolving. @@ -848,12 +890,14 @@ Choose from the list of available rules: defaults to ``['@internal']`` - ``scope`` (``'all'``, ``'namespaced'``): only fix function calls that are made within a namespace or fix all; defaults to ``'all'`` + - ``strict`` (``bool``): whether leading ``\`` of function call not meant to have it + should be removed; defaults to ``false`` -* **new_with_braces** [@Symfony] +* **new_with_braces** [@Symfony, @PhpCsFixer] All instances created with new keyword must be followed by braces. -* **no_alias_functions** [@Symfony:risky] +* **no_alias_functions** [@Symfony:risky, @PhpCsFixer:risky] Master functions shall be used instead of aliases. @@ -866,19 +910,19 @@ Choose from the list of available rules: (IMAP functions), ``@mbreg`` (from ``ext-mbstring``) ``@all`` (all listed sets); defaults to ``['@internal', '@IMAP']`` -* **no_alternative_syntax** +* **no_alternative_syntax** [@PhpCsFixer] Replace control structure alternative syntax to use braces. -* **no_binary_string** +* **no_binary_string** [@PhpCsFixer] There should not be a binary flag before strings. -* **no_blank_lines_after_class_opening** [@Symfony] +* **no_blank_lines_after_class_opening** [@Symfony, @PhpCsFixer] There should be no empty lines after class opening brace. -* **no_blank_lines_after_phpdoc** [@Symfony] +* **no_blank_lines_after_phpdoc** [@Symfony, @PhpCsFixer] There should not be blank lines between docblock and the documented element. @@ -887,7 +931,7 @@ Choose from the list of available rules: There should be no blank lines before a namespace declaration. -* **no_break_comment** [@PSR2, @Symfony] +* **no_break_comment** [@PSR2, @Symfony, @PhpCsFixer] There must be a comment when fall-through is intentional in a non-empty case body. @@ -897,23 +941,23 @@ Choose from the list of available rules: - ``comment_text`` (``string``): the text to use in the added comment and to detect it; defaults to ``'no break'`` -* **no_closing_tag** [@PSR2, @Symfony] +* **no_closing_tag** [@PSR2, @Symfony, @PhpCsFixer] The closing ``?>`` tag MUST be omitted from files containing only PHP. -* **no_empty_comment** [@Symfony] +* **no_empty_comment** [@Symfony, @PhpCsFixer] There should not be any empty comments. -* **no_empty_phpdoc** [@Symfony] +* **no_empty_phpdoc** [@Symfony, @PhpCsFixer] There should not be empty PHPDoc blocks. -* **no_empty_statement** [@Symfony] +* **no_empty_statement** [@Symfony, @PhpCsFixer] Remove useless semicolon statements. -* **no_extra_blank_lines** [@Symfony] +* **no_extra_blank_lines** [@Symfony, @PhpCsFixer] Removes extra blank lines and/or blank lines following configuration. @@ -924,21 +968,21 @@ Choose from the list of available rules: 'square_brace_block', 'switch', 'throw', 'use', 'use_trait']``): list of tokens to fix; defaults to ``['extra']`` -* **no_homoglyph_names** [@Symfony:risky] +* **no_homoglyph_names** [@Symfony:risky, @PhpCsFixer:risky] Replace accidental usage of homoglyphs (non ascii characters) in names. *Risky rule: renames classes and cannot rename the files. You might have string references to renamed code (``$$name``).* -* **no_leading_import_slash** [@Symfony] +* **no_leading_import_slash** [@Symfony, @PhpCsFixer] Remove leading slashes in ``use`` clauses. -* **no_leading_namespace_whitespace** [@Symfony] +* **no_leading_namespace_whitespace** [@Symfony, @PhpCsFixer] The namespace declaration line shouldn't contain leading whitespace. -* **no_mixed_echo_print** [@Symfony] +* **no_mixed_echo_print** [@Symfony, @PhpCsFixer] Either language construct ``print`` or ``echo`` should be used. @@ -947,11 +991,11 @@ Choose from the list of available rules: - ``use`` (``'echo'``, ``'print'``): the desired language construct; defaults to ``'echo'`` -* **no_multiline_whitespace_around_double_arrow** [@Symfony] +* **no_multiline_whitespace_around_double_arrow** [@Symfony, @PhpCsFixer] Operator ``=>`` should not be surrounded by multi-line whitespaces. -* **no_null_property_initialization** +* **no_null_property_initialization** [@PhpCsFixer] Properties MUST not be explicitly initialized with ``null``. @@ -961,24 +1005,24 @@ Choose from the list of available rules: *Risky rule: risky when old style constructor being fixed is overridden or overrides parent one.* -* **no_short_bool_cast** [@Symfony] +* **no_short_bool_cast** [@Symfony, @PhpCsFixer] Short cast ``bool`` using double exclamation mark should not be used. -* **no_short_echo_tag** +* **no_short_echo_tag** [@PhpCsFixer] Replace short-echo ````. -* **ordered_class_elements** +* **ordered_class_elements** [@PhpCsFixer] Orders the elements of classes/interfaces/traits. @@ -1123,7 +1176,7 @@ Choose from the list of available rules: - ``sortAlgorithm`` (``'alpha'``, ``'none'``): how multiple occurrences of same type statements should be sorted; defaults to ``'none'`` -* **ordered_imports** +* **ordered_imports** [@PhpCsFixer] Ordering ``use`` statements. @@ -1135,7 +1188,7 @@ Choose from the list of available rules: should be sorted alphabetically or by length, or not sorted; defaults to ``'alpha'``; DEPRECATED alias: ``sortAlgorithm`` -* **php_unit_construct** [@Symfony:risky] +* **php_unit_construct** [@Symfony:risky, @PhpCsFixer:risky] PHPUnit assertion method calls like ``->assertSame(true, $foo)`` should be written with dedicated method like ``->assertTrue($foo)``. @@ -1173,11 +1226,11 @@ Choose from the list of available rules: - ``target`` (``'5.2'``, ``'5.6'``, ``'newest'``): target version of PHPUnit; defaults to ``'newest'`` -* **php_unit_fqcn_annotation** [@Symfony] +* **php_unit_fqcn_annotation** [@Symfony, @PhpCsFixer] PHPUnit annotations should be a FQCNs including a root namespace. -* **php_unit_internal_class** +* **php_unit_internal_class** [@PhpCsFixer] All PHPUnit test classes should be marked as internal. @@ -1186,6 +1239,16 @@ Choose from the list of available rules: - ``types`` (a subset of ``['normal', 'final', 'abstract']``): what types of classes to mark as internal; defaults to ``['normal', 'final']`` +* **php_unit_method_casing** [@PhpCsFixer] + + Enforce camel (or snake) case for PHPUnit test methods, following + configuration. + + Configuration options: + + - ``case`` (``'camel_case'``, ``'snake_case'``): apply camel or snake case to test + methods; defaults to ``'camel_case'`` + * **php_unit_mock** [@PHPUnit54Migration:risky, @PHPUnit55Migration:risky, @PHPUnit56Migration:risky, @PHPUnit57Migration:risky, @PHPUnit60Migration:risky] Usages of ``->getMock`` and @@ -1224,18 +1287,18 @@ Choose from the list of available rules: ``'newest'`` - ``use_class_const`` (``bool``): use ::class notation; defaults to ``true`` -* **php_unit_ordered_covers** +* **php_unit_ordered_covers** [@PhpCsFixer] Order ``@covers`` annotation of PHPUnit tests. -* **php_unit_set_up_tear_down_visibility** +* **php_unit_set_up_tear_down_visibility** [@PhpCsFixer:risky] Changes the visibility of the ``setUp()`` and ``tearDown()`` functions of PHPUnit to ``protected``, to match the PHPUnit TestCase. *Risky rule: this fixer may change functions named ``setUp()`` or ``tearDown()`` outside of PHPUnit tests, when a class is wrongly seen as a PHPUnit test.* -* **php_unit_strict** +* **php_unit_strict** [@PhpCsFixer:risky] PHPUnit methods like ``assertSame`` should be used instead of ``assertEquals``. @@ -1249,7 +1312,7 @@ Choose from the list of available rules: of assertion methods to fix; defaults to ``['assertAttributeEquals', 'assertAttributeNotEquals', 'assertEquals', 'assertNotEquals']`` -* **php_unit_test_annotation** +* **php_unit_test_annotation** [@PhpCsFixer:risky] Adds or removes @test annotations from tests, following configuration. @@ -1257,12 +1320,10 @@ Choose from the list of available rules: Configuration options: - - ``case`` (``'camel'``, ``'snake'``): whether to camel or snake case when adding the - test prefix; defaults to ``'camel'`` - ``style`` (``'annotation'``, ``'prefix'``): whether to use the @test annotation or not; defaults to ``'prefix'`` -* **php_unit_test_case_static_method_calls** +* **php_unit_test_case_static_method_calls** [@PhpCsFixer:risky] Calls to ``PHPUnit\Framework\TestCase`` static methods must all be of the same type, either ``$this->``, ``self::`` or ``static::``. @@ -1276,12 +1337,12 @@ Choose from the list of available rules: - ``methods`` (``array``): dictionary of ``method`` => ``call_type`` values that differ from the default strategy; defaults to ``[]`` -* **php_unit_test_class_requires_covers** +* **php_unit_test_class_requires_covers** [@PhpCsFixer] Adds a default ``@coversNothing`` annotation to PHPUnit test classes that have no ``@covers*`` annotation. -* **phpdoc_add_missing_param_annotation** +* **phpdoc_add_missing_param_annotation** [@PhpCsFixer] PHPDoc should contain ``@param`` for all params. @@ -1290,7 +1351,7 @@ Choose from the list of available rules: - ``only_untyped`` (``bool``): whether to add missing ``@param`` annotations for untyped parameters only; defaults to ``true`` -* **phpdoc_align** [@Symfony] +* **phpdoc_align** [@Symfony, @PhpCsFixer] All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically. @@ -1302,23 +1363,23 @@ Choose from the list of available rules: 'var', 'method']``): the tags that should be aligned; defaults to ``['method', 'param', 'property', 'return', 'throws', 'type', 'var']`` -* **phpdoc_annotation_without_dot** [@Symfony] +* **phpdoc_annotation_without_dot** [@Symfony, @PhpCsFixer] PHPDoc annotation descriptions should not be a sentence. -* **phpdoc_indent** [@Symfony] +* **phpdoc_indent** [@Symfony, @PhpCsFixer] Docblocks should have the same indentation as the documented subject. -* **phpdoc_inline_tag** [@Symfony] +* **phpdoc_inline_tag** [@Symfony, @PhpCsFixer] Fix PHPDoc inline tags, make ``@inheritdoc`` always inline. -* **phpdoc_no_access** [@Symfony] +* **phpdoc_no_access** [@Symfony, @PhpCsFixer] ``@access`` annotations should be omitted from PHPDoc. -* **phpdoc_no_alias_tag** [@Symfony] +* **phpdoc_no_alias_tag** [@Symfony, @PhpCsFixer] No alias PHPDoc tags should be used. @@ -1328,25 +1389,25 @@ Choose from the list of available rules: ones; defaults to ``['property-read' => 'property', 'property-write' => 'property', 'type' => 'var', 'link' => 'see']`` -* **phpdoc_no_empty_return** [@Symfony] +* **phpdoc_no_empty_return** [@Symfony, @PhpCsFixer] ``@return void`` and ``@return null`` annotations should be omitted from PHPDoc. -* **phpdoc_no_package** [@Symfony] +* **phpdoc_no_package** [@Symfony, @PhpCsFixer] ``@package`` and ``@subpackage`` annotations should be omitted from PHPDoc. -* **phpdoc_no_useless_inheritdoc** [@Symfony] +* **phpdoc_no_useless_inheritdoc** [@Symfony, @PhpCsFixer] Classy that does not inherit must not have ``@inheritdoc`` tags. -* **phpdoc_order** +* **phpdoc_order** [@PhpCsFixer] Annotations in PHPDoc should be ordered so that ``@param`` annotations come first, then ``@throws`` annotations, then ``@return`` annotations. -* **phpdoc_return_self_reference** [@Symfony] +* **phpdoc_return_self_reference** [@Symfony, @PhpCsFixer] The type of ``@return`` annotations of methods returning a reference to itself must the configured one. @@ -1358,7 +1419,7 @@ Choose from the list of available rules: 'self', '@self' => 'self', '$static' => 'static', '@static' => 'static']`` -* **phpdoc_scalar** [@Symfony] +* **phpdoc_scalar** [@Symfony, @PhpCsFixer] Scalar types should always be written in the same form. ``int`` not ``integer``, ``bool`` not ``boolean``, ``float`` not ``real`` or ``double``. @@ -1369,22 +1430,22 @@ Choose from the list of available rules: 'str']``): a map of types to fix; defaults to ``['boolean', 'double', 'integer', 'real', 'str']`` -* **phpdoc_separation** [@Symfony] +* **phpdoc_separation** [@Symfony, @PhpCsFixer] Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line. -* **phpdoc_single_line_var_spacing** [@Symfony] +* **phpdoc_single_line_var_spacing** [@Symfony, @PhpCsFixer] Single line ``@var`` PHPDoc should have proper spacing. -* **phpdoc_summary** [@Symfony] +* **phpdoc_summary** [@Symfony, @PhpCsFixer] PHPDoc summary should end in either a full stop, exclamation mark, or question mark. -* **phpdoc_to_comment** [@Symfony] +* **phpdoc_to_comment** [@Symfony, @PhpCsFixer] Docblocks should only be used on structural elements. @@ -1400,20 +1461,25 @@ Choose from the list of available rules: - ``scalar_types`` (``bool``): fix also scalar types; may have unexpected behaviour due to PHP bad type coercion system; defaults to ``true`` -* **phpdoc_trim** [@Symfony] +* **phpdoc_trim** [@Symfony, @PhpCsFixer] PHPDoc should start and end with content, excluding the very first and last line of the docblocks. -* **phpdoc_trim_consecutive_blank_line_separation** +* **phpdoc_trim_consecutive_blank_line_separation** [@PhpCsFixer] Removes extra blank lines after summary and after description in PHPDoc. -* **phpdoc_types** [@Symfony] +* **phpdoc_types** [@Symfony, @PhpCsFixer] The correct case must be used for standard PHP types in PHPDoc. -* **phpdoc_types_order** + Configuration options: + + - ``groups`` (a subset of ``['simple', 'alias', 'meta']``): type groups to fix; + defaults to ``['simple', 'alias', 'meta']`` + +* **phpdoc_types_order** [@Symfony, @PhpCsFixer] Sorts PHPDoc types. @@ -1425,7 +1491,12 @@ Choose from the list of available rules: - ``sort_algorithm`` (``'alpha'``, ``'none'``): the sorting algorithm to apply; defaults to ``'alpha'`` -* **phpdoc_var_without_name** [@Symfony] +* **phpdoc_var_annotation_correct_order** [@PhpCsFixer] + + ``@var`` and ``@type`` annotations must have type and name in the correct + order. + +* **phpdoc_var_without_name** [@Symfony, @PhpCsFixer] ``@var`` and ``@type`` annotations should not contain the variable name. @@ -1435,7 +1506,7 @@ Choose from the list of available rules: *Risky rule: risky when the function ``pow`` is overridden.* -* **protected_to_private** [@Symfony] +* **protected_to_private** [@Symfony, @PhpCsFixer] Converts ``protected`` variables and methods to ``private`` where possible. @@ -1451,7 +1522,7 @@ Choose from the list of available rules: - ``dir`` (``string``): the directory where the project code is placed; defaults to ``''`` -* **psr4** [@Symfony:risky] +* **psr4** [@Symfony:risky, @PhpCsFixer:risky] Class names should match the file name. @@ -1470,12 +1541,12 @@ Choose from the list of available rules: ones; defaults to ``['getrandmax' => 'mt_getrandmax', 'rand' => 'mt_rand', 'srand' => 'mt_srand']`` -* **return_assignment** +* **return_assignment** [@PhpCsFixer] Local, dynamic and directly referenced variables should not be assigned and directly returned by a function or method. -* **return_type_declaration** [@Symfony] +* **return_type_declaration** [@Symfony, @PhpCsFixer] There should be one or no space before colon, and one space after it in return type declarations, according to configuration. @@ -1485,24 +1556,24 @@ Choose from the list of available rules: - ``space_before`` (``'none'``, ``'one'``): spacing to apply before colon; defaults to ``'none'`` -* **self_accessor** [@Symfony:risky] +* **self_accessor** [@Symfony:risky, @PhpCsFixer:risky] Inside class or interface element ``self`` should be preferred to the class name itself. *Risky rule: risky when using dynamic calls like get_called_class() or late static binding.* -* **semicolon_after_instruction** [@Symfony] +* **semicolon_after_instruction** [@Symfony, @PhpCsFixer] Instructions must be terminated with a semicolon. -* **set_type_to_cast** [@Symfony:risky] +* **set_type_to_cast** [@Symfony:risky, @PhpCsFixer:risky] Cast shall be used, not ``settype``. *Risky rule: risky when the ``settype`` function is overridden or when used as the 2nd or 3rd expression in a ``for`` loop .* -* **short_scalar_cast** [@Symfony] +* **short_scalar_cast** [@Symfony, @PhpCsFixer] Cast ``(boolean)`` and ``(integer)`` should be written as ``(bool)`` and ``(int)``, ``(double)`` and ``(real)`` as ``(float)``, ``(binary)`` as @@ -1519,16 +1590,16 @@ Choose from the list of available rules: A return statement wishing to return ``void`` should not return ``null``. -* **single_blank_line_at_eof** [@PSR2, @Symfony] +* **single_blank_line_at_eof** [@PSR2, @Symfony, @PhpCsFixer] A PHP file without end tag must always end with a single empty line feed. -* **single_blank_line_before_namespace** [@Symfony] +* **single_blank_line_before_namespace** [@Symfony, @PhpCsFixer] There should be exactly one blank line before a namespace declaration. -* **single_class_element_per_statement** [@PSR2, @Symfony] +* **single_class_element_per_statement** [@PSR2, @Symfony, @PhpCsFixer] There MUST NOT be more than one property or constant declared per statement. @@ -1538,16 +1609,16 @@ Choose from the list of available rules: - ``elements`` (a subset of ``['const', 'property']``): list of strings which element should be modified; defaults to ``['const', 'property']`` -* **single_import_per_statement** [@PSR2, @Symfony] +* **single_import_per_statement** [@PSR2, @Symfony, @PhpCsFixer] There MUST be one use keyword per declaration. -* **single_line_after_imports** [@PSR2, @Symfony] +* **single_line_after_imports** [@PSR2, @Symfony, @PhpCsFixer] Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block. -* **single_line_comment_style** [@Symfony] +* **single_line_comment_style** [@Symfony, @PhpCsFixer] Single-line comments and multi-line comments with only one line of actual content should use the ``//`` syntax. @@ -1557,7 +1628,7 @@ Choose from the list of available rules: - ``comment_types`` (a subset of ``['asterisk', 'hash']``): list of comment types to fix; defaults to ``['asterisk', 'hash']`` -* **single_quote** [@Symfony] +* **single_quote** [@Symfony, @PhpCsFixer] Convert double quotes to single quotes for simple strings. @@ -1566,7 +1637,7 @@ Choose from the list of available rules: - ``strings_containing_single_quote_chars`` (``bool``): whether to fix double-quoted strings that contains single-quotes; defaults to ``false`` -* **space_after_semicolon** [@Symfony] +* **space_after_semicolon** [@Symfony, @PhpCsFixer] Fix whitespace after a semicolon. @@ -1575,11 +1646,11 @@ Choose from the list of available rules: - ``remove_in_empty_for_expressions`` (``bool``): whether spaces should be removed for empty ``for`` expressions; defaults to ``false`` -* **standardize_increment** [@Symfony] +* **standardize_increment** [@Symfony, @PhpCsFixer] Increment and decrement operators should be used if possible. -* **standardize_not_equals** [@Symfony] +* **standardize_not_equals** [@Symfony, @PhpCsFixer] Replace all ``<>`` with ``!=``. @@ -1589,33 +1660,33 @@ Choose from the list of available rules: *Risky rule: risky when using "->bindTo" on lambdas without referencing to ``$this``.* -* **strict_comparison** +* **strict_comparison** [@PhpCsFixer:risky] Comparisons should be strict. *Risky rule: changing comparisons to strict might change code behavior.* -* **strict_param** +* **strict_param** [@PhpCsFixer:risky] Functions should be used with ``$strict`` param set to ``true``. *Risky rule: risky when the fixed function is overridden or if the code relies on non-strict usage.* -* **string_line_ending** +* **string_line_ending** [@PhpCsFixer:risky] All multi-line strings must use correct line ending. *Risky rule: changing the line endings of multi-line strings might affect string comparisons and outputs.* -* **switch_case_semicolon_to_colon** [@PSR2, @Symfony] +* **switch_case_semicolon_to_colon** [@PSR2, @Symfony, @PhpCsFixer] A case should be followed by a colon and not a semicolon. -* **switch_case_space** [@PSR2, @Symfony] +* **switch_case_space** [@PSR2, @Symfony, @PhpCsFixer] Removes extra spaces between colon and case value. -* **ternary_operator_spaces** [@Symfony] +* **ternary_operator_spaces** [@Symfony, @PhpCsFixer] Standardize spaces around ternary operator. @@ -1623,20 +1694,20 @@ Choose from the list of available rules: Use ``null`` coalescing operator ``??`` where possible. Requires PHP >= 7.0. -* **trailing_comma_in_multiline_array** [@Symfony] +* **trailing_comma_in_multiline_array** [@Symfony, @PhpCsFixer] PHP multi-line arrays should have a trailing comma. -* **trim_array_spaces** [@Symfony] +* **trim_array_spaces** [@Symfony, @PhpCsFixer] Arrays should be formatted like function/method arguments, without leading or trailing single line space. -* **unary_operator_spaces** [@Symfony] +* **unary_operator_spaces** [@Symfony, @PhpCsFixer] Unary operators should be placed adjacent to their operands. -* **visibility_required** [@PSR2, @Symfony, @PHP71Migration] +* **visibility_required** [@PSR2, @Symfony, @PhpCsFixer, @PHP71Migration] Visibility MUST be declared on all properties and methods; ``abstract`` and ``final`` MUST be declared before the visibility; ``static`` MUST be @@ -1656,11 +1727,11 @@ Choose from the list of available rules: *Risky rule: modifies the signature of functions.* -* **whitespace_after_comma_in_array** [@Symfony] +* **whitespace_after_comma_in_array** [@Symfony, @PhpCsFixer] In array declaration, there MUST be a whitespace after each comma. -* **yoda_style** [@Symfony] +* **yoda_style** [@Symfony, @PhpCsFixer] Write conditions in Yoda style (``true``), non-Yoda style (``false``) or ignore those conditions (``null``) based on configuration. @@ -1689,7 +1760,7 @@ Config file Instead of using command line options to customize the rule, you can save the project configuration in a ``.php_cs.dist`` file in the root directory of your project. -The file must return an instance of `PhpCsFixer\\ConfigInterface `_ +The file must return an instance of `PhpCsFixer\\ConfigInterface `_ which lets you configure the rules, the files and directories that need to be analyzed. You may also create ``.php_cs`` file, which is the local configuration that will be used instead of the project configuration. It @@ -1833,6 +1904,7 @@ Dedicated plugins exist for: * `PhpStorm`_ * `Sublime Text`_ * `Vim`_ +* `VS Code`_ Contribute ---------- @@ -1854,10 +1926,11 @@ 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). -.. _php-cs-fixer.phar: https://cs.sensiolabs.org/download/php-cs-fixer-v2.phar +.. _php-cs-fixer.phar: https://cs.symfony.com/download/php-cs-fixer-v2.phar .. _Atom: https://github.com/Glavin001/atom-beautify .. _NetBeans: http://plugins.netbeans.org/plugin/49042/php-cs-fixer .. _PhpStorm: https://medium.com/@valeryan/how-to-configure-phpstorm-to-use-php-cs-fixer-1844991e521f .. _Sublime Text: https://github.com/benmatselby/sublime-phpcs .. _Vim: https://github.com/stephpy/vim-php-cs-fixer +.. _VS Code: https://github.com/junstyle/vscode-php-cs-fixer .. _contribute: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/CONTRIBUTING.md diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 5b7800569dd..00000000000 --- a/circle.yml +++ /dev/null @@ -1,25 +0,0 @@ -machine: - xcode: - version: 8.2 - -checkout: - pre: - - system_profiler SPSoftwareDataType - -dependencies: - cache_directories: - - ~/.composer - - ~/Library/Caches/Homebrew - pre: - - brew update - - brew install php72 - - echo "memory_limit = 512M" > $(brew --prefix)/etc/php/7.2/conf.d/memory.ini - - curl -sS https://getcomposer.org/installer | php - - php composer.phar global show hirak/prestissimo -q || php composer.phar global require --no-interaction --no-progress --optimize-autoloader hirak/prestissimo - override: - - php composer.phar install --optimize-autoloader --no-interaction --no-progress --no-suggest - -test: - override: - - vendor/bin/phpunit - - PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer --diff --dry-run -v fix diff --git a/composer.json b/composer.json index abf64a60df4..4a922eebdd3 100644 --- a/composer.json +++ b/composer.json @@ -14,14 +14,14 @@ } ], "require": { - "php": "^5.6 || >=7.0 <7.3", + "php": "^5.6 || ^7.0", "ext-json": "*", "ext-tokenizer": "*", "composer/semver": "^1.4", - "composer/xdebug-handler": "^1.0", + "composer/xdebug-handler": "^1.2", "doctrine/annotations": "^1.2", "php-cs-fixer/diff": "^2.0", - "symfony/console": "^3.2 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6", "symfony/event-dispatcher": "^3.0 || ^4.0", "symfony/filesystem": "^3.0 || ^4.0", "symfony/finder": "^3.0 || ^4.0", @@ -31,13 +31,10 @@ "symfony/process": "^3.0 || ^4.0", "symfony/stopwatch": "^3.0 || ^4.0" }, - "conflict": { - "hhvm": "*" - }, "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.1", + "keradus/cli-executor": "^1.2", "mikey179/vfsStream": "^1.6", "php-coveralls/php-coveralls": "^2.1", "php-cs-fixer/accessible-object": "^1.0", diff --git a/dev-tools/composer.json b/dev-tools/composer.json index a53dc150839..cc14e2ef8d6 100644 --- a/dev-tools/composer.json +++ b/dev-tools/composer.json @@ -3,7 +3,7 @@ "php": "^7.1" }, "require-dev": { - "localheinz/composer-normalize": "^0.6.0", + "localheinz/composer-normalize": "^1.0.0", "maglnet/composer-require-checker": "^1.0", "mi-schi/phpmd-extension": "^4.2", "phpmd/phpmd": "^2.4.3" diff --git a/dev-tools/info-extractor.php b/dev-tools/info-extractor.php new file mode 100644 index 00000000000..c8ec7d1b186 --- /dev/null +++ b/dev-tools/info-extractor.php @@ -0,0 +1,14 @@ +#!/usr/bin/env php + PhpCsFixer\Console\Application::VERSION, + 'vnumber' => 'v'.PhpCsFixer\Console\Application::VERSION, + 'codename' => PhpCsFixer\Console\Application::VERSION_CODENAME, +]; + +echo json_encode([ + 'version' => $version, +], JSON_PRETTY_PRINT); diff --git a/doc/COOKBOOK-FIXERS.md b/doc/COOKBOOK-FIXERS.md index 540339572bb..1728c827570 100644 --- a/doc/COOKBOOK-FIXERS.md +++ b/doc/COOKBOOK-FIXERS.md @@ -49,9 +49,10 @@ Put this content inside: + * Dariusz Rumiński * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. @@ -70,17 +71,17 @@ final class RemoveCommentsFixer extends AbstractFixer /** * {@inheritdoc} */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens) + public function getDefinition() { - // Add the fixing logic of the fixer here. + // Return a definition of the fixer, it will be used in the README.rst. } /** * {@inheritdoc} */ - public function getDefinition() + protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - // Return a definition of the fixer, it will be used in the README.rst. + // Add the fixing logic of the fixer here. } } ``` @@ -97,9 +98,10 @@ Now let us create the test file at + * Dariusz Rumiński * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. @@ -113,10 +115,15 @@ use PhpCsFixer\Tests\Test\AbstractFixerTestCase; * @author Your name * * @internal + * + * @covers \PhpCsFixer\Fixer\Comment\RemoveCommentsFixer */ final class RemoveCommentsFixerTest extends AbstractFixerTestCase { /** + * @param string $expected + * @param null|string $input + * * @dataProvider provideFixCases */ public function testFix($expected, $input = null) @@ -126,7 +133,7 @@ final class RemoveCommentsFixerTest extends AbstractFixerTestCase public function provideFixCases() { - return array(); + return []; } } ``` @@ -150,9 +157,9 @@ fixer does not change what is not supposed to change. Thus: ... public function provideFixCases() { - return array( - array(' + * Dariusz Rumiński * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. @@ -202,6 +210,9 @@ use PhpCsFixer\Tests\Fixer\AbstractFixerTestBase; final class RemoveCommentsFixerTest extends AbstractFixerTestBase { /** + * @param string $expected + * @param null|string $input + * * @dataProvider provideFixCases */ public function testFix($expected, $input = null) @@ -211,12 +222,12 @@ final class RemoveCommentsFixerTest extends AbstractFixerTestBase public function provideFixCases() { - return array( - array( + return [ + [ ' $token){ + foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_COMMENT)) { continue; } @@ -333,7 +348,6 @@ final class RemoveCommentsFixer extends AbstractFixer // need to figure out what to do here! } } - ... } ``` @@ -343,12 +357,14 @@ token is a semicolon. ```php final class RemoveCommentsFixer extends AbstractFixer { + ... + /** * {@inheritdoc} */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - foreach($tokens as $index => $token){ + foreach ($tokens as $index => $token) { if (!$token->isGivenKind(T_COMMENT)) { continue; } @@ -356,12 +372,11 @@ final class RemoveCommentsFixer extends AbstractFixer $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); $prevToken = $tokens[$prevTokenIndex]; - if($prevToken->equals(';')){ + if ($prevToken->equals(';')) { $tokens->clearAt($index); } } } - ... } ``` @@ -370,13 +385,13 @@ So the fixer in the end looks like this: + * 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\Comment; @@ -387,25 +402,8 @@ use PhpCsFixer\Tokenizer\Tokens; /** * @author Your name */ -final class RemoveCommentsFixer extends AbstractFixer { - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - foreach($tokens as $index => $token){ - if (!$token->isGivenKind(T_COMMENT)) { - continue; - } - - $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); - $prevToken = $tokens[$prevTokenIndex]; - - if ($prevToken->equals(';')) { - $tokens->clearAt($index); - } - } - } - +final class RemoveCommentsFixer extends AbstractFixer +{ /** * {@inheritdoc} */ @@ -428,6 +426,24 @@ final class RemoveCommentsFixer extends AbstractFixer { { return $tokens->isTokenKindFound(T_COMMENT); } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) { + foreach($tokens as $index => $token){ + if (!$token->isGivenKind(T_COMMENT)) { + continue; + } + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + $prevToken = $tokens[$prevTokenIndex]; + + if ($prevToken->equals(';')) { + $tokens->clearAt($index); + } + } + } } ``` diff --git a/php-cs-fixer b/php-cs-fixer index aa918247621..cc0cc374ab6 100755 --- a/php-cs-fixer +++ b/php-cs-fixer @@ -2,7 +2,7 @@ * Dariusz Rumiński @@ -15,7 +15,6 @@ * @author Fabien Potencier * @author Dariusz Rumiński */ - error_reporting(-1); if (defined('HHVM_VERSION_ID')) { @@ -26,8 +25,8 @@ if (defined('HHVM_VERSION_ID')) { } else { exit(1); } -} elseif (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600 || PHP_VERSION_ID >= 70300) { - fwrite(STDERR, "PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.2.*.\n"); +} elseif (!defined('PHP_VERSION_ID') || \PHP_VERSION_ID < 50600 || \PHP_VERSION_ID >= 70400) { + fwrite(STDERR, "PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.3.*.\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"); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 03c34e822cd..c20e2dfe637 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -53,6 +53,6 @@ - + diff --git a/src/AbstractDoctrineAnnotationFixer.php b/src/AbstractDoctrineAnnotationFixer.php index 534041f1fe8..99f8a5108ca 100644 --- a/src/AbstractDoctrineAnnotationFixer.php +++ b/src/AbstractDoctrineAnnotationFixer.php @@ -79,7 +79,7 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['array']) ->setAllowedValues([static function ($values) { foreach ($values as $value) { - if (!is_string($value)) { + if (!\is_string($value)) { return false; } } diff --git a/src/AbstractFixer.php b/src/AbstractFixer.php index 7239bedafd1..f87003a64ad 100644 --- a/src/AbstractFixer.php +++ b/src/AbstractFixer.php @@ -88,7 +88,7 @@ public function isRisky() public function getName() { $nameParts = explode('\\', static::class); - $name = substr(end($nameParts), 0, -strlen('Fixer')); + $name = substr(end($nameParts), 0, -\strlen('Fixer')); return Utils::camelCaseToUnderscore($name); } @@ -121,7 +121,7 @@ public function configure(array $configuration) } $name = $option->getName(); - if (array_key_exists($name, $configuration)) { + if (\array_key_exists($name, $configuration)) { $message = sprintf( 'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s', $name, diff --git a/src/AbstractFopenFlagFixer.php b/src/AbstractFopenFlagFixer.php new file mode 100644 index 00000000000..363a76f07a3 --- /dev/null +++ b/src/AbstractFopenFlagFixer.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; + +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @author SpacePossum + */ +abstract class AbstractFopenFlagFixer extends AbstractFunctionReferenceFixer +{ + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $index = 0; + $end = $tokens->count() - 1; + while (true) { + $candidate = $this->find('fopen', $tokens, $index, $end); + + if (null === $candidate) { + break; + } + + $index = $candidate[1]; // proceed to '(' of `fopen` + + // fetch arguments + $arguments = $argumentsAnalyzer->getArguments( + $tokens, + $index, + $candidate[2] + ); + + $argumentsCount = \count($arguments); // argument count sanity check + + if ($argumentsCount < 2 || $argumentsCount > 4) { + continue; + } + + $argumentStartIndex = array_keys($arguments)[1]; // get second argument index + + $this->fixFopenFlagToken( + $tokens, + $argumentStartIndex, + $arguments[$argumentStartIndex] + ); + } + } + + abstract protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex); + + /** + * @param string $mode + * + * @return bool + */ + protected function isValidModeString($mode) + { + $modeLength = \strlen($mode); + if ($modeLength < 1 || $modeLength > 13) { // 13 === length 'r+w+a+x+c+etb' + return false; + } + + $validFlags = [ + 'a' => true, + 'b' => true, + 'c' => true, + 'e' => true, + 'r' => true, + 't' => true, + 'w' => true, + 'x' => true, + ]; + + if (!isset($validFlags[$mode[0]])) { + return false; + } + + unset($validFlags[$mode[0]]); + + for ($i = 1; $i < $modeLength; ++$i) { + if (isset($validFlags[$mode[$i]])) { + unset($validFlags[$mode[$i]]); + + continue; + } + + if ('+' !== $mode[$i] + || ( + 'a' !== $mode[$i - 1] // 'a+','c+','r+','w+','x+' + && 'c' !== $mode[$i - 1] + && 'r' !== $mode[$i - 1] + && 'w' !== $mode[$i - 1] + && 'x' !== $mode[$i - 1] + ) + ) { + return false; + } + } + + return true; + } +} diff --git a/src/AbstractFunctionReferenceFixer.php b/src/AbstractFunctionReferenceFixer.php index 142f5f22bdc..21da7cd34c8 100644 --- a/src/AbstractFunctionReferenceFixer.php +++ b/src/AbstractFunctionReferenceFixer.php @@ -12,7 +12,7 @@ namespace PhpCsFixer; -use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Tokens; /** @@ -57,28 +57,12 @@ protected function find($functionNameToSearch, Tokens $tokens, $start = 0, $end // translate results for humans list($functionName, $openParenthesis) = array_keys($matches); - // first criteria check: shall look like function call - $functionNamePrefix = $tokens->getPrevMeaningfulToken($functionName); - $functionNamePrecedingToken = $tokens[$functionNamePrefix]; - if ($functionNamePrecedingToken->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) { - // this expression is differs from expected, resume - return $this->find($functionNameToSearch, $tokens, $openParenthesis, $end); - } + $functionsAnalyzer = new FunctionsAnalyzer(); - // second criteria check: ensure namespace is the root one - if ($functionNamePrecedingToken->isGivenKind(T_NS_SEPARATOR)) { - $namespaceCandidate = $tokens->getPrevMeaningfulToken($functionNamePrefix); - $namespaceCandidateToken = $tokens[$namespaceCandidate]; - if ($namespaceCandidateToken->isGivenKind([T_NEW, T_STRING, CT::T_NAMESPACE_OPERATOR])) { - // here can be added complete namespace scan - // this expression is differs from expected, resume - return $this->find($functionNameToSearch, $tokens, $openParenthesis, $end); - } + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $functionName)) { + return $this->find($functionNameToSearch, $tokens, $openParenthesis, $end); } - // final step: find closing parenthesis - $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); - - return [$functionName, $openParenthesis, $closeParenthesis]; + return [$functionName, $openParenthesis, $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis)]; } } diff --git a/src/AbstractLinesBeforeNamespaceFixer.php b/src/AbstractLinesBeforeNamespaceFixer.php index 2f90e36495c..b921f4285ce 100644 --- a/src/AbstractLinesBeforeNamespaceFixer.php +++ b/src/AbstractLinesBeforeNamespaceFixer.php @@ -99,13 +99,12 @@ protected function fixLinesBeforeNamespace(Tokens $tokens, $index, $expectedMin, return; } - $newWhitespaceToken = new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)]); if ($previous->isWhitespace()) { // Fix the previous whitespace token - $tokens[$previousIndex] = $newWhitespaceToken; + $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, $newWhitespaceToken); + $tokens->insertAt($index, new Token([T_WHITESPACE, str_repeat($lineEnding, $newlinesForWhitespaceToken)])); } } } diff --git a/src/AbstractProxyFixer.php b/src/AbstractProxyFixer.php index 3a4566dbe7b..b17336120bc 100644 --- a/src/AbstractProxyFixer.php +++ b/src/AbstractProxyFixer.php @@ -70,7 +70,7 @@ public function isRisky() */ public function getPriority() { - if (count($this->proxyFixers) > 1) { + if (\count($this->proxyFixers) > 1) { throw new \LogicException('You need to override this method to provide the priority of combined fixers.'); } diff --git a/src/Cache/Cache.php b/src/Cache/Cache.php index 2e608ed8a26..2f5cb62332d 100644 --- a/src/Cache/Cache.php +++ b/src/Cache/Cache.php @@ -41,7 +41,7 @@ public function getSignature() public function has($file) { - return array_key_exists($file, $this->hashes); + return \array_key_exists($file, $this->hashes); } public function get($file) @@ -55,10 +55,10 @@ public function get($file) public function set($file, $hash) { - if (!is_int($hash)) { + if (!\is_int($hash)) { throw new \InvalidArgumentException(sprintf( 'Value needs to be an integer, got "%s".', - is_object($hash) ? get_class($hash) : gettype($hash) + \is_object($hash) ? \get_class($hash) : \gettype($hash) )); } @@ -103,7 +103,7 @@ public static function fromJson($json) if (null === $data && JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException(sprintf( 'Value needs to be a valid JSON string, got "%s", error: "%s".', - is_object($json) ? get_class($json) : gettype($json), + \is_object($json) ? \get_class($json) : \gettype($json), json_last_error_msg() )); } @@ -117,7 +117,7 @@ public static function fromJson($json) $missingKeys = array_diff_key(array_flip($requiredKeys), $data); - if (count($missingKeys)) { + if (\count($missingKeys)) { throw new \InvalidArgumentException(sprintf( 'JSON data is missing keys "%s"', implode('", "', $missingKeys) diff --git a/src/Cache/Directory.php b/src/Cache/Directory.php index 846f6b281a7..8c4e7719fbe 100644 --- a/src/Cache/Directory.php +++ b/src/Cache/Directory.php @@ -43,7 +43,7 @@ public function getRelativePathTo($file) return $file; } - return substr($file, strlen($this->directoryName) + 1); + return substr($file, \strlen($this->directoryName) + 1); } private function normalizePath($path) diff --git a/src/Cache/Signature.php b/src/Cache/Signature.php index b0313b6b9ea..2407f548e17 100644 --- a/src/Cache/Signature.php +++ b/src/Cache/Signature.php @@ -70,12 +70,12 @@ public function equals(SignatureInterface $signature) private static function utf8Encode(array $data) { - if (!function_exists('mb_detect_encoding')) { + if (!\function_exists('mb_detect_encoding')) { return $data; } array_walk_recursive($data, static function (&$item) { - if (is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { + if (\is_string($item) && !mb_detect_encoding($item, 'utf-8', true)) { $item = utf8_encode($item); } }); diff --git a/src/Config.php b/src/Config.php index 576a2f051ad..d5d7cc8d16a 100644 --- a/src/Config.php +++ b/src/Config.php @@ -152,10 +152,10 @@ public function getUsingCache() */ public function registerCustomFixers($fixers) { - if (false === is_array($fixers) && false === $fixers instanceof \Traversable) { + if (false === \is_array($fixers) && false === $fixers instanceof \Traversable) { throw new \InvalidArgumentException(sprintf( 'Argument must be an array or a Traversable, got "%s".', - is_object($fixers) ? get_class($fixers) : gettype($fixers) + \is_object($fixers) ? \get_class($fixers) : \gettype($fixers) )); } @@ -181,10 +181,10 @@ public function setCacheFile($cacheFile) */ public function setFinder($finder) { - if (false === is_array($finder) && false === $finder instanceof \Traversable) { + if (false === \is_array($finder) && false === $finder instanceof \Traversable) { throw new \InvalidArgumentException(sprintf( 'Argument must be an array or a Traversable, got "%s".', - is_object($finder) ? get_class($finder) : gettype($finder) + \is_object($finder) ? \get_class($finder) : \gettype($finder) )); } diff --git a/src/Console/Command/DescribeCommand.php b/src/Console/Command/DescribeCommand.php index 64fee4afdfd..4ba627952f9 100644 --- a/src/Console/Command/DescribeCommand.php +++ b/src/Console/Command/DescribeCommand.php @@ -154,7 +154,7 @@ private function describeRule(OutputInterface $output, $name) $output->writeln(sprintf('Description of %s rule.', $name)); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { - $output->writeln(sprintf('Fixer class: %s.', get_class($fixer))); + $output->writeln(sprintf('Fixer class: %s.', \get_class($fixer))); } $output->writeln($description); @@ -177,7 +177,7 @@ private function describeRule(OutputInterface $output, $name) $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()).''; @@ -243,7 +243,7 @@ function ($type) { return true; }); - if (!count($codeSamples)) { + if (!\count($codeSamples)) { $output->writeln([ 'Fixing examples can not be demonstrated on the current PHP version.', '', @@ -297,7 +297,7 @@ function ($type) { */ private function describeSet(OutputInterface $output, $name) { - if (!in_array($name, $this->getSetNames(), true)) { + if (!\in_array($name, $this->getSetNames(), true)) { throw new DescribeNameNotFoundException($name, 'set'); } @@ -384,7 +384,7 @@ private function describeList(OutputInterface $output, $type) foreach ($describe as $list => $items) { $output->writeln(sprintf('Defined %s:', $list)); foreach ($items as $name => $item) { - $output->writeln(sprintf('* %s', is_string($name) ? $name : $item)); + $output->writeln(sprintf('* %s', \is_string($name) ? $name : $item)); } } } diff --git a/src/Console/Command/FixCommand.php b/src/Console/Command/FixCommand.php index f82dd206c4e..673cd236940 100644 --- a/src/Console/Command/FixCommand.php +++ b/src/Console/Command/FixCommand.php @@ -188,7 +188,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $stdErr, $this->eventDispatcher, (new Terminal())->getWidth(), - count($finder) + \count($finder) ); } @@ -234,15 +234,15 @@ protected function execute(InputInterface $input, OutputInterface $output) if (null !== $stdErr) { $errorOutput = new ErrorOutput($stdErr); - if (count($invalidErrors) > 0) { + if (\count($invalidErrors) > 0) { $errorOutput->listErrors('linting before fixing', $invalidErrors); } - if (count($exceptionErrors) > 0) { + if (\count($exceptionErrors) > 0) { $errorOutput->listErrors('fixing', $exceptionErrors); } - if (count($lintErrors) > 0) { + if (\count($lintErrors) > 0) { $errorOutput->listErrors('linting after fixing', $lintErrors); } } @@ -251,9 +251,9 @@ protected function execute(InputInterface $input, OutputInterface $output) return $exitStatusCalculator->calculate( $resolver->isDryRun(), - count($changed) > 0, - count($invalidErrors) > 0, - count($exceptionErrors) > 0 + \count($changed) > 0, + \count($invalidErrors) > 0, + \count($exceptionErrors) > 0 ); } } diff --git a/src/Console/Command/HelpCommand.php b/src/Console/Command/HelpCommand.php index 93afac28f3c..42dfbefa33b 100644 --- a/src/Console/Command/HelpCommand.php +++ b/src/Console/Command/HelpCommand.php @@ -279,7 +279,7 @@ public static function getHelpCopy() ), '%%%CI_INTEGRATION%%%' => implode("\n", array_map( static function ($line) { return ' $ '.$line; }, - array_slice(file(__DIR__.'/../../../dev-tools/ci-integration.sh', FILE_IGNORE_NEW_LINES), 3) + \array_slice(file(__DIR__.'/../../../dev-tools/ci-integration.sh', FILE_IGNORE_NEW_LINES), 3) )), '%%%FIXERS_DETAILS%%%' => self::getFixersHelp(), ]); @@ -292,7 +292,7 @@ static function ($line) { return ' $ '.$line; }, */ public static function toString($value) { - if (is_array($value)) { + if (\is_array($value)) { // Output modifications: // - remove new-lines // - combine multiple whitespaces @@ -357,7 +357,7 @@ public static function getDisplayableAllowedValues(FixerOptionInterface $option) ); }); - if (0 === count($allowed)) { + if (0 === \count($allowed)) { $allowed = null; } } @@ -463,7 +463,7 @@ static function (FixerInterface $a, FixerInterface $b) { return $sets; }; - $count = count($fixers) - 1; + $count = \count($fixers) - 1; foreach ($fixers as $i => $fixer) { $sets = $getSetsWithRule($fixer->getName()); $description = $fixer->getDefinition()->getSummary(); @@ -501,7 +501,7 @@ static function (FixerInterface $a, FixerInterface $b) { if ($fixer instanceof ConfigurableFixerInterface) { $configurationDefinition = $fixer->getConfigurationDefinition(); $configurationDefinitionOptions = $configurationDefinition->getOptions(); - if (count($configurationDefinitionOptions)) { + if (\count($configurationDefinitionOptions)) { $help .= " |\n | Configuration options:\n"; usort( @@ -591,7 +591,7 @@ private static function wordwrap($string, $width) $currentLine = 0; $lineLength = 0; foreach (explode(' ', $string) as $word) { - $wordLength = strlen(Preg::replace('~~', '', $word)); + $wordLength = \strlen(Preg::replace('~~', '', $word)); if (0 !== $lineLength) { ++$wordLength; // space before word } diff --git a/src/Console/Command/ReadmeCommand.php b/src/Console/Command/ReadmeCommand.php index 36528265703..4e27b3d3af9 100644 --- a/src/Console/Command/ReadmeCommand.php +++ b/src/Console/Command/ReadmeCommand.php @@ -203,6 +203,7 @@ protected function execute(InputInterface $input, OutputInterface $output) * `PhpStorm`_ * `Sublime Text`_ * `Vim`_ +* `VS Code`_ Contribute ---------- @@ -230,6 +231,7 @@ protected function execute(InputInterface $input, OutputInterface $output) .. _PhpStorm: https://medium.com/@valeryan/how-to-configure-phpstorm-to-use-php-cs-fixer-1844991e521f .. _Sublime Text: https://github.com/benmatselby/sublime-phpcs .. _Vim: https://github.com/stephpy/vim-php-cs-fixer +.. _VS Code: https://github.com/junstyle/vscode-php-cs-fixer .. _contribute: https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/CONTRIBUTING.md EOF; @@ -269,7 +271,7 @@ static function ($matches) { // `description `_ $help = Preg::replaceCallback( - '#`(.+)`\s?\((.+)<\/url>\)#', + '#`(.+)`\s?\((.+)<\/url>\)#', static function (array $matches) { return sprintf('`%s <%s>`_', str_replace('\\', '\\\\', $matches[1]), $matches[2]); }, @@ -280,7 +282,7 @@ static function (array $matches) { $help = Preg::replace('#\*\* +\[#', '** [', $help); $downloadLatestUrl = sprintf('https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v%s/php-cs-fixer.phar', HelpCommand::getLatestReleaseVersionFromChangeLog()); - $downloadUrl = 'https://cs.sensiolabs.org/download/php-cs-fixer-v2.phar'; + $downloadUrl = 'https://cs.symfony.com/download/php-cs-fixer-v2.phar'; $header = str_replace('%download.version_url%', $downloadLatestUrl, $header); $header = str_replace('%download.url%', $downloadUrl, $header); diff --git a/src/Console/Command/SelfUpdateCommand.php b/src/Console/Command/SelfUpdateCommand.php index 0becd316244..9ae53d3b10a 100644 --- a/src/Console/Command/SelfUpdateCommand.php +++ b/src/Console/Command/SelfUpdateCommand.php @@ -149,7 +149,7 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - $tempFilename = dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar'; + $tempFilename = \dirname($localFilename).'/'.basename($localFilename, '.phar').'-tmp.phar'; $remoteFilename = $this->toolInfo->getPharDownloadUri($remoteTag); if (false === @copy($remoteFilename, $tempFilename)) { diff --git a/src/Console/ConfigurationResolver.php b/src/Console/ConfigurationResolver.php index 246350d2791..ec687289d41 100644 --- a/src/Console/ConfigurationResolver.php +++ b/src/Console/ConfigurationResolver.php @@ -229,7 +229,7 @@ public function getConfig() // 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".', $configFile, 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".', $configFile, \is_object($config) ? \get_class($config) : \gettype($config))); } $this->config = $config; @@ -303,7 +303,7 @@ public function getDirectory() ? $path : $this->cwd.\DIRECTORY_SEPARATOR.$path; - $this->directory = new Directory(dirname($absolutePath)); + $this->directory = new Directory(\dirname($absolutePath)); } return $this->directory; @@ -318,7 +318,8 @@ public function getFixers() $this->fixers = $this->createFixerFactory() ->useRuleSet($this->getRuleSet()) ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding())) - ->getFixers(); + ->getFixers() + ; if (false === $this->getRiskyAllowed()) { $riskyFixers = array_map( @@ -333,8 +334,8 @@ static function (FixerInterface $fixer) { ) ); - if (count($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 option?', implode(', ', $riskyFixers))); + if (\count($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?', implode(', ', $riskyFixers))); } } } @@ -365,7 +366,7 @@ public function getPath() $filesystem = new Filesystem(); $cwd = $this->cwd; - if (1 === count($this->options['path']) && '-' === $this->options['path'][0]) { + if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) { $this->path = $this->options['path']; } else { $this->path = array_map( @@ -405,7 +406,7 @@ public function getProgress() if (null === $progressType) { $progressType = $this->getConfig()->getHideProgress() ? 'none' : 'dots'; - } elseif (!in_array($progressType, $progressTypes, true)) { + } elseif (!\in_array($progressType, $progressTypes, true)) { throw new InvalidConfigurationException(sprintf( 'The progress type "%s" is not defined, supported are "%s".', $progressType, @@ -552,9 +553,9 @@ private function computeConfigFiles() $path = $this->getPath(); - if ($this->isStdIn() || 0 === count($path)) { + if ($this->isStdIn() || 0 === \count($path)) { $configDir = $this->cwd; - } elseif (1 < count($path)) { + } elseif (1 < \count($path)) { throw new InvalidConfigurationException('For multiple paths config parameter is required.'); } elseif (is_file($path[0]) && $dirName = pathinfo($path[0], PATHINFO_DIRNAME)) { $configDir = $dirName; @@ -623,7 +624,7 @@ private function getRuleSet() private function isStdIn() { if (null === $this->isStdIn) { - $this->isStdIn = 1 === count($this->options['path']) && '-' === $this->options['path'][0]; + $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0]; } return $this->isStdIn; @@ -636,7 +637,7 @@ private function isStdIn() */ private function iterableToTraversable($iterable) { - return is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; + return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable; } /** @@ -696,7 +697,7 @@ private function validateRules(array $rules) */ $ruleSet = []; foreach ($rules as $key => $value) { - if (is_int($key)) { + if (\is_int($key)) { throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value)); } @@ -719,7 +720,7 @@ private function validateRules(array $rules) $availableFixers ); - if (count($unknownFixers)) { + if (\count($unknownFixers)) { $matcher = new WordMatcher($availableFixers); $message = 'The rules contain unknown fixers: '; @@ -767,7 +768,7 @@ private function resolveFinder() $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION]; - if (!in_array( + if (!\in_array( $this->options['path-mode'], $modes, true @@ -788,7 +789,7 @@ static function ($path) { $this->getPath() )); - if (!count($paths)) { + if (!\count($paths)) { if ($isIntersectionPathMode) { return new \ArrayIterator([]); } @@ -829,7 +830,7 @@ static function ($path) { static function (\SplFileInfo $current) use ($pathsByType) { $currentRealPath = $current->getRealPath(); - if (in_array($currentRealPath, $pathsByType['file'], true)) { + if (\in_array($currentRealPath, $pathsByType['file'], true)) { return true; } @@ -864,7 +865,7 @@ static function (\SplFileInfo $current) use ($pathsByType) { */ private function setOption($name, $value) { - if (!array_key_exists($name, $this->options)) { + if (!\array_key_exists($name, $this->options)) { throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name)); } @@ -879,11 +880,11 @@ private function setOption($name, $value) private function resolveOptionBooleanValue($optionName) { $value = $this->options[$optionName]; - if (is_bool($value)) { + if (\is_bool($value)) { return $value; } - if (!is_string($value)) { + if (!\is_string($value)) { throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName)); } diff --git a/src/Console/Output/ErrorOutput.php b/src/Console/Output/ErrorOutput.php index 587663359c1..ffedf13f7ab 100644 --- a/src/Console/Output/ErrorOutput.php +++ b/src/Console/Output/ErrorOutput.php @@ -61,14 +61,14 @@ public function listErrors($process, array $errors) continue; } - $class = sprintf('[%s]', get_class($e)); + $class = sprintf('[%s]', \get_class($e)); $message = $e->getMessage(); $code = $e->getCode(); if (0 !== $code) { $message .= " (${code})"; } - $length = max(strlen($class), strlen($message)); + $length = max(\strlen($class), \strlen($message)); $lines = [ '', $class, @@ -79,8 +79,8 @@ public function listErrors($process, array $errors) $this->output->writeln(''); foreach ($lines as $line) { - if (strlen($line) < $length) { - $line .= str_repeat(' ', $length - strlen($line)); + if (\strlen($line) < $length) { + $line .= str_repeat(' ', $length - \strlen($line)); } $this->output->writeln(sprintf(' %s ', $this->prepareOutput($line))); @@ -100,7 +100,7 @@ public function listErrors($process, array $errors) } } - if (Error::TYPE_LINT === $error->getType() && 0 < count($error->getAppliedFixers())) { + if (Error::TYPE_LINT === $error->getType() && 0 < \count($error->getAppliedFixers())) { $this->output->writeln(''); $this->output->writeln(sprintf(' Applied fixers: %s', implode(', ', $error->getAppliedFixers()))); diff --git a/src/Console/Output/ProcessOutput.php b/src/Console/Output/ProcessOutput.php index 3e52f84eaad..9461821a0e4 100644 --- a/src/Console/Output/ProcessOutput.php +++ b/src/Console/Output/ProcessOutput.php @@ -79,7 +79,7 @@ public function __construct(OutputInterface $output, EventDispatcherInterface $d // 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); + $this->symbolsPerLine = max(1, $width - \strlen((string) $this->files) * 2 - 11); } public function __destruct() @@ -99,7 +99,7 @@ public function onFixerFileProcessed(FixerFileProcessedEvent $event) if (0 === $symbolsOnCurrentLine || $isLast) { $this->output->write(sprintf( - '%s %'.strlen((string) $this->files).'d / %d (%3d%%)', + '%s %'.\strlen((string) $this->files).'d / %d (%3d%%)', $isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '', $this->processedFiles, $this->files, diff --git a/src/Console/WarningsDetector.php b/src/Console/WarningsDetector.php index c5e1c573ea0..3f5d130d91d 100644 --- a/src/Console/WarningsDetector.php +++ b/src/Console/WarningsDetector.php @@ -62,7 +62,7 @@ public function detectOldVendor() */ public function getWarnings() { - if (!count($this->warnings)) { + if (!\count($this->warnings)) { return []; } diff --git a/src/DocBlock/Annotation.php b/src/DocBlock/Annotation.php index a606c94e3d1..5da487a5a86 100644 --- a/src/DocBlock/Annotation.php +++ b/src/DocBlock/Annotation.php @@ -205,7 +205,7 @@ public function getTypes() ); $this->types[] = $matches['type']; - $content = substr($content, strlen($matches['type']) + 1); + $content = substr($content, \strlen($matches['type']) + 1); } } @@ -226,6 +226,22 @@ public function setTypes(array $types) $this->clearCache(); } + /** + * Get the normalized types associated with this annotation, so they can easily be compared. + * + * @return string[] + */ + public function getNormalizedTypes() + { + $normalized = array_map(static function ($type) { + return strtolower($type); + }, $this->getTypes()); + + sort($normalized); + + return $normalized; + } + /** * Remove this annotation by removing all its lines. */ @@ -245,12 +261,12 @@ public function remove() */ public function getContent() { - return implode($this->lines); + return implode('', $this->lines); } public function supportTypes() { - return in_array($this->getTag()->getName(), self::$tags, true); + return \in_array($this->getTag()->getName(), self::$tags, true); } /** diff --git a/src/DocBlock/DocBlock.php b/src/DocBlock/DocBlock.php index ddeecea1167..6f47a886557 100644 --- a/src/DocBlock/DocBlock.php +++ b/src/DocBlock/DocBlock.php @@ -12,7 +12,7 @@ namespace PhpCsFixer\DocBlock; -use PhpCsFixer\Utils; +use PhpCsFixer\Preg; /** * This class represents a docblock. @@ -44,7 +44,7 @@ final class DocBlock */ public function __construct($content) { - foreach (Utils::splitLines($content) as $line) { + foreach (Preg::split('/([^\n\r]+\R*)/', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $line) { $this->lines[] = new Line($line); } } @@ -92,12 +92,12 @@ public function getAnnotations() { if (null === $this->annotations) { $this->annotations = []; - $total = count($this->lines); + $total = \count($this->lines); for ($index = 0; $index < $total; ++$index) { if ($this->lines[$index]->containsATag()) { // get all the lines that make up the annotation - $lines = array_slice($this->lines, $index, $this->findAnnotationLength($index), true); + $lines = \array_slice($this->lines, $index, $this->findAnnotationLength($index), true); $annotation = new Annotation($lines); // move the index to the end of the annotation to avoid // checking it again because we know the lines inside the @@ -159,7 +159,7 @@ public function getAnnotationsOfType($types) */ public function getContent() { - return implode($this->lines); + return implode('', $this->lines); } private function findAnnotationLength($start) diff --git a/src/DocBlock/Tag.php b/src/DocBlock/Tag.php index 43e3e804fc0..915224baa69 100644 --- a/src/DocBlock/Tag.php +++ b/src/DocBlock/Tag.php @@ -108,6 +108,6 @@ public function setName($name) */ public function valid() { - return in_array($this->getName(), self::$tags, true); + return \in_array($this->getName(), self::$tags, true); } } diff --git a/src/DocBlock/TagComparator.php b/src/DocBlock/TagComparator.php index 854a2b30014..044f3dcf6c6 100644 --- a/src/DocBlock/TagComparator.php +++ b/src/DocBlock/TagComparator.php @@ -50,7 +50,7 @@ public static function shouldBeTogether(Tag $first, Tag $second) } foreach (self::$groups as $group) { - if (in_array($firstName, $group, true) && in_array($secondName, $group, true)) { + if (\in_array($firstName, $group, true) && \in_array($secondName, $group, true)) { return true; } } diff --git a/src/Doctrine/Annotation/Token.php b/src/Doctrine/Annotation/Token.php index 931646af186..890b5e48db6 100644 --- a/src/Doctrine/Annotation/Token.php +++ b/src/Doctrine/Annotation/Token.php @@ -82,11 +82,11 @@ public function setContent($content) */ public function isType($types) { - if (!is_array($types)) { + if (!\is_array($types)) { $types = [$types]; } - return in_array($this->getType(), $types, true); + return \in_array($this->getType(), $types, true); } /** diff --git a/src/Doctrine/Annotation/Tokens.php b/src/Doctrine/Annotation/Tokens.php index 59da4bbe0d8..640b4000e0c 100644 --- a/src/Doctrine/Annotation/Tokens.php +++ b/src/Doctrine/Annotation/Tokens.php @@ -62,14 +62,14 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags } if (1 === $index) { - if (DocLexer::T_IDENTIFIER !== $token['type'] || in_array($token['value'], $ignoredTags, true)) { + if (DocLexer::T_IDENTIFIER !== $token['type'] || \in_array($token['value'], $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 && !\in_array($token['type'], [DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS], true)) { break; } @@ -79,7 +79,7 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags ++$nbScopes; } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token['type']) { if (0 === --$nbScopes) { - $nbScannedTokensToUse = count($scannedTokens); + $nbScannedTokensToUse = \count($scannedTokens); break; } @@ -99,7 +99,7 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags } $lastTokenEndIndex = 0; - foreach (array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) { + foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) { if (DocLexer::T_STRING === $token['type']) { $token['value'] = '"'.str_replace('"', '""', $token['value']).'"'; } @@ -114,16 +114,16 @@ public static function createFromDocComment(PhpToken $input, array $ignoredTags } $tokens[] = new Token($token['type'], $token['value']); - $lastTokenEndIndex = $token['position'] + strlen($token['value']); + $lastTokenEndIndex = $token['position'] + \strlen($token['value']); } - $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + strlen($token['value']); + $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + \strlen($token['value']); } else { $currentPosition = $nextAtPosition + 1; } } - if ($ignoredTextPosition < strlen($content)) { + if ($ignoredTextPosition < \strlen($content)) { $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition)); } @@ -206,7 +206,7 @@ public function getAnnotationEnd($index) if (null !== $currentIndex) { $level = 0; - for ($max = count($this); $currentIndex < $max; ++$currentIndex) { + for ($max = \count($this); $currentIndex < $max; ++$currentIndex) { if ($this[$currentIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) { ++$level; } elseif ($this[$currentIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) { @@ -234,7 +234,7 @@ public function getAnnotationEnd($index) public function getArrayEnd($index) { $level = 1; - for (++$index, $max = count($this); $index < $max; ++$index) { + for (++$index, $max = \count($this); $index < $max; ++$index) { if ($this[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) { ++$level; } elseif ($this[$index]->isType($index, DocLexer::T_CLOSE_CURLY_BRACES)) { @@ -289,9 +289,9 @@ public function insertAt($index, Token $token) public function offsetSet($index, $token) { if (!$token instanceof Token) { - $type = gettype($token); + $type = \gettype($token); if ('object' === $type) { - $type = get_class($token); + $type = \get_class($token); } throw new \InvalidArgumentException(sprintf( @@ -301,7 +301,7 @@ public function offsetSet($index, $token) } if (null === $index) { - $index = count($this); + $index = \count($this); $this->setSize($this->getSize() + 1); } @@ -319,7 +319,7 @@ public function offsetUnset($index) throw new \OutOfBoundsException(sprintf('Index %s is invalid or does not exist.', $index)); } - $max = count($this) - 1; + $max = \count($this) - 1; while ($index < $max) { $this[$index] = $this[$index + 1]; ++$index; diff --git a/src/FileReader.php b/src/FileReader.php index 925a7eb9cdc..2e4736e8deb 100644 --- a/src/FileReader.php +++ b/src/FileReader.php @@ -70,6 +70,18 @@ public function read($filePath) */ private function readRaw($realPath) { - return file_get_contents($realPath); + $content = @file_get_contents($realPath); + + if (false === $content) { + $error = error_get_last(); + + throw new \RuntimeException(sprintf( + 'Failed to read content from "%s".%s', + $realPath, + $error ? ' '.$error['message'] : '' + )); + } + + return $content; } } diff --git a/src/Fixer/Alias/BacktickToShellExecFixer.php b/src/Fixer/Alias/BacktickToShellExecFixer.php index 371d84348a7..4da7eb04e8c 100644 --- a/src/Fixer/Alias/BacktickToShellExecFixer.php +++ b/src/Fixer/Alias/BacktickToShellExecFixer.php @@ -41,7 +41,7 @@ public function getDefinition() 'Converts backtick operators to `shell_exec` calls.', [ new CodeSample( -<<<'EOT' + <<<'EOT' call()}`; @@ -108,7 +108,7 @@ private function fixBackticks(Tokens $tokens, array $backtickTokens) // Double-quoted strings are parsed differently if they contain // variables or not, so we need to build the new token array accordingly - $count = count($backtickTokens); + $count = \count($backtickTokens); $newTokens = [ new Token([T_STRING, 'shell_exec']), diff --git a/src/Fixer/Alias/EregToPregFixer.php b/src/Fixer/Alias/EregToPregFixer.php index b8b38321032..6186c1ffb24 100644 --- a/src/Fixer/Alias/EregToPregFixer.php +++ b/src/Fixer/Alias/EregToPregFixer.php @@ -17,6 +17,7 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\Preg; use PhpCsFixer\PregException; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Utils; @@ -79,6 +80,7 @@ public function isRisky() protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $end = $tokens->count() - 1; + $functionsAnalyzer = new FunctionsAnalyzer(); foreach (self::$functions as $map) { // the sequence is the function name, followed by "(" and a quoted string @@ -102,9 +104,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) // advance tokenizer cursor $currIndex = $match[2]; - // ensure it's a function call (not a method / static call) - $prev = $tokens->getPrevMeaningfulToken($match[0]); - if (null === $prev || $tokens[$prev]->isGivenKind([T_OBJECT_OPERATOR, T_DOUBLE_COLON])) { + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $match[0])) { continue; } diff --git a/src/Fixer/Alias/MbStrFunctionsFixer.php b/src/Fixer/Alias/MbStrFunctionsFixer.php index e00f8fab65d..e791002f662 100644 --- a/src/Fixer/Alias/MbStrFunctionsFixer.php +++ b/src/Fixer/Alias/MbStrFunctionsFixer.php @@ -51,7 +51,7 @@ public function getDefinition() 'Replace non multibyte-safe functions with corresponding mb function.', [ new CodeSample( -'countArguments($tokens, $openParenthesis, $closeParenthesis); - if (!in_array($count, $functionReplacement['argumentCount'], true)) { + if (!\in_array($count, $functionReplacement['argumentCount'], true)) { continue 2; } diff --git a/src/Fixer/Alias/NoAliasFunctionsFixer.php b/src/Fixer/Alias/NoAliasFunctionsFixer.php index 074b1020eac..aa4d9b63a8b 100644 --- a/src/Fixer/Alias/NoAliasFunctionsFixer.php +++ b/src/Fixer/Alias/NoAliasFunctionsFixer.php @@ -19,7 +19,7 @@ use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; -use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -38,6 +38,7 @@ final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableF 'close' => 'closedir', 'doubleval' => 'floatval', 'fputs' => 'fwrite', + 'get_required_files' => 'get_included_files', 'ini_alter' => 'ini_set', 'is_double' => 'is_float', 'is_integer' => 'is_int', @@ -51,6 +52,7 @@ final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableF 'show_source' => 'highlight_file', 'sizeof' => 'count', 'strchr' => 'strstr', + 'user_error' => 'trigger_error', ]; /** @var array stores alias (key) - master (value) functions mapping */ @@ -79,6 +81,7 @@ final class NoAliasFunctionsFixer extends AbstractFixer implements ConfigurableF 'mbereg_search_setpos' => 'mb_ereg_search_setpos', 'mberegi' => 'mb_eregi', 'mberegi_replace' => 'mb_eregi_replace', + 'mbregex_encoding' => 'mb_regex_encoding', 'mbsplit' => 'mb_split', ]; @@ -114,11 +117,12 @@ public function getDefinition() 'Master functions shall be used instead of aliases.', [ new CodeSample( -'findGivenKind(T_STRING) as $index => $token) { // check mapping hit @@ -184,16 +191,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } - // skip expressions which are not function reference - $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); - $prevToken = $tokens[$prevTokenIndex]; - - // handle function reference with namespaces - if ($prevToken->isGivenKind([T_NS_SEPARATOR])) { - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($prevTokenIndex)]; - } - - if ($prevToken->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, T_STRING, CT::T_NAMESPACE_OPERATOR, CT::T_RETURN_REF])) { + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { continue; } diff --git a/src/Fixer/Alias/PowToExponentiationFixer.php b/src/Fixer/Alias/PowToExponentiationFixer.php index 500eecce6f2..2b3942d8afd 100644 --- a/src/Fixer/Alias/PowToExponentiationFixer.php +++ b/src/Fixer/Alias/PowToExponentiationFixer.php @@ -69,7 +69,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $argumentsAnalyzer = new ArgumentsAnalyzer(); $numberOfTokensAdded = 0; - $previousCloseParenthesisIndex = count($tokens); + $previousCloseParenthesisIndex = \count($tokens); foreach (array_reverse($candidates) as $candidate) { // if in the previous iteration(s) tokens were added to the collection and this is done within the tokens // indexes of the current candidate than the index of the close ')' of the candidate has moved and so @@ -83,7 +83,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]); - if (2 !== count($arguments)) { + if (2 !== \count($arguments)) { continue; } @@ -107,7 +107,7 @@ private function findPowCalls(Tokens $tokens) $candidates = []; // Minimal candidate to fix is seven tokens: pow(x,x); - $end = count($tokens) - 6; + $end = \count($tokens) - 6; // First possible location is after the open token: 1 for ($i = 1; $i < $end; ++$i) { @@ -140,6 +140,10 @@ private function fixPowToExponentiation(Tokens $tokens, $functionNameIndex, $ope // clean up the function call tokens prt. I $tokens->clearAt($closeParenthesisIndex); + $previousIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); + if ($tokens[$previousIndex]->equals(',')) { + $tokens->clearAt($previousIndex); // trailing ',' in function call (PHP 7.3) + } $added = 0; // check if the arguments need to be wrapped in parenthesis diff --git a/src/Fixer/Alias/RandomApiMigrationFixer.php b/src/Fixer/Alias/RandomApiMigrationFixer.php index 8acd70c60d1..0372f5cc922 100644 --- a/src/Fixer/Alias/RandomApiMigrationFixer.php +++ b/src/Fixer/Alias/RandomApiMigrationFixer.php @@ -103,7 +103,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) list($functionName, $openParenthesis, $closeParenthesis) = $boundaries; $count = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); - if (!in_array($count, $functionReplacement['argumentCount'], true)) { + if (!\in_array($count, $functionReplacement['argumentCount'], true)) { continue 2; } @@ -138,18 +138,18 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['array']) ->setAllowedValues([static function ($value) { foreach ($value as $functionName => $replacement) { - if (!array_key_exists($functionName, self::$argumentCounts)) { + if (!\array_key_exists($functionName, self::$argumentCounts)) { throw new InvalidOptionsException(sprintf( 'Function "%s" is not handled by the fixer.', $functionName )); } - if (!is_string($replacement)) { + if (!\is_string($replacement)) { throw new InvalidOptionsException(sprintf( 'Replacement for function "%s" must be a string, "%s" given.', $functionName, - is_object($replacement) ? get_class($replacement) : gettype($replacement) + \is_object($replacement) ? \get_class($replacement) : \gettype($replacement) )); } } diff --git a/src/Fixer/Alias/SetTypeToCastFixer.php b/src/Fixer/Alias/SetTypeToCastFixer.php index a02f78a7964..088f24fb71b 100644 --- a/src/Fixer/Alias/SetTypeToCastFixer.php +++ b/src/Fixer/Alias/SetTypeToCastFixer.php @@ -77,7 +77,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $functionNameIndex = $candidate[0]; $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]); - if (2 !== count($arguments)) { + if (2 !== \count($arguments)) { continue; // function must be overridden or used incorrectly } @@ -156,7 +156,7 @@ private function findSettypeCalls(Tokens $tokens) { $candidates = []; - $end = count($tokens); + $end = \count($tokens); for ($i = 1; $i < $end; ++$i) { $candidate = $this->find('settype', $tokens, $i, $end); if (null === $candidate) { @@ -189,6 +189,10 @@ private function removeSettypeCall( $closeParenthesisIndex ) { $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex); + $prevIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex); + if ($tokens[$prevIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } $tokens->clearTokenAndMergeSurroundingWhitespace($secondArgumentStart); $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex); $tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStart); diff --git a/src/Fixer/Basic/BracesFixer.php b/src/Fixer/Basic/BracesFixer.php index 73a08c66789..425c17ed353 100644 --- a/src/Fixer/Basic/BracesFixer.php +++ b/src/Fixer/Basic/BracesFixer.php @@ -51,7 +51,7 @@ public function getDefinition() 'The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.', [ new CodeSample( -'= 0; }; $negative = function ($item) { return $item < 0; }; @@ -87,7 +87,7 @@ public function bar($baz) { ['allow_single_line_closure' => true] ), new CodeSample( -'getControlContinuationTokens(); - for ($index = count($tokens) - 1; 0 <= $index; --$index) { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind($controlContinuationTokens)) { @@ -261,7 +261,7 @@ private function fixControlContinuationBraces(Tokens $tokens) private function fixDoWhile(Tokens $tokens) { - for ($index = count($tokens) - 1; 0 <= $index; --$index) { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_DO)) { @@ -295,7 +295,7 @@ static function ($item) { ); $tokensAnalyzer = new TokensAnalyzer($tokens); - for ($index = 0, $limit = count($tokens); $index < $limit; ++$index) { + for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { $token = $tokens[$index]; // if token is not a structure element - continue @@ -565,7 +565,7 @@ static function ($item) { if ( self::LINE_SAME === $this->configuration['position_after_functions_and_oop_constructs'] && ( - $token->isGivenKind([T_FUNCTION]) && !$tokensAnalyzer->isLambda($index) + $token->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index) || $token->isGivenKind($classyTokens) && !$tokensAnalyzer->isAnonymousClass($index) ) && !$tokens[$tokens->getPrevNonWhitespace($startBraceIndex)]->isComment() @@ -582,7 +582,7 @@ static function ($item) { } // reset loop limit due to collection change - $limit = count($tokens); + $limit = \count($tokens); } } diff --git a/src/Fixer/Basic/EncodingFixer.php b/src/Fixer/Basic/EncodingFixer.php index 08fe5334f6d..b193a8dc474 100644 --- a/src/Fixer/Basic/EncodingFixer.php +++ b/src/Fixer/Basic/EncodingFixer.php @@ -43,7 +43,7 @@ public function getDefinition() 'PHP code MUST use only UTF-8 without BOM (remove BOM).', [ new CodeSample( -$this->BOM.'BOM.' 1 && $tokens->isAnyTokenKindsFound(self::$tokens); + return \count($tokens) > 1 && $tokens->isAnyTokenKindsFound(self::$tokens); } /** diff --git a/src/Fixer/Basic/Psr0Fixer.php b/src/Fixer/Basic/Psr0Fixer.php index 3a7fb2e2d0a..efad8b9c02e 100644 --- a/src/Fixer/Basic/Psr0Fixer.php +++ b/src/Fixer/Basic/Psr0Fixer.php @@ -81,12 +81,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $namespace = trim($tokens->generatePartialCode($namespaceIndex, $namespaceEndIndex - 1)); } elseif ($token->isClassy()) { - if (null !== $classyName) { - return; - } - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind(T_NEW)) { + continue; + } + + if (null !== $classyName) { return; } @@ -102,25 +102,25 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) if (false !== $namespace) { $normNamespace = str_replace('\\', '/', $namespace); $path = str_replace('\\', '/', $file->getRealPath()); - $dir = dirname($path); + $dir = \dirname($path); if ('' !== $this->configuration['dir']) { - $dir = substr($dir, strlen(realpath($this->configuration['dir'])) + 1); + $dir = substr($dir, \strlen(realpath($this->configuration['dir'])) + 1); if (false === $dir) { $dir = ''; } - if (strlen($normNamespace) > strlen($dir)) { + if (\strlen($normNamespace) > \strlen($dir)) { if ('' !== $dir) { - $normNamespace = substr($normNamespace, -strlen($dir)); + $normNamespace = substr($normNamespace, -\strlen($dir)); } else { $normNamespace = ''; } } } - $dir = substr($dir, -strlen($normNamespace)); + $dir = substr($dir, -\strlen($normNamespace)); if (false === $dir) { $dir = ''; } @@ -135,7 +135,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) for ($i = $namespaceIndex; $i <= $namespaceEndIndex; ++$i) { $tokens->clearAt($i); } - $namespace = substr($namespace, 0, -strlen($dir)).str_replace('/', '\\', $dir); + $namespace = substr($namespace, 0, -\strlen($dir)).str_replace('/', '\\', $dir); $newNamespace = Tokens::fromCode('clearRange(0, 2); @@ -146,7 +146,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } else { $normClass = str_replace('_', '/', $classyName); $path = str_replace('\\', '/', $file->getRealPath()); - $filename = substr($path, -strlen($normClass) - 4, -4); + $filename = substr($path, -\strlen($normClass) - 4, -4); if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]); diff --git a/src/Fixer/Basic/Psr4Fixer.php b/src/Fixer/Basic/Psr4Fixer.php index 8c49065f803..af6be841865 100644 --- a/src/Fixer/Basic/Psr4Fixer.php +++ b/src/Fixer/Basic/Psr4Fixer.php @@ -52,24 +52,24 @@ class InvalidName {} */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - $namespace = false; - $classyName = null; + $isNamespaceFound = false; $classyIndex = 0; + $classyName = null; foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_NAMESPACE)) { - if (false !== $namespace) { + if ($isNamespaceFound) { return; } - $namespace = true; + $isNamespaceFound = true; } elseif ($token->isClassy()) { - if (null !== $classyName) { - return; - } - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->isGivenKind(T_NEW)) { + continue; + } + + if (null !== $classyName) { return; } @@ -82,7 +82,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) return; } - if (false !== $namespace) { + if ($isNamespaceFound) { $filename = basename(str_replace('\\', '/', $file->getRealPath()), '.php'); if ($classyName !== $filename) { @@ -90,7 +90,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } } else { $normClass = str_replace('_', '/', $classyName); - $filename = substr(str_replace('\\', '/', $file->getRealPath()), -strlen($normClass) - 4, -4); + $filename = substr(str_replace('\\', '/', $file->getRealPath()), -\strlen($normClass) - 4, -4); if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]); diff --git a/src/Fixer/Casing/LowercaseConstantsFixer.php b/src/Fixer/Casing/LowercaseConstantsFixer.php index f6776e818ab..b2609ba9dc3 100644 --- a/src/Fixer/Casing/LowercaseConstantsFixer.php +++ b/src/Fixer/Casing/LowercaseConstantsFixer.php @@ -83,6 +83,7 @@ private function isNeighbourAccepted(Tokens $tokens, $index) T_INTERFACE, T_NEW, T_NS_SEPARATOR, + T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM, T_TRAIT, T_USE, diff --git a/src/Fixer/Casing/LowercaseKeywordsFixer.php b/src/Fixer/Casing/LowercaseKeywordsFixer.php index 751dd45d84a..a6e8e3ed13b 100644 --- a/src/Fixer/Casing/LowercaseKeywordsFixer.php +++ b/src/Fixer/Casing/LowercaseKeywordsFixer.php @@ -36,7 +36,7 @@ public function getDefinition() 'PHP keywords MUST be in lower case.', [ new CodeSample( -'getPrevMeaningfulToken($index); - if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_OBJECT_OPERATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { + if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR, T_OBJECT_OPERATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { continue; } @@ -95,7 +95,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } - if ('static' === $newContent && $tokens[$nextIndex]->isGivenKind([T_VARIABLE])) { + if ('static' === $newContent && $tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { continue; } diff --git a/src/Fixer/Casing/MagicMethodCasingFixer.php b/src/Fixer/Casing/MagicMethodCasingFixer.php new file mode 100644 index 00000000000..e073c9bd9b3 --- /dev/null +++ b/src/Fixer/Casing/MagicMethodCasingFixer.php @@ -0,0 +1,230 @@ + + * 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\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class MagicMethodCasingFixer extends AbstractFixer +{ + private static $magicNames = [ + '__call' => '__call', + '__callstatic' => '__callStatic', + '__clone' => '__clone', + '__construct' => '__construct', + '__debuginfo' => '__debugInfo', + '__destruct' => '__destruct', + '__get' => '__get', + '__invoke' => '__invoke', + '__isset' => '__isset', + '__set' => '__set', + '__set_state' => '__set_state', + '__sleep' => '__sleep', + '__tostring' => '__toString', + '__unset' => '__unset', + '__wakeup' => '__wakeup', + ]; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Magic method definitions and calls must be using the correct casing.', + [ + new CodeSample( + '__INVOKE(1); +' + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_OBJECT_OPERATOR, T_DOUBLE_COLON]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $inClass = 0; + $tokenCount = \count($tokens); + + for ($index = 1; $index < $tokenCount - 2; ++$index) { + if (0 === $inClass && $tokens[$index]->isClassy()) { + $inClass = 1; + $index = $tokens->getNextTokenOfKind($index, ['{']); + + continue; + } + + if (0 !== $inClass) { + if ($tokens[$index]->equals('{')) { + ++$inClass; + + continue; + } + + if ($tokens[$index]->equals('}')) { + --$inClass; + + continue; + } + } + + if (!$tokens[$index]->isGivenKind(T_STRING)) { + continue; // wrong type + } + + $content = $tokens[$index]->getContent(); + if ('__' !== substr($content, 0, 2)) { + continue; // cheap look ahead + } + + $name = strtolower($content); + + if (!$this->isMagicMethodName($name)) { + continue; // method name is not one of the magic ones we can fix + } + + $nameInCorrectCasing = $this->getMagicMethodNameInCorrectCasing($name); + if ($nameInCorrectCasing === $content) { + continue; // method name is already in the correct casing, no fix needed + } + + if ($this->isFunctionSignature($tokens, $index)) { + if (0 !== $inClass) { + // this is a method definition we want to fix + $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); + } + + continue; + } + + if ($this->isMethodCall($tokens, $index)) { + $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); + + continue; + } + + if ( + ('__callstatic' === $name || '__set_state' === $name) + && $this->isStaticMethodCall($tokens, $index) + ) { + $this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing); + } + } + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return bool + */ + private function isFunctionSignature(Tokens $tokens, $index) + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) { + return false; // not a method signature + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return bool + */ + private function isMethodCall(Tokens $tokens, $index) + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->equals([T_OBJECT_OPERATOR, '->'])) { + return false; // not a "simple" method call + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return bool + */ + private function isStaticMethodCall(Tokens $tokens, $index) + { + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON)) { + return false; // not a "simple" static method call + } + + return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('('); + } + + /** + * @param string $name + * + * @return bool + */ + private function isMagicMethodName($name) + { + return isset(self::$magicNames[$name]); + } + + /** + * @param string $name name of a magic method + * + * @return string + */ + private function getMagicMethodNameInCorrectCasing($name) + { + return self::$magicNames[$name]; + } + + /** + * @param Tokens $tokens + * @param int $index + * @param string $nameInCorrectCasing + */ + private function setTokenToCorrectCasing(Tokens $tokens, $index, $nameInCorrectCasing) + { + $tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]); + } +} diff --git a/src/Fixer/Casing/NativeFunctionCasingFixer.php b/src/Fixer/Casing/NativeFunctionCasingFixer.php index 2ae1c5f663a..64143e59dda 100644 --- a/src/Fixer/Casing/NativeFunctionCasingFixer.php +++ b/src/Fixer/Casing/NativeFunctionCasingFixer.php @@ -15,6 +15,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -67,7 +68,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $functionNamePrefix = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION])) { + if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) { continue; } @@ -81,7 +82,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) // test if the function call is to a native PHP function $lower = strtolower($tokens[$index]->getContent()); - if (!array_key_exists($lower, $nativeFunctionNames)) { + if (!\array_key_exists($lower, $nativeFunctionNames)) { continue; } diff --git a/src/Fixer/CastNotation/LowercaseCastFixer.php b/src/Fixer/CastNotation/LowercaseCastFixer.php index 51047b5069f..06d69a03f74 100644 --- a/src/Fixer/CastNotation/LowercaseCastFixer.php +++ b/src/Fixer/CastNotation/LowercaseCastFixer.php @@ -32,7 +32,7 @@ public function getDefinition() 'Cast should be written in lower case.', [ new CodeSample( -'getPrevMeaningfulToken($paramContentEnd); + if ($tokens[$commaCandidate]->equals(',')) { + $tokens->removeTrailingWhitespace($commaCandidate); + $tokens->clearAt($commaCandidate); + $paramContentEnd = $commaCandidate; + } + // check if something complex passed as an argument and preserve parenthesises then $countParamTokens = 0; - for ($paramContentIndex = $openParenthesis + 1; $paramContentIndex < $closeParenthesis; ++$paramContentIndex) { + for ($paramContentIndex = $openParenthesis + 1; $paramContentIndex < $paramContentEnd; ++$paramContentIndex) { //not a space, means some sensible token if (!$tokens[$paramContentIndex]->isGivenKind(T_WHITESPACE)) { ++$countParamTokens; @@ -102,6 +110,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $preserveParenthesises = $countParamTokens > 1; + $afterCloseParenthesisIndex = $tokens->getNextMeaningfulToken($closeParenthesis); + $afterCloseParenthesisToken = $tokens[$afterCloseParenthesisIndex]; + $wrapInParenthesises = $afterCloseParenthesisToken->equals('[') || $afterCloseParenthesisToken->isGivenKind(T_POW); + // analyse namespace specification (root one or none) and decide what to do $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionName); if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { @@ -115,6 +127,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) new Token($newToken), new Token([T_WHITESPACE, ' ']), ]; + if ($wrapInParenthesises) { + array_unshift($replacementSequence, new Token('(')); + } if (!$preserveParenthesises) { // closing parenthesis removed with leading spaces @@ -130,6 +145,10 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $tokens->removeTrailingWhitespace($functionName); } + if ($wrapInParenthesises) { + $tokens->insertAt($closeParenthesis, new Token(')')); + } + $tokens->overrideRange($functionName, $functionName, $replacementSequence); // nested transformations support diff --git a/src/Fixer/CastNotation/NoShortBoolCastFixer.php b/src/Fixer/CastNotation/NoShortBoolCastFixer.php index 2caae37fff6..495a17aa95f 100644 --- a/src/Fixer/CastNotation/NoShortBoolCastFixer.php +++ b/src/Fixer/CastNotation/NoShortBoolCastFixer.php @@ -56,7 +56,7 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - for ($index = count($tokens) - 1; $index > 1; --$index) { + for ($index = \count($tokens) - 1; $index > 1; --$index) { if ($tokens[$index]->equals('!')) { $index = $this->fixShortCast($tokens, $index); } diff --git a/src/Fixer/CastNotation/NoUnsetCastFixer.php b/src/Fixer/CastNotation/NoUnsetCastFixer.php new file mode 100644 index 00000000000..1e18a0dad9d --- /dev/null +++ b/src/Fixer/CastNotation/NoUnsetCastFixer.php @@ -0,0 +1,86 @@ + + * 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\CastNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class NoUnsetCastFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Variables must be set `null` instead of using `(unset)` casting.', + [new CodeSample("isTokenKindFound(T_UNSET_CAST); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = \count($tokens) - 1; $index > 0; --$index) { + if ($tokens[$index]->isGivenKind(T_UNSET_CAST)) { + $this->fixUnsetCast($tokens, $index); + } + } + } + + /** + * @param Tokens $tokens + * @param int $index + */ + private function fixUnsetCast(Tokens $tokens, $index) + { + $assignmentIndex = $tokens->getPrevMeaningfulToken($index); + if (null === $assignmentIndex || !$tokens[$assignmentIndex]->equals('=')) { + return; + } + + $varIndex = $tokens->getNextMeaningfulToken($index); + if (null === $varIndex || !$tokens[$varIndex]->isGivenKind(T_VARIABLE)) { + return; + } + + $nextIsWhiteSpace = $tokens[$assignmentIndex + 1]->isWhitespace(); + + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($varIndex); + + ++$assignmentIndex; + if (!$nextIsWhiteSpace) { + $tokens->insertAt($assignmentIndex, new Token([T_WHITESPACE, ' '])); + } + + ++$assignmentIndex; + $tokens->insertAt($assignmentIndex, new Token([T_STRING, 'null'])); + } +} diff --git a/src/Fixer/CastNotation/ShortScalarCastFixer.php b/src/Fixer/CastNotation/ShortScalarCastFixer.php index 8226f9406fd..63394f28bb2 100644 --- a/src/Fixer/CastNotation/ShortScalarCastFixer.php +++ b/src/Fixer/CastNotation/ShortScalarCastFixer.php @@ -63,7 +63,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $castFrom = trim(substr($tokens[$index]->getContent(), 1, -1)); $castFromLowered = strtolower($castFrom); - if (!array_key_exists($castFromLowered, $castMap)) { + if (!\array_key_exists($castFromLowered, $castMap)) { continue; } diff --git a/src/Fixer/ClassNotation/ClassDefinitionFixer.php b/src/Fixer/ClassNotation/ClassDefinitionFixer.php index f8b28393270..8a2a5ba9f78 100644 --- a/src/Fixer/ClassNotation/ClassDefinitionFixer.php +++ b/src/Fixer/ClassNotation/ClassDefinitionFixer.php @@ -42,7 +42,7 @@ public function getDefinition() 'Whitespace around the keywords of a class, trait or interfaces definition should be one space.', [ new CodeSample( -' true] ), new CodeSample( -' true] ), new CodeSample( -'isGivenKind(T_TRAIT)) { $extends = $tokens->findGivenKind(T_EXTENDS, $classyIndex, $openIndex); - $extends = count($extends) ? $this->getClassyInheritanceInfo($tokens, key($extends), 'numberOfExtends') : false; + $extends = \count($extends) ? $this->getClassyInheritanceInfo($tokens, key($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; + $implements = \count($implements) ? $this->getClassyInheritanceInfo($tokens, key($implements), 'numberOfImplements') : false; $tokensAnalyzer = new TokensAnalyzer($tokens); $anonymousClass = $tokensAnalyzer->isAnonymousClass($classyIndex); } diff --git a/src/Fixer/ClassNotation/FinalInternalClassFixer.php b/src/Fixer/ClassNotation/FinalInternalClassFixer.php index 89e57ab95b6..c1e17e62ea7 100644 --- a/src/Fixer/ClassNotation/FinalInternalClassFixer.php +++ b/src/Fixer/ClassNotation/FinalInternalClassFixer.php @@ -42,7 +42,7 @@ public function configure(array $configuration) $this->configuration['annotation-black-list'] ); - if (count($intersect)) { + if (\count($intersect)) { throw new InvalidFixerConfigurationException($this->getName(), sprintf('Annotation cannot be used in both the white- and black list, got duplicates: "%s".', implode('", "', array_keys($intersect)))); } } @@ -112,7 +112,7 @@ protected function createConfigurationDefinition() { $annotationsAsserts = [static function (array $values) { foreach ($values as $value) { - if (!is_string($value) || '' === $value) { + if (!\is_string($value) || '' === $value) { return false; } } diff --git a/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php b/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php index 9c7b1863c9c..4839dda86ef 100644 --- a/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php +++ b/src/Fixer/ClassNotation/NoBlankLinesAfterClassOpeningFixer.php @@ -18,7 +18,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -use PhpCsFixer\Utils; /** * @author Ceeram @@ -87,8 +86,7 @@ private function fixWhitespace(Tokens $tokens, $index) // if there is more than one new line in the whitespace, then we need to fix it if (substr_count($content, "\n") > 1) { // the final bit of the whitespace must be the next statement's indentation - $lines = Utils::splitLines($content); - $tokens[$index] = new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().end($lines)]); + $tokens[$index] = new Token([T_WHITESPACE, $this->whitespacesConfig->getLineEnding().substr($content, strrpos($content, "\n") + 1)]); } } } diff --git a/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php b/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php index cd1e55683a0..84296c2b91a 100644 --- a/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php +++ b/src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php @@ -31,7 +31,7 @@ public function getDefinition() 'Properties MUST not be explicitly initialized with `null`.', [ new CodeSample( -'findGivenKind(T_CLASS)); - $numClasses = count($classes); + $numClasses = \count($classes); for ($i = 0; $i < $numClasses; ++$i) { $index = $classes[$i]; @@ -310,7 +310,7 @@ private function getWrapperMethodSequence(Tokens $tokens, $method, $startIndex, } // append a comma if it's not the first variable - if (count($seq) > 5) { + if (\count($seq) > 5) { $seq[] = ','; } diff --git a/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php b/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php index 9e5db292d16..0813b4ec35e 100644 --- a/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php +++ b/src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php @@ -31,7 +31,7 @@ public function getDefinition() 'A final class must not have final methods.', [ new CodeSample( -'isGivenKind(T_CLASS)) { continue; @@ -84,7 +84,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) */ private function fixClass(Tokens $tokens, $classOpenIndex, $classIsFinal) { - $tokensCount = count($tokens); + $tokensCount = \count($tokens); for ($index = $classOpenIndex + 1; $index < $tokensCount; ++$index) { // Class end if ($tokens[$index]->equals('}')) { diff --git a/src/Fixer/ClassNotation/OrderedClassElementsFixer.php b/src/Fixer/ClassNotation/OrderedClassElementsFixer.php index 2c1023e01f0..54275fbb853 100644 --- a/src/Fixer/ClassNotation/OrderedClassElementsFixer.php +++ b/src/Fixer/ClassNotation/OrderedClassElementsFixer.php @@ -123,7 +123,7 @@ public function configure(array $configuration) $this->typePosition[$type] = null; } - $lastPosition = count($this->configuration['order']); + $lastPosition = \count($this->configuration['order']); foreach ($this->typePosition as &$pos) { if (null === $pos) { $pos = $lastPosition; @@ -236,7 +236,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $sorted = $this->sortElements($elements); - $endIndex = $elements[count($elements) - 1]['end']; + $endIndex = $elements[\count($elements) - 1]['end']; if ($sorted !== $elements) { $this->sortTokens($tokens, $i, $endIndex, $sorted); @@ -324,7 +324,7 @@ private function getElements(Tokens $tokens, $startIndex) } $type = $this->detectElementType($tokens, $i); - if (is_array($type)) { + if (\is_array($type)) { $element['type'] = $type[0]; $element['name'] = $type[1]; } else { @@ -333,7 +333,7 @@ private function getElements(Tokens $tokens, $startIndex) if ('property' === $element['type']) { $element['name'] = $tokens[$i]->getContent(); - } elseif (in_array($element['type'], ['use_trait', 'constant', 'method', 'magic'], true)) { + } elseif (\in_array($element['type'], ['use_trait', 'constant', 'method', 'magic'], true)) { $element['name'] = $tokens[$tokens->getNextMeaningfulToken($i)]->getContent(); } @@ -435,7 +435,7 @@ private function sortElements(array $elements) foreach ($elements as &$element) { $type = $element['type']; - if (array_key_exists($type, self::$specialTypes)) { + if (\array_key_exists($type, self::$specialTypes)) { if (isset($this->typePosition[$type])) { $element['position'] = $this->typePosition[$type]; if ('phpunit' === $type) { @@ -448,7 +448,7 @@ private function sortElements(array $elements) $type = 'method'; } - if (in_array($type, ['constant', 'property', 'method'], true)) { + if (\in_array($type, ['constant', 'property', 'method'], true)) { $type .= '_'.$element['visibility']; if ($element['static']) { $type .= '_static'; diff --git a/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php b/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php index 0e6fff3f554..f8f4bad6638 100644 --- a/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php +++ b/src/Fixer/ClassNotation/ProtectedToPrivateFixer.php @@ -34,7 +34,7 @@ public function getDefinition() 'Converts `protected` variables and methods to `private` where possible.', [ new CodeSample( - 'isGivenKind(T_CLASS)) { continue; diff --git a/src/Fixer/ClassNotation/SelfAccessorFixer.php b/src/Fixer/ClassNotation/SelfAccessorFixer.php index 0ba6cb8a7ed..58827d9dc06 100644 --- a/src/Fixer/ClassNotation/SelfAccessorFixer.php +++ b/src/Fixer/ClassNotation/SelfAccessorFixer.php @@ -15,6 +15,8 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -76,21 +78,22 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); - for ($i = 0, $c = $tokens->count(); $i < $c; ++$i) { - if (!$tokens[$i]->isGivenKind([T_CLASS, T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($i)) { - continue; - } + foreach ((new NamespacesAnalyzer())->getDeclarations($tokens) as $namespace) { + for ($index = $namespace->getScopeStartIndex(); $index < $namespace->getScopeEndIndex(); ++$index) { + if (!$tokens[$index]->isGivenKind([T_CLASS, T_INTERFACE]) || $tokensAnalyzer->isAnonymousClass($index)) { + continue; + } - $nameIndex = $tokens->getNextTokenOfKind($i, [[T_STRING]]); - $startIndex = $tokens->getNextTokenOfKind($nameIndex, ['{']); - $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); + $nameIndex = $tokens->getNextTokenOfKind($index, [[T_STRING]]); + $startIndex = $tokens->getNextTokenOfKind($nameIndex, ['{']); + $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex); - $name = $tokens[$nameIndex]->getContent(); + $name = $tokens[$nameIndex]->getContent(); - $this->replaceNameOccurrences($tokens, $name, $startIndex, $endIndex); + $this->replaceNameOccurrences($tokens, $namespace->getFullName(), $name, $startIndex, $endIndex); - // continue after the class declaration - $i = $endIndex; + $index = $endIndex; + } } } @@ -98,11 +101,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) * Replace occurrences of the name of the classy element by "self" (if possible). * * @param Tokens $tokens + * @param string $namespace * @param string $name * @param int $startIndex * @param int $endIndex */ - private function replaceNameOccurrences(Tokens $tokens, $name, $startIndex, $endIndex) + private function replaceNameOccurrences(Tokens $tokens, $namespace, $name, $startIndex, $endIndex) { $tokensAnalyzer = new TokensAnalyzer($tokens); $insideMethodSignatureUntil = null; @@ -133,11 +137,21 @@ private function replaceNameOccurrences(Tokens $tokens, $name, $startIndex, $end continue; } - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($i)]; $nextToken = $tokens[$tokens->getNextMeaningfulToken($i)]; + if ($nextToken->isGivenKind(T_NS_SEPARATOR)) { + continue; + } - // skip tokens that are part of a fully qualified name or used in class property access - if ($prevToken->isGivenKind([T_NS_SEPARATOR, T_OBJECT_OPERATOR]) || $nextToken->isGivenKind(T_NS_SEPARATOR)) { + $classStartIndex = $i; + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($i)]; + if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { + $classStartIndex = $this->getClassStart($tokens, $i, $namespace); + if (null === $classStartIndex) { + continue; + } + $prevToken = $tokens[$tokens->getPrevMeaningfulToken($classStartIndex)]; + } + if ($prevToken->isGivenKind(T_OBJECT_OPERATOR)) { continue; } @@ -150,8 +164,31 @@ private function replaceNameOccurrences(Tokens $tokens, $name, $startIndex, $end && $prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [CT::T_NULLABLE_TYPE]]) ) ) { + for ($j = $classStartIndex; $j < $i; ++$j) { + $tokens->clearTokenAndMergeSurroundingWhitespace($j); + } $tokens[$i] = new Token([T_STRING, 'self']); } } } + + private function getClassStart(Tokens $tokens, $index, $namespace) + { + $namespace = ('' !== $namespace ? '\\'.$namespace : '').'\\'; + + 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])) { + return null; + } + } else { + if (!$tokens[$index]->equals([T_STRING, $piece], false)) { + return null; + } + } + } + + return $index; + } } diff --git a/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php b/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php index 86799e3afd8..ae2e5fc64e8 100644 --- a/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php +++ b/src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php @@ -83,7 +83,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $elements = array_reverse($analyzer->getClassyElements(), true); foreach ($elements as $index => $element) { - if (!in_array($element['type'], $this->configuration['elements'], true)) { + if (!\in_array($element['type'], $this->configuration['elements'], true)) { continue; // not in configuration } diff --git a/src/Fixer/ClassNotation/VisibilityRequiredFixer.php b/src/Fixer/ClassNotation/VisibilityRequiredFixer.php index aaa5ab116be..bdcee8264f6 100644 --- a/src/Fixer/ClassNotation/VisibilityRequiredFixer.php +++ b/src/Fixer/ClassNotation/VisibilityRequiredFixer.php @@ -44,7 +44,7 @@ public function getDefinition() 'Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.', [ new CodeSample( -'isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - /** - * {@inheritdoc} - */ - protected function applyFix(\SplFileInfo $file, Tokens $tokens) - { - $tokensAnalyzer = new TokensAnalyzer($tokens); - $elements = $tokensAnalyzer->getClassyElements(); - - foreach (array_reverse($elements, true) as $index => $element) { - if (!in_array($element['type'], $this->configuration['elements'], true)) { - continue; - } - - if ('method' === $element['type']) { - $this->fixMethodVisibility($tokens, $index); - } elseif ('property' === $element['type']) { - $this->fixPropertyVisibility($tokens, $index); - } elseif ('const' === $element['type']) { - $this->fixConstVisibility($tokens, $index); - } - } - } - /** * {@inheritdoc} */ @@ -111,7 +88,7 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['array']) ->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])]) ->setNormalizer(static function (Options $options, $value) { - if (\PHP_VERSION_ID < 70100 && in_array('const', $value, true)) { + if (\PHP_VERSION_ID < 70100 && \in_array('const', $value, true)) { throw new InvalidOptionsForEnvException('"const" option can only be enabled with PHP 7.1+.'); } @@ -123,186 +100,94 @@ protected function createConfigurationDefinition() } /** - * @param Tokens $tokens - * @param int $index - */ - private function fixMethodVisibility(Tokens $tokens, $index) - { - $this->overrideAttribs($tokens, $index, $this->grabAttribsBeforeMethodToken($tokens, $index)); - - // force whitespace between function keyword and function name to be single space char - if ($tokens[$index + 1]->isWhitespace()) { - $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']); - } - } - - /** - * @param Tokens $tokens - * @param int $index - */ - private function fixPropertyVisibility(Tokens $tokens, $index) - { - $prevIndex = $tokens->getPrevTokenOfKind($index, [';', ',', '{']); - - if (null === $prevIndex || !$tokens[$prevIndex]->equals(',')) { - $this->overrideAttribs($tokens, $index, $this->grabAttribsBeforePropertyToken($tokens, $index)); - } - } - - /** - * @param Tokens $tokens - * @param int $index + * {@inheritdoc} */ - private function fixConstVisibility(Tokens $tokens, $index) + protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - $prev = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prev]->isGivenKind([T_PRIVATE, T_PROTECTED, T_PUBLIC])) { - return; - } + $tokensAnalyzer = new TokensAnalyzer($tokens); + $elements = $tokensAnalyzer->getClassyElements(); - $tokens->insertAt($index, new Token([T_WHITESPACE, ' '])); - $tokens->insertAt($index, new Token([T_PUBLIC, 'public'])); - } + foreach (array_reverse($elements, true) as $index => $element) { + if (!\in_array($element['type'], $this->configuration['elements'], true)) { + continue; + } - /** - * Grab attributes before method token at given index. - * - * It's a shorthand for grabAttribsBeforeToken method. - * - * @param Tokens $tokens Tokens collection - * @param int $index token index - * - * @return array map of grabbed attributes, key is attribute name and value is array of index and clone of Token - */ - private function grabAttribsBeforeMethodToken(Tokens $tokens, $index) - { - static $tokenAttribsMap = [ - T_PRIVATE => 'visibility', - T_PROTECTED => 'visibility', - T_PUBLIC => 'visibility', - T_ABSTRACT => 'abstract', - T_FINAL => 'final', - T_STATIC => 'static', - ]; + $abstractFinalIndex = null; + $visibilityIndex = null; + $staticIndex = null; + $prevIndex = $tokens->getPrevMeaningfulToken($index); + while ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR])) { + if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) { + $abstractFinalIndex = $prevIndex; + } elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) { + $staticIndex = $prevIndex; + } else { + $visibilityIndex = $prevIndex; + } + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } - return $this->grabAttribsBeforeToken( - $tokens, - $index, - $tokenAttribsMap, - [ - 'abstract' => null, - 'final' => null, - 'visibility' => ['index' => null, 'token' => new Token([T_PUBLIC, 'public'])], - 'static' => null, - ] - ); - } + if ($tokens[$prevIndex]->equals(',')) { + continue; + } - /** - * Apply token attributes. - * - * Token at given index is prepended by attributes. - * - * @param Tokens $tokens Tokens collection - * @param int $memberIndex token index - * @param array $attribs map of grabbed attributes, key is attribute name and value is array of index and clone of Token - */ - private function overrideAttribs(Tokens $tokens, $memberIndex, array $attribs) - { - $toOverride = []; - $firstAttribIndex = $memberIndex; + if (null !== $staticIndex) { + if ($this->isKeywordPlacedProperly($tokens, $staticIndex, $index)) { + $index = $staticIndex; + } else { + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $staticIndex, $index); + } + } - foreach ($attribs as $attrib) { - if (null === $attrib) { - continue; + if (null === $visibilityIndex) { + $tokens->insertAt($index, [new Token([T_PUBLIC, 'public']), new Token([T_WHITESPACE, ' '])]); + } else { + if ($tokens[$visibilityIndex]->isGivenKind(T_VAR)) { + $tokens[$visibilityIndex] = new Token([T_PUBLIC, 'public']); + } + if ($this->isKeywordPlacedProperly($tokens, $visibilityIndex, $index)) { + $index = $visibilityIndex; + } else { + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilityIndex, $index); + } } - if (null !== $attrib['index']) { - $firstAttribIndex = min($firstAttribIndex, $attrib['index']); + if (null === $abstractFinalIndex) { + continue; } - if (!$attrib['token']->isGivenKind(T_VAR) && '' !== $attrib['token']->getContent()) { - $toOverride[] = $attrib['token']; - $toOverride[] = new Token([T_WHITESPACE, ' ']); + if ($this->isKeywordPlacedProperly($tokens, $abstractFinalIndex, $index)) { + continue; } - } - if (!empty($toOverride)) { - $tokens->overrideRange($firstAttribIndex, $memberIndex - 1, $toOverride); + $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $abstractFinalIndex, $index); } } /** - * Grab attributes before property token at given index. - * - * It's a shorthand for grabAttribsBeforeToken method. - * - * @param Tokens $tokens Tokens collection - * @param int $index token index + * @param Tokens $tokens + * @param int $keywordIndex + * @param int $comparedIndex * - * @return array map of grabbed attributes, key is attribute name and value is array of index and clone of Token + * @return bool */ - private function grabAttribsBeforePropertyToken(Tokens $tokens, $index) + private function isKeywordPlacedProperly(Tokens $tokens, $keywordIndex, $comparedIndex) { - static $tokenAttribsMap = [ - T_VAR => 'var', - T_PRIVATE => 'visibility', - T_PROTECTED => 'visibility', - T_PUBLIC => 'visibility', - T_STATIC => 'static', - ]; - - return $this->grabAttribsBeforeToken( - $tokens, - $index, - $tokenAttribsMap, - [ - 'visibility' => ['index' => null, 'token' => new Token([T_PUBLIC, 'public'])], - 'static' => null, - ] - ); + return $keywordIndex + 2 === $comparedIndex && ' ' === $tokens[$keywordIndex + 1]->getContent(); } /** - * Grab info about attributes before token at given index. - * - * @param Tokens $tokens Tokens collection - * @param int $index token index - * @param array $tokenAttribsMap token to attribute name map - * @param array $attribs array of token attributes - * - * @return array map of grabbed attributes, key is attribute name and value is array of index and clone of Token + * @param Tokens $tokens + * @param int $fromIndex + * @param int $toIndex */ - private function grabAttribsBeforeToken(Tokens $tokens, $index, array $tokenAttribsMap, array $attribs) + private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, $fromIndex, $toIndex) { - while (true) { - $token = $tokens[--$index]; - - if (!$token->isArray()) { - if ($token->equalsAny(['{', '}', '(', ')'])) { - break; - } + $tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([T_WHITESPACE, ' '])]); - continue; - } - - // if token is attribute, set token attribute name - if (isset($tokenAttribsMap[$token->getId()])) { - $attribs[$tokenAttribsMap[$token->getId()]] = [ - 'token' => clone $token, - 'index' => $index, - ]; - - continue; - } - - if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { - continue; - } - - break; + $tokens->clearAt($fromIndex); + if ($tokens[$fromIndex + 1]->isWhitespace()) { + $tokens->clearAt($fromIndex + 1); } - - return $attribs; } } diff --git a/src/Fixer/ClassUsage/DateTimeImmutableFixer.php b/src/Fixer/ClassUsage/DateTimeImmutableFixer.php index b7570829416..f5012864c38 100644 --- a/src/Fixer/ClassUsage/DateTimeImmutableFixer.php +++ b/src/Fixer/ClassUsage/DateTimeImmutableFixer.php @@ -134,7 +134,7 @@ private function fixClassUsage(Tokens $tokens, $index, $isInNamespace, $isImport } if ($isUsedWithLeadingBackslash || $isUsedAlone && ($isInNamespace && $isImported || !$isInNamespace)) { - $tokens[$index] = new Token([T_STRING, 'DateTimeImmutable']); + $tokens[$index] = new Token([T_STRING, \DateTimeImmutable::class]); if ($isInNamespace && $isUsedAlone) { $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); } diff --git a/src/Fixer/Comment/CommentToPhpdocFixer.php b/src/Fixer/Comment/CommentToPhpdocFixer.php index 3417aeea0fc..db7e662fdf7 100644 --- a/src/Fixer/Comment/CommentToPhpdocFixer.php +++ b/src/Fixer/Comment/CommentToPhpdocFixer.php @@ -72,7 +72,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $commentsAnalyzer = new CommentsAnalyzer(); - for ($index = 0, $limit = count($tokens); $index < $limit; ++$index) { + for ($index = 0, $limit = \count($tokens); $index < $limit; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_COMMENT)) { @@ -120,7 +120,7 @@ function ($carry, $index) use ($tokens) { */ private function fixComment(Tokens $tokens, $indices) { - if (1 === count($indices)) { + if (1 === \count($indices)) { $this->fixCommentSingleLine($tokens, reset($indices)); } else { $this->fixCommentMultiLine($tokens, $indices); @@ -155,18 +155,26 @@ private function fixCommentMultiLine(Tokens $tokens, array $indices) $startIndex = reset($indices); $indent = Utils::calculateTrailingWhitespaceIndent($tokens[$startIndex - 1]); - $content = '/**'.$this->whitespacesConfig->getLineEnding(); + $newContent = '/**'.$this->whitespacesConfig->getLineEnding(); $count = max($indices); for ($index = $startIndex; $index <= $count; ++$index) { - if ($tokens[$index]->isComment()) { - $content .= $indent.' *'.$this->getMessage($tokens[$index]->getContent()).$this->whitespacesConfig->getLineEnding(); + if (!$tokens[$index]->isComment()) { + continue; + } + if (false !== strpos($tokens[$index]->getContent(), '*/')) { + return; } + $newContent .= $indent.' *'.$this->getMessage($tokens[$index]->getContent()).$this->whitespacesConfig->getLineEnding(); + } + + for ($index = $startIndex; $index <= $count; ++$index) { $tokens->clearAt($index); } - $content .= $indent.' */'; - $tokens->insertAt($startIndex, new Token([T_DOC_COMMENT, $content])); + $newContent .= $indent.' */'; + + $tokens->insertAt($startIndex, new Token([T_DOC_COMMENT, $newContent])); } private function getMessage($content) diff --git a/src/Fixer/Comment/HeaderCommentFixer.php b/src/Fixer/Comment/HeaderCommentFixer.php index d8248d7d6ff..213f40732f6 100644 --- a/src/Fixer/Comment/HeaderCommentFixer.php +++ b/src/Fixer/Comment/HeaderCommentFixer.php @@ -13,6 +13,7 @@ namespace PhpCsFixer\Fixer\Comment; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AliasedFixerOptionBuilder; @@ -102,6 +103,19 @@ public function isCandidate(Tokens $tokens) return $tokens[0]->isGivenKind(T_OPEN_TAG) && $tokens->isMonolithicPhp(); } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should be run after the NoBlankLinesAfterPhpdocFixer. + // + // When this fixer is configured with ["separate" => "bottom", "commentType" => "PHPDoc"] + // and the target file has no namespace or declare() construct, + // the fixed header comment gets trimmed by NoBlankLinesAfterPhpdocFixer if we run before it. + return -30; + } + /** * {@inheritdoc} */ @@ -138,14 +152,20 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) */ protected function createConfigurationDefinition() { + $fixerName = $this->getName(); + return new FixerConfigurationResolver([ (new FixerOptionBuilder('header', 'Proper header content.')) ->setAllowedTypes(['string']) - ->setNormalizer(static function (Options $options, $value) { + ->setNormalizer(static function (Options $options, $value) use ($fixerName) { if ('' === trim($value)) { return ''; } + if (false !== strpos($value, '*/')) { + throw new InvalidFixerConfigurationException($fixerName, 'Cannot use \'*/\' in header.'); + } + return $value; }) ->getOption(), @@ -265,11 +285,11 @@ private function fixWhiteSpaceAroundHeader(Tokens $tokens, $headerIndex) // fix lines after header comment $expectedLineCount = 'both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate'] ? 2 : 1; - if ($headerIndex === count($tokens) - 1) { + if ($headerIndex === \count($tokens) - 1) { $tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount)])); } else { $afterCommentIndex = $tokens->getNextNonWhitespace($headerIndex); - $lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex + 1, null === $afterCommentIndex ? count($tokens) : $afterCommentIndex); + $lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex + 1, null === $afterCommentIndex ? \count($tokens) : $afterCommentIndex); if ($lineBreakCount < $expectedLineCount) { $missing = str_repeat($lineEnding, $expectedLineCount - $lineBreakCount); if ($tokens[$headerIndex + 1]->isWhitespace()) { diff --git a/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php b/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php index d489ccce840..0d79ec63286 100644 --- a/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php +++ b/src/Fixer/Comment/MultilineCommentOpeningClosingFixer.php @@ -33,7 +33,7 @@ public function getDefinition() 'DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash.', [ new CodeSample( -<<<'EOT' + <<<'EOT' isGivenKind(T_COMMENT)) { - $newContent = Preg::replace('/^\\/\\*\\*+/', '/*', $newContent); + $newContent = Preg::replace('/^\\/\\*{2,}(?!\\/)/', '/*', $newContent); } // Fix closing - $newContent = Preg::replace('/\\*+\\*\\/$/', '*/', $newContent); + $newContent = Preg::replace('/(?getId(), $newContent]); diff --git a/src/Fixer/Comment/NoEmptyCommentFixer.php b/src/Fixer/Comment/NoEmptyCommentFixer.php index c566318bcbd..e6b2a605340 100644 --- a/src/Fixer/Comment/NoEmptyCommentFixer.php +++ b/src/Fixer/Comment/NoEmptyCommentFixer.php @@ -71,7 +71,7 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - for ($index = 1, $count = count($tokens); $index < $count; ++$index) { + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { if (!$tokens[$index]->isGivenKind(T_COMMENT)) { continue; } @@ -100,7 +100,7 @@ private function getCommentBlock(Tokens $tokens, $index) $commentType = $this->getCommentType($tokens[$index]->getContent()); $empty = $this->isEmptyComment($tokens[$index]->getContent()); $start = $index; - $count = count($tokens); + $count = \count($tokens); ++$index; for (; $index < $count; ++$index) { diff --git a/src/Fixer/Comment/SingleLineCommentStyleFixer.php b/src/Fixer/Comment/SingleLineCommentStyleFixer.php index 10a594a5111..422cf616dda 100644 --- a/src/Fixer/Comment/SingleLineCommentStyleFixer.php +++ b/src/Fixer/Comment/SingleLineCommentStyleFixer.php @@ -45,8 +45,8 @@ public function configure(array $configuration) { parent::configure($configuration); - $this->asteriskEnabled = in_array('asterisk', $this->configuration['comment_types'], true); - $this->hashEnabled = in_array('hash', $this->configuration['comment_types'], true); + $this->asteriskEnabled = \in_array('asterisk', $this->configuration['comment_types'], true); + $this->hashEnabled = \in_array('hash', $this->configuration['comment_types'], true); } /** diff --git a/src/Fixer/ConfigurableFixerInterface.php b/src/Fixer/ConfigurableFixerInterface.php index 67985a52636..55ffe1a85a1 100644 --- a/src/Fixer/ConfigurableFixerInterface.php +++ b/src/Fixer/ConfigurableFixerInterface.php @@ -13,6 +13,7 @@ namespace PhpCsFixer\Fixer; use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; /** * @author Dariusz Rumiński diff --git a/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php b/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php index 2a6ba4937bc..a2e47b25ef4 100644 --- a/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php +++ b/src/Fixer/ConstantNotation/NativeConstantInvocationFixer.php @@ -18,6 +18,8 @@ use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -48,6 +50,17 @@ public function getDefinition() 'Add leading `\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`.', [ new CodeSample(' 'namespaced'] + ), new CodeSample( 'configuration['exclude']); + $uniqueConfiguredExclude = array_unique($this->configuration['exclude']); // Case sensitive constants handling - $constantsToEscape = \array_values($this->configuration['include']); + $constantsToEscape = array_values($this->configuration['include']); if (true === $this->configuration['fix_built_in']) { - $getDefinedConstants = \get_defined_constants(true); + $getDefinedConstants = get_defined_constants(true); unset($getDefinedConstants['user']); foreach ($getDefinedConstants as $constants) { - $constantsToEscape = \array_merge($constantsToEscape, \array_keys($constants)); + $constantsToEscape = array_merge($constantsToEscape, array_keys($constants)); } } - $constantsToEscape = \array_diff( - \array_unique($constantsToEscape), + $constantsToEscape = array_diff( + array_unique($constantsToEscape), $uniqueConfiguredExclude ); @@ -122,24 +135,24 @@ public function configure(array $configuration = null) static $caseInsensitiveConstants = ['null', 'false', 'true']; $caseInsensitiveConstantsToEscape = []; foreach ($constantsToEscape as $constantIndex => $constant) { - $loweredConstant = \strtolower($constant); + $loweredConstant = strtolower($constant); if (\in_array($loweredConstant, $caseInsensitiveConstants, true)) { $caseInsensitiveConstantsToEscape[] = $loweredConstant; unset($constantsToEscape[$constantIndex]); } } - $caseInsensitiveConstantsToEscape = \array_diff( - \array_unique($caseInsensitiveConstantsToEscape), - \array_map('strtolower', $uniqueConfiguredExclude) + $caseInsensitiveConstantsToEscape = array_diff( + array_unique($caseInsensitiveConstantsToEscape), + array_map(function ($function) { return strtolower($function); }, $uniqueConfiguredExclude) ); // Store the cache - $this->constantsToEscape = \array_fill_keys($constantsToEscape, true); - \ksort($this->constantsToEscape); + $this->constantsToEscape = array_fill_keys($constantsToEscape, true); + ksort($this->constantsToEscape); - $this->caseInsensitiveConstantsToEscape = \array_fill_keys($caseInsensitiveConstantsToEscape, true); - \ksort($this->caseInsensitiveConstantsToEscape); + $this->caseInsensitiveConstantsToEscape = array_fill_keys($caseInsensitiveConstantsToEscape, true); + ksort($this->caseInsensitiveConstantsToEscape); } /** @@ -147,48 +160,22 @@ public function configure(array $configuration = null) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); - $useConstantDeclarations = []; - foreach ($useDeclarations as $use) { - if ($use->isConstant()) { - $useConstantDeclarations[$use->getShortName()] = true; - } - } - - $tokenAnalyzer = new TokensAnalyzer($tokens); - - $indexes = []; - foreach ($tokens as $index => $token) { - // test if we are at a constant call - if (!$token->isGivenKind(T_STRING)) { - continue; - } - - $tokenContent = $token->getContent(); - - if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) { - continue; - } + if ('all' === $this->configuration['scope']) { + $this->fixConstantInvocations($tokens, 0, \count($tokens) - 1); - if (isset($useConstantDeclarations[$tokenContent])) { - continue; - } + return; + } - $prevIndex = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { - continue; - } + $namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens); - if (!$tokenAnalyzer->isConstantInvocation($index)) { + // 'scope' is 'namespaced' here + /** @var NamespaceAnalysis $namespace */ + foreach (array_reverse($namespaces) as $namespace) { + if ('' === $namespace->getFullName()) { continue; } - $indexes[] = $index; - } - - $indexes = \array_reverse($indexes); - foreach ($indexes as $index) { - $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + $this->fixConstantInvocations($tokens, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex()); } } @@ -199,8 +186,8 @@ protected function createConfigurationDefinition() { $constantChecker = static function ($value) { foreach ($value as $constantName) { - if (!\is_string($constantName) || '' === \trim($constantName) || \trim($constantName) !== $constantName) { - throw new InvalidOptionsException(\sprintf( + if (!\is_string($constantName) || '' === trim($constantName) || trim($constantName) !== $constantName) { + throw new InvalidOptionsException(sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', \is_object($constantName) ? \get_class($constantName) : \gettype($constantName) )); @@ -225,6 +212,64 @@ protected function createConfigurationDefinition() ->setAllowedValues([$constantChecker]) ->setDefault(['null', 'false', 'true']) ->getOption(), + (new FixerOptionBuilder('scope', 'Only fix constant invocations that are made within a namespace or fix all.')) + ->setAllowedValues(['all', 'namespaced']) + ->setDefault('all') + ->getOption(), ]); } + + /** + * @param Tokens $tokens + * @param int $start + * @param int $end + */ + private function fixConstantInvocations(Tokens $tokens, $start, $end) + { + $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); + $useConstantDeclarations = []; + foreach ($useDeclarations as $use) { + if ($use->isConstant()) { + $useConstantDeclarations[$use->getShortName()] = true; + } + } + + $tokenAnalyzer = new TokensAnalyzer($tokens); + + $indexes = []; + for ($index = $start; $index < $end; ++$index) { + $token = $tokens[$index]; + + // test if we are at a constant call + if (!$token->isGivenKind(T_STRING)) { + continue; + } + + $tokenContent = $token->getContent(); + + if (!isset($this->constantsToEscape[$tokenContent]) && !isset($this->caseInsensitiveConstantsToEscape[strtolower($tokenContent)])) { + continue; + } + + if (isset($useConstantDeclarations[$tokenContent])) { + continue; + } + + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + if (!$tokenAnalyzer->isConstantInvocation($index)) { + continue; + } + + $indexes[] = $index; + } + + $indexes = array_reverse($indexes); + foreach ($indexes as $index) { + $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); + } + } } diff --git a/src/Fixer/ControlStructure/IncludeFixer.php b/src/Fixer/ControlStructure/IncludeFixer.php index d53a261112f..46bcada13fb 100644 --- a/src/Fixer/ControlStructure/IncludeFixer.php +++ b/src/Fixer/ControlStructure/IncludeFixer.php @@ -33,7 +33,7 @@ public function getDefinition() 'Include/Require and file path should be divided with a single space. File path should not be placed under brackets.', [ new CodeSample( -'fixElseif($index, $token, $tokens); $this->fixElse($index, $token, $tokens); @@ -209,7 +209,7 @@ private function addBraces(Tokens $tokens, Token $token, $index, $colonIndex) ); // increment the position of the colon by number of items inserted - $colonIndex += count($items); + $colonIndex += \count($items); $items = [new Token('{')]; if (!$tokens[$colonIndex + 1]->isWhitespace()) { diff --git a/src/Fixer/ControlStructure/NoBreakCommentFixer.php b/src/Fixer/ControlStructure/NoBreakCommentFixer.php index 227c7c42e1d..333d9d513a9 100644 --- a/src/Fixer/ControlStructure/NoBreakCommentFixer.php +++ b/src/Fixer/ControlStructure/NoBreakCommentFixer.php @@ -75,7 +75,7 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['string']) ->setAllowedValues([ function ($value) { - if (is_string($value) && Preg::match('/\R/', $value)) { + if (\is_string($value) && Preg::match('/\R/', $value)) { throw new InvalidOptionsException('The comment text must not contain new lines.'); } @@ -95,7 +95,7 @@ function ($value) { */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - for ($position = count($tokens) - 1; $position >= 0; --$position) { + for ($position = \count($tokens) - 1; $position >= 0; --$position) { if ($tokens[$position]->isGivenKind([T_CASE, T_DEFAULT])) { $this->fixCase($tokens, $position); } @@ -111,7 +111,7 @@ private function fixCase(Tokens $tokens, $casePosition) $empty = true; $fallThrough = true; $commentPosition = null; - for ($i = $tokens->getNextTokenOfKind($casePosition, [':', ';']) + 1, $max = count($tokens); $i < $max; ++$i) { + for ($i = $tokens->getNextTokenOfKind($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])) { $empty = false; $i = $this->getStructureEnd($tokens, $i); @@ -335,7 +335,7 @@ private function getStructureEnd(Tokens $tokens, $position) Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']) ); - } elseif ($initialToken->isGivenKind([T_CLASS])) { + } elseif ($initialToken->isGivenKind(T_CLASS)) { $openParenthesisPosition = $tokens->getNextMeaningfulToken($position); if ('(' === $tokens[$openParenthesisPosition]->getContent()) { $position = $tokens->findBlockEnd( @@ -352,7 +352,7 @@ private function getStructureEnd(Tokens $tokens, $position) $position = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $position); - if ($initialToken->isGivenKind([T_DO])) { + if ($initialToken->isGivenKind(T_DO)) { $position = $tokens->findBlockEnd( Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextTokenOfKind($position, ['(']) diff --git a/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php b/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php index 7145bd6b8a2..2b9db5251b9 100644 --- a/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php +++ b/src/Fixer/ControlStructure/NoUnneededControlParenthesesFixer.php @@ -47,7 +47,7 @@ public function __construct() parent::__construct(); // To be moved back to compile time property declaration when PHP support of PHP CS Fixer will be 7.0+ - if (defined('T_COALESCE')) { + if (\defined('T_COALESCE')) { self::$loops['clone']['forbiddenContents'][] = [T_COALESCE, '??']; } } @@ -146,7 +146,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } - if (array_key_exists('forbiddenContents', $loop)) { + if (\array_key_exists('forbiddenContents', $loop)) { $forbiddenTokenIndex = $tokens->getNextTokenOfKind($blockStartIndex, $loop['forbiddenContents']); // A forbidden token is found and is inside the parenthesis. if (null !== $forbiddenTokenIndex && $forbiddenTokenIndex < $blockEndIndex) { diff --git a/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php b/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php index 5b6344b57f7..7e95623b76e 100644 --- a/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php +++ b/src/Fixer/ControlStructure/NoUnneededCurlyBracesFixer.php @@ -88,7 +88,7 @@ private function clearOverCompleteBraces(Tokens $tokens, $openIndex, $closeIndex private function findCurlyBraceOpen(Tokens $tokens) { - for ($i = count($tokens) - 1; $i > 0; --$i) { + for ($i = \count($tokens) - 1; $i > 0; --$i) { if ($tokens[$i]->equals('{')) { yield $i; } diff --git a/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php b/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php index fee92741716..2c839b753ab 100644 --- a/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php +++ b/src/Fixer/ControlStructure/SwitchCaseSemicolonToColonFixer.php @@ -34,7 +34,7 @@ public function getDefinition() 'A case should be followed by a colon and not a semicolon.', [ new CodeSample( -'isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { @@ -241,12 +241,12 @@ private function findComparisonStart(Tokens $tokens, $index) */ private function fixTokens(Tokens $tokens) { - for ($i = count($tokens) - 1; $i > 1; --$i) { + for ($i = \count($tokens) - 1; $i > 1; --$i) { if ($tokens[$i]->isGivenKind($this->candidateTypes)) { $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getId()]; } elseif ( - ($tokens[$i]->equals('<') && in_array('<', $this->candidateTypes, true)) - || ($tokens[$i]->equals('>') && in_array('>', $this->candidateTypes, true)) + ($tokens[$i]->equals('<') && \in_array('<', $this->candidateTypes, true)) + || ($tokens[$i]->equals('>') && \in_array('>', $this->candidateTypes, true)) ) { $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getContent()]; } else { @@ -301,9 +301,9 @@ private function fixTokensCompare( ) { $type = $tokens[$compareOperatorIndex]->getId(); $content = $tokens[$compareOperatorIndex]->getContent(); - if (array_key_exists($type, $this->candidatesMap)) { + if (\array_key_exists($type, $this->candidatesMap)) { $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$type]; - } elseif (array_key_exists($content, $this->candidatesMap)) { + } elseif (\array_key_exists($content, $this->candidatesMap)) { $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$content]; } @@ -335,7 +335,7 @@ private function fixTokensComparePart(Tokens $tokens, $start, $end) { $newTokens = $tokens->generatePartialCode($start, $end); $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('clearAt(count($newTokens) - 1); + $newTokens->clearAt(\count($newTokens) - 1); $newTokens->clearAt(0); $newTokens->clearEmptyTokens(); @@ -476,11 +476,11 @@ private function isOfLowerPrecedence(Token $token) T_OPEN_TAG_WITH_ECHO, ]; - if (defined('T_POW_EQUAL')) { + if (\defined('T_POW_EQUAL')) { $tokens[] = T_POW_EQUAL; // **= } - if (defined('T_COALESCE')) { + if (\defined('T_COALESCE')) { $tokens[] = T_COALESCE; // ?? } } diff --git a/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php b/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php index 89a9241d97f..08d84639415 100644 --- a/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php +++ b/src/Fixer/DoctrineAnnotation/DoctrineAnnotationBracesFixer.php @@ -98,7 +98,7 @@ private function addBracesToAnnotations(Tokens $tokens) */ private function removesBracesFromAnnotations(Tokens $tokens) { - for ($index = 0, $max = count($tokens); $index < $max; ++$index) { + for ($index = 0, $max = \count($tokens); $index < $max; ++$index) { if (!$tokens[$index]->isType(DocLexer::T_AT)) { continue; } diff --git a/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php b/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php index 30469f26750..6c501edffad 100644 --- a/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php +++ b/src/Fixer/DoctrineAnnotation/DoctrineAnnotationIndentationFixer.php @@ -62,7 +62,7 @@ protected function createConfigurationDefinition() protected function fixAnnotations(Tokens $tokens) { $annotationPositions = []; - for ($index = 0, $max = count($tokens); $index < $max; ++$index) { + for ($index = 0, $max = \count($tokens); $index < $max; ++$index) { if (!$tokens[$index]->isType(DocLexer::T_AT)) { continue; } @@ -187,7 +187,7 @@ private function indentationCanBeFixed(Tokens $tokens, $newLineTokenIndex, array } } - for ($index = $newLineTokenIndex + 1, $max = count($tokens); $index < $max; ++$index) { + for ($index = $newLineTokenIndex + 1, $max = \count($tokens); $index < $max; ++$index) { $token = $tokens[$index]; if (false !== strpos($token->getContent(), "\n")) { diff --git a/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php b/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php index 0a7556857c3..1ef06a36be8 100644 --- a/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php +++ b/src/Fixer/DoctrineAnnotation/DoctrineAnnotationSpacesFixer.php @@ -200,7 +200,7 @@ private function fixSpacesAroundCommas(Tokens $tokens) $token->clear(); } - if ($index < count($tokens) - 1 && !Preg::match('/^\s/', $tokens[$index + 1]->getContent())) { + if ($index < \count($tokens) - 1 && !Preg::match('/^\s/', $tokens[$index + 1]->getContent())) { $tokens->insertAt($index + 1, new Token(DocLexer::T_NONE, ' ')); } } diff --git a/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php b/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php new file mode 100644 index 00000000000..3d0219e14d8 --- /dev/null +++ b/src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php @@ -0,0 +1,240 @@ + + * 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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class CombineNestedDirnameFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.', + [ + new VersionSpecificCodeSample( + "= 70000 && $tokens->isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should run after DirConstantFixer + // should run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer + return 3; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = $tokens->count() - 1; 0 <= $index; --$index) { + $token = $tokens[$index]; + + if (!$token->equals([T_STRING, 'dirname'], false)) { + continue; + } + + $dirnameInfo = $this->getDirnameInfo($tokens, $index); + + if (!$dirnameInfo) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); + + if (!$tokens[$prev]->equals('(')) { + continue; + } + + $prev = $tokens->getPrevMeaningfulToken($prev); + + $firstArgumentEnd = $dirnameInfo['end']; + + $dirnameInfoArray = [$dirnameInfo]; + + while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) { + $dirnameInfoArray[] = $dirnameInfo; + + $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); + + if (!$tokens[$prev]->equals('(')) { + break; + } + + $prev = $tokens->getPrevMeaningfulToken($prev); + $firstArgumentEnd = $dirnameInfo['end']; + } + + if (\count($dirnameInfoArray) > 1) { + $this->combineDirnames($tokens, $dirnameInfoArray); + } + + $index = $prev; + } + } + + /** + * @param Tokens $tokens + * @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 + */ + private function getDirnameInfo(Tokens $tokens, $index, $firstArgumentEndIndex = null) + { + if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) { + return false; + } + + if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) { + return false; + } + + $info['indexes'] = []; + + $prev = $tokens->getPrevMeaningfulToken($index); + + if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { + $info['indexes'][] = $prev; + } + + $info['indexes'][] = $index; + + // opening parenthesis "(" + $next = $tokens->getNextMeaningfulToken($index); + $info['indexes'][] = $next; + + if (null !== $firstArgumentEndIndex) { + $next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex); + } else { + $next = $tokens->getNextMeaningfulToken($next); + + if ($tokens[$next]->equals(')')) { + return false; + } + + while (!$tokens[$next]->equalsAny([',', ')'])) { + $blockType = Tokens::detectBlockType($tokens[$next]); + + if ($blockType) { + $next = $tokens->findBlockEnd($blockType['type'], $next); + } + + $next = $tokens->getNextMeaningfulToken($next); + } + } + + $info['indexes'][] = $next; + + if ($tokens[$next]->equals(',')) { + $next = $tokens->getNextMeaningfulToken($next); + $info['indexes'][] = $next; + } + + if ($tokens[$next]->equals(')')) { + $info['levels'] = 1; + $info['end'] = $next; + + return $info; + } + + if (!$tokens[$next]->isGivenKind(T_LNUMBER)) { + return false; + } + + $info['secondArgument'] = $next; + $info['levels'] = (int) $tokens[$next]->getContent(); + + $next = $tokens->getNextMeaningfulToken($next); + + if ($tokens[$next]->equals(',')) { + $info['indexes'][] = $next; + $next = $tokens->getNextMeaningfulToken($next); + } + + if (!$tokens[$next]->equals(')')) { + return false; + } + + $info['indexes'][] = $next; + $info['end'] = $next; + + return $info; + } + + private function combineDirnames(Tokens $tokens, array $dirnameInfoArray) + { + $outerDirnameInfo = array_pop($dirnameInfoArray); + $levels = $outerDirnameInfo['levels']; + + foreach ($dirnameInfoArray as $dirnameInfo) { + $levels += $dirnameInfo['levels']; + + foreach ($dirnameInfo['indexes'] as $index) { + $tokens->removeLeadingWhitespace($index); + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + } + } + + $levelsToken = new Token([T_LNUMBER, (string) $levels]); + + if (isset($outerDirnameInfo['secondArgument'])) { + $tokens[$outerDirnameInfo['secondArgument']] = $levelsToken; + } else { + $prev = $tokens->getPrevMeaningfulToken($outerDirnameInfo['end']); + $items = []; + if (!$tokens[$prev]->equals(',')) { + $items[] = new Token(','); + } + $items[] = $levelsToken; + $tokens->insertAt($outerDirnameInfo['end'], $items); + } + } +} diff --git a/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php b/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php new file mode 100644 index 00000000000..37d6effba64 --- /dev/null +++ b/src/Fixer/FunctionNotation/FopenFlagOrderFixer.php @@ -0,0 +1,131 @@ + + * 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\FunctionNotation; + +use PhpCsFixer\AbstractFopenFlagFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class FopenFlagOrderFixer extends AbstractFopenFlagFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Order the flags in `fopen` calls, `b` and `t` must be last.', + [new CodeSample("isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + continue; + } + + if (null !== $argumentFlagIndex) { + return; // multiple meaningful tokens found, no candidate for fixing + } + + $argumentFlagIndex = $i; + } + + // check if second argument is candidate + if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + return; + } + + $content = $tokens[$argumentFlagIndex]->getContent(); + $contentQuote = $content[0]; // `'`, `"`, `b` or `B` + + if ('b' === $contentQuote || 'B' === $contentQuote) { + $binPrefix = $contentQuote; + $contentQuote = $content[1]; // `'` or `"` + $mode = substr($content, 2, -1); + } else { + $binPrefix = ''; + $mode = substr($content, 1, -1); + } + + $modeLength = \strlen($mode); + if ($modeLength < 2) { + return; // nothing to sort + } + + if (false === $this->isValidModeString($mode)) { + return; + } + + $split = $this->sortFlags(Preg::split('#([^\+]\+?)#', $mode, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)); + $newContent = $binPrefix.$contentQuote.implode('', $split).$contentQuote; + + if ($content !== $newContent) { + $tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]); + } + } + + /** + * @param string[] $flags + * + * @return string[] + */ + private function sortFlags(array $flags) + { + usort( + $flags, + static function ($flag1, $flag2) { + if ($flag1 === $flag2) { + return 0; + } + + if ('b' === $flag1) { + return 1; + } + + if ('b' === $flag2) { + return -1; + } + + if ('t' === $flag1) { + return 1; + } + + if ('t' === $flag2) { + return -1; + } + + return $flag1 < $flag2 ? -1 : 1; + } + ); + + return $flags; + } +} diff --git a/src/Fixer/FunctionNotation/FopenFlagsFixer.php b/src/Fixer/FunctionNotation/FopenFlagsFixer.php new file mode 100644 index 00000000000..27330b36e49 --- /dev/null +++ b/src/Fixer/FunctionNotation/FopenFlagsFixer.php @@ -0,0 +1,112 @@ + + * 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\FunctionNotation; + +use PhpCsFixer\AbstractFopenFlagFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author SpacePossum + */ +final class FopenFlagsFixer extends AbstractFopenFlagFixer implements ConfigurableFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'The flags in `fopen` calls must omit `t`, and `b` must be omitted or included consistently.', + [new CodeSample("setAllowedTypes(['bool']) + ->setDefault(true) + ->getOption(), + ]); + } + + /** + * @param Tokens $tokens + * @param int $argumentStartIndex + * @param int $argumentEndIndex + */ + protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex) + { + $argumentFlagIndex = null; + + for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) { + if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) { + continue; + } + + if (null !== $argumentFlagIndex) { + return; // multiple meaningful tokens found, no candidate for fixing + } + + $argumentFlagIndex = $i; + } + + // check if second argument is candidate + if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + return; + } + + $content = $tokens[$argumentFlagIndex]->getContent(); + $contentQuote = $content[0]; // `'`, `"`, `b` or `B` + + if ('b' === $contentQuote || 'B' === $contentQuote) { + $binPrefix = $contentQuote; + $contentQuote = $content[1]; // `'` or `"` + $mode = substr($content, 2, -1); + } else { + $binPrefix = ''; + $mode = substr($content, 1, -1); + } + + if (false === $this->isValidModeString($mode)) { + return; + } + + $mode = str_replace('t', '', $mode); + if ($this->configuration['b_mode']) { + if (false === strpos($mode, 'b')) { + $mode .= 'b'; + } + } else { + $mode = str_replace('b', '', $mode); + } + + $newContent = $binPrefix.$contentQuote.$mode.$contentQuote; + + if ($content !== $newContent) { + $tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]); + } + } +} diff --git a/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php b/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php index ca6fcf22f15..7c8916fa0f6 100644 --- a/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php +++ b/src/Fixer/FunctionNotation/FunctionDeclarationFixer.php @@ -60,7 +60,7 @@ public function getDefinition() 'Spaces should be properly placed in a function declaration.', [ new CodeSample( -' self::SPACING_NONE] diff --git a/src/Fixer/FunctionNotation/ImplodeCallFixer.php b/src/Fixer/FunctionNotation/ImplodeCallFixer.php new file mode 100644 index 00000000000..639e3ac39e1 --- /dev/null +++ b/src/Fixer/FunctionNotation/ImplodeCallFixer.php @@ -0,0 +1,150 @@ + + * 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\FunctionNotation; + +use PhpCsFixer\AbstractFixer; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class ImplodeCallFixer extends AbstractFixer +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Function `implode` must be called with 2 arguments in the documented order.', + [ + new CodeSample("isTokenKindFound(T_STRING); + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + // must be run after NoAliasFunctionsFixer + // must be run before MethodArgumentSpaceFixer + return -1; + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $functionsAnalyzer = new FunctionsAnalyzer(); + + foreach ($tokens as $index => $token) { + if (!$tokens[$index]->equals([T_STRING, 'implode'], false)) { + continue; + } + + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { + continue; + } + + $argumentsIndices = $this->getArgumentIndices($tokens, $index); + + if (1 === \count($argumentsIndices)) { + $firstArgumentIndex = key($argumentsIndices); + $tokens->insertAt($firstArgumentIndex, [ + new Token([T_CONSTANT_ENCAPSED_STRING, "''"]), + new Token(','), + new Token([T_WHITESPACE, ' ']), + ]); + + continue; + } + + if (2 === \count($argumentsIndices)) { + list($firstArgumentIndex, $secondArgumentIndex) = array_keys($argumentsIndices); + + // If the first argument is string we have nothing to do + if ($tokens[$firstArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + // If the second argument is not string we cannot make a swap + if (!$tokens[$secondArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + + // collect tokens from first argument + $firstArgumentEndIndex = $argumentsIndices[key($argumentsIndices)]; + $newSecondArgumentTokens = []; + for ($i = key($argumentsIndices); $i <= $firstArgumentEndIndex; ++$i) { + $newSecondArgumentTokens[] = clone $tokens[$i]; + $tokens->clearAt($i); + } + + $tokens->insertAt($firstArgumentIndex, clone $tokens[$secondArgumentIndex]); + + // insert above increased the second argument index + ++$secondArgumentIndex; + $tokens->clearAt($secondArgumentIndex); + $tokens->insertAt($secondArgumentIndex, $newSecondArgumentTokens); + } + } + } + + /** + * @param Tokens $tokens + * @param int $functionNameIndex + * + * @return array In the format: startIndex => endIndex + */ + private function getArgumentIndices(Tokens $tokens, $functionNameIndex) + { + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']); + $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); + + $indices = []; + + foreach ($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis) as $startIndexCandidate => $endIndex) { + $indices[$tokens->getNextMeaningfulToken($startIndexCandidate - 1)] = $tokens->getPrevMeaningfulToken($endIndex + 1); + } + + return $indices; + } +} diff --git a/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php b/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php index 3a1abb35f2a..873da65f926 100644 --- a/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php +++ b/src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php @@ -96,6 +96,15 @@ public function configure(array $configuration = null) } } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // must be run after ImplodeCallFixer + return -2; + } + /** * {@inheritdoc} */ @@ -358,7 +367,7 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, $startFunctionInde */ private function fixNewline(Tokens $tokens, $index, $indentation, $override = true) { - if ($this->isNewline($tokens[$index + 1]) || $tokens[$index + 1]->isComment()) { + if ($tokens[$index + 1]->isComment()) { return; } diff --git a/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php b/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php index d00dc98daca..202caa06547 100644 --- a/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php +++ b/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php @@ -19,6 +19,7 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -73,7 +74,7 @@ public function getDefinition() 'Add leading `\` before function invocation to speed up resolving.', [ new CodeSample( -'configuration['scope']) { - $this->fixFunctionCalls($tokens, $this->functionFilter, 0, \count($tokens) - 1); + $this->fixFunctionCalls($tokens, $this->functionFilter, 0, \count($tokens) - 1, false); return; } @@ -190,12 +191,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) // 'scope' is 'namespaced' here /** @var NamespaceAnalysis $namespace */ - foreach (\array_reverse($namespaces) as $namespace) { - if ('' === $namespace->getFullName()) { - continue; - } - - $this->fixFunctionCalls($tokens, $this->functionFilter, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex()); + foreach (array_reverse($namespaces) as $namespace) { + $this->fixFunctionCalls($tokens, $this->functionFilter, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), '' === $namespace->getFullName()); } } @@ -209,8 +206,8 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $value) { foreach ($value as $functionName) { - if (!\is_string($functionName) || '' === \trim($functionName) || \trim($functionName) !== $functionName) { - throw new InvalidOptionsException(\sprintf( + if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { + throw new InvalidOptionsException(sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', \is_object($functionName) ? \get_class($functionName) : \gettype($functionName) )); @@ -225,8 +222,8 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['array']) ->setAllowedValues([static function (array $value) { foreach ($value as $functionName) { - if (!\is_string($functionName) || '' === \trim($functionName) || \trim($functionName) !== $functionName) { - throw new InvalidOptionsException(\sprintf( + if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) { + throw new InvalidOptionsException(sprintf( 'Each element must be a non-empty, trimmed string, got "%s" instead.', \is_object($functionName) ? \get_class($functionName) : \gettype($functionName) )); @@ -239,7 +236,7 @@ protected function createConfigurationDefinition() ]; if ('@' === $functionName[0] && !\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, implode('", "', $sets))); } } @@ -251,6 +248,10 @@ protected function createConfigurationDefinition() ->setAllowedValues(['all', 'namespaced']) ->setDefault('all') ->getOption(), + (new FixerOptionBuilder('strict', 'Whether leading `\` of function call not meant to have it should be removed.')) + ->setAllowedTypes(['bool']) + ->setDefault(false) // @TODO: 3.0 change to true as default + ->getOption(), ]); } @@ -259,44 +260,39 @@ protected function createConfigurationDefinition() * @param callable $functionFilter * @param int $start * @param int $end + * @param bool $tryToRemove */ - private function fixFunctionCalls(Tokens $tokens, callable $functionFilter, $start, $end) + private function fixFunctionCalls(Tokens $tokens, callable $functionFilter, $start, $end, $tryToRemove) { + $functionsAnalyzer = new FunctionsAnalyzer(); + $insertAtIndexes = []; for ($index = $start; $index < $end; ++$index) { - // test if we are at a function call - if (!$tokens[$index]->isGivenKind(T_STRING)) { + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { continue; } - if (!$tokens[$tokens->getNextMeaningfulToken($index)]->equals('(')) { - continue; - } - - $functionNamePrefix = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION])) { - continue; - } + $prevIndex = $tokens->getPrevMeaningfulToken($index); - if ( - $tokens[$functionNamePrefix]->isGivenKind(T_NS_SEPARATOR) - && $tokens[$tokens->getPrevMeaningfulToken($functionNamePrefix)]->isGivenKind([T_STRING, T_NEW]) - ) { - continue; // skip if the call is to a constructor or to a function in a namespace other than the default - } + if (!$functionFilter($tokens[$index]->getContent()) || $tryToRemove) { + if (!$this->configuration['strict']) { + continue; + } + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } - if (!$functionFilter($tokens[$index]->getContent())) { continue; } - if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { + if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) { continue; // do not bother if previous token is already namespace separator } $insertAtIndexes[] = $index; } - foreach (\array_reverse($insertAtIndexes) as $index) { + foreach (array_reverse($insertAtIndexes) as $index) { $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); } } @@ -311,7 +307,7 @@ private function getFunctionFilter() if (\in_array(self::SET_ALL, $this->configuration['include'], true)) { if (\count($exclude) > 0) { return static function ($functionName) use ($exclude) { - return !isset($exclude[\strtolower($functionName)]); + return !isset($exclude[strtolower($functionName)]); }; } @@ -329,18 +325,18 @@ private function getFunctionFilter() foreach ($this->configuration['include'] as $additional) { if ('@' !== $additional[0]) { - $include[\strtolower($additional)] = true; + $include[strtolower($additional)] = true; } } if (\count($exclude) > 0) { return static function ($functionName) use ($include, $exclude) { - return isset($include[\strtolower($functionName)]) && !isset($exclude[\strtolower($functionName)]); + return isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]); }; } return static function ($functionName) use ($include) { - return isset($include[\strtolower($functionName)]); + return isset($include[strtolower($functionName)]); }; } @@ -350,7 +346,8 @@ private function getFunctionFilter() private function getAllCompilerOptimizedFunctionsNormalized() { return $this->normalizeFunctionNames([ - // @see https://github.com/php/php-src/blob/php-7.2.6/Zend/zend_compile.c "zend_try_compile_special_func" + // @see https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_compile.c "zend_try_compile_special_func" + 'array_key_exists', 'array_slice', 'assert', 'boolval', @@ -398,7 +395,7 @@ private function getAllCompilerOptimizedFunctionsNormalized() */ private function getAllInternalFunctionsNormalized() { - return $this->normalizeFunctionNames(\get_defined_functions()['internal']); + return $this->normalizeFunctionNames(get_defined_functions()['internal']); } /** @@ -409,7 +406,7 @@ private function getAllInternalFunctionsNormalized() private function normalizeFunctionNames(array $functionNames) { foreach ($functionNames as $index => $functionName) { - $functionNames[\strtolower($functionName)] = true; + $functionNames[strtolower($functionName)] = true; unset($functionNames[$index]); } diff --git a/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php b/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php index 5a046005b62..6fa13f3f31f 100644 --- a/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php +++ b/src/Fixer/FunctionNotation/NoUnreachableDefaultArgumentValueFixer.php @@ -99,7 +99,7 @@ private function fixFunctionDefinition(Tokens $tokens, $startIndex, $endIndex) continue; } - if (!$token->equals('=') || $this->isTypehintedNullableVariable($tokens, $i)) { + if (!$token->equals('=') || $this->isNonNullableTypehintedNullableVariable($tokens, $i)) { continue; } @@ -164,7 +164,7 @@ private function removeDefaultArgument(Tokens $tokens, $startIndex, $endIndex) * * @return bool */ - private function isTypehintedNullableVariable(Tokens $tokens, $index) + private function isNonNullableTypehintedNullableVariable(Tokens $tokens, $index) { $nextToken = $tokens[$tokens->getNextMeaningfulToken($index)]; @@ -179,7 +179,11 @@ private function isTypehintedNullableVariable(Tokens $tokens, $index) $prevIndex = $tokens->getPrevTokenOfKind($variableIndex, $searchTokens); - return $tokens[$prevIndex]->isGivenKind($typehintKinds); + if (!$tokens[$prevIndex]->isGivenKind($typehintKinds)) { + return false; + } + + return !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(CT::T_NULLABLE_TYPE); } /** diff --git a/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php b/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php index 7fda3d5d404..a11776e1756 100644 --- a/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php +++ b/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php @@ -169,12 +169,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $returnTypeAnnotation = $this->findReturnAnnotations($tokens, $index); - if (1 !== count($returnTypeAnnotation)) { + if (1 !== \count($returnTypeAnnotation)) { continue; } $returnTypeAnnotation = current($returnTypeAnnotation); $types = array_values($returnTypeAnnotation->getTypes()); - $typesCount = count($types); + $typesCount = \count($types); if (1 > $typesCount || 2 < $typesCount) { continue; } diff --git a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php index 207b7c99495..88b543acc69 100644 --- a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php +++ b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php @@ -70,14 +70,24 @@ public function doSomething(\Foo\Bar $foo): \Foo\Bar\Baz ); } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should run after PhpdocToReturnTypeFixer + // should run before NoSuperfluousPhpdocTagsFixer + return 7; + } + /** * {@inheritdoc} */ public function isCandidate(Tokens $tokens) { return $tokens->isTokenKindFound(T_FUNCTION) && ( - count((new NamespacesAnalyzer())->getDeclarations($tokens)) || - count((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens)) + \count((new NamespacesAnalyzer())->getDeclarations($tokens)) || + \count((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens)) ); } diff --git a/src/Fixer/Import/NoUnusedImportsFixer.php b/src/Fixer/Import/NoUnusedImportsFixer.php index b860009c0b5..ce8480f9409 100644 --- a/src/Fixer/Import/NoUnusedImportsFixer.php +++ b/src/Fixer/Import/NoUnusedImportsFixer.php @@ -63,7 +63,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens); - if (0 === count($useDeclarations)) { + if (0 === \count($useDeclarations)) { return; } @@ -212,7 +212,7 @@ private function removeUseDeclaration(Tokens $tokens, NamespaceUseAnalysis $useD private function removeUsesInSameNamespace(Tokens $tokens, array $useDeclarations, NamespaceAnalysis $namespaceDeclaration) { $namespace = $namespaceDeclaration->getFullName(); - $nsLength = strlen($namespace.'\\'); + $nsLength = \strlen($namespace.'\\'); foreach ($useDeclarations as $useDeclaration) { if ($useDeclaration->isAliased()) { diff --git a/src/Fixer/Import/OrderedImportsFixer.php b/src/Fixer/Import/OrderedImportsFixer.php index 20b96529f34..695955ef13d 100644 --- a/src/Fixer/Import/OrderedImportsFixer.php +++ b/src/Fixer/Import/OrderedImportsFixer.php @@ -89,20 +89,20 @@ public function getDefinition() [ new CodeSample(" self::SORT_LENGTH] ), new VersionSpecificCodeSample( - "getImportUseIndexes(true); - if (0 === count($namespacesImports)) { + if (0 === \count($namespacesImports)) { return; } @@ -215,9 +215,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) // Now insert the new tokens, starting from the end foreach ($usesOrder as $index => $use) { - $declarationTokens = Tokens::fromCode('clearRange(0, 2); // clear `clearAt(count($declarationTokens) - 1); // clear `;` + $declarationTokens->clearAt(\count($declarationTokens) - 1); // clear `;` $declarationTokens->clearEmptyTokens(); $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); @@ -259,19 +266,19 @@ protected function createConfigurationDefinition() ->setAllowedValues([static function ($value) use ($supportedSortTypes) { if (null !== $value) { $missing = array_diff($supportedSortTypes, $value); - if (count($missing)) { + if (\count($missing)) { throw new InvalidOptionsException(sprintf( 'Missing sort %s "%s".', - 1 === count($missing) ? 'type' : 'types', + 1 === \count($missing) ? 'type' : 'types', implode('", "', $missing) )); } $unknown = array_diff($value, $supportedSortTypes); - if (count($unknown)) { + if (\count($unknown)) { throw new InvalidOptionsException(sprintf( 'Unknown sort %s "%s".', - 1 === count($unknown) ? 'type' : 'types', + 1 === \count($unknown) ? 'type' : 'types', implode('", "', $unknown) )); } @@ -287,8 +294,8 @@ protected function createConfigurationDefinition() /** * This method is used for sorting the uses in a namespace. * - * @param string[] $first - * @param string[] $second + * @param array $first + * @param array $second * * @return int * @@ -296,10 +303,6 @@ protected function createConfigurationDefinition() */ private function sortAlphabetically(array $first, array $second) { - if ($first['importType'] !== $second['importType']) { - return $first['importType'] > $second['importType'] ? 1 : -1; - } - // Replace backslashes by spaces before sorting for correct sort order $firstNamespace = str_replace('\\', ' ', $this->prepareNamespace($first['namespace'])); $secondNamespace = str_replace('\\', ' ', $this->prepareNamespace($second['namespace'])); @@ -310,8 +313,8 @@ private function sortAlphabetically(array $first, array $second) /** * This method is used for sorting the uses statements in a namespace by length. * - * @param string[] $first - * @param string[] $second + * @param array $first + * @param array $second * * @return int * @@ -319,11 +322,11 @@ private function sortAlphabetically(array $first, array $second) */ private function sortByLength(array $first, array $second) { - $firstNamespace = $this->prepareNamespace($first['namespace']); - $secondNamespace = $this->prepareNamespace($second['namespace']); + $firstNamespace = (self::IMPORT_TYPE_CLASS === $first['importType'] ? '' : $first['importType'].' ').$this->prepareNamespace($first['namespace']); + $secondNamespace = (self::IMPORT_TYPE_CLASS === $second['importType'] ? '' : $second['importType'].' ').$this->prepareNamespace($second['namespace']); - $firstNamespaceLength = strlen($firstNamespace); - $secondNamespaceLength = strlen($secondNamespace); + $firstNamespaceLength = \strlen($firstNamespace); + $secondNamespaceLength = \strlen($secondNamespace); if ($firstNamespaceLength === $secondNamespaceLength) { $sortResult = strcasecmp($firstNamespace, $secondNamespace); @@ -344,13 +347,19 @@ private function prepareNamespace($namespace) return trim(Preg::replace('%/\*(.*)\*/%s', '', $namespace)); } + /** + * @param int[] $uses + * @param Tokens $tokens + * + * @return array + */ private function getNewOrder(array $uses, Tokens $tokens) { $indexes = []; $originalIndexes = []; $lineEnding = $this->whitespacesConfig->getLineEnding(); - for ($i = count($uses) - 1; $i >= 0; --$i) { + for ($i = \count($uses) - 1; $i >= 0; --$i) { $index = $uses[$i]; $startIndex = $tokens->getTokenNotOfKindSibling($index + 1, 1, [[T_WHITESPACE]]); @@ -358,16 +367,18 @@ private function getNewOrder(array $uses, Tokens $tokens) $previous = $tokens->getPrevMeaningfulToken($endIndex); $group = $tokens[$previous]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE); - if ($tokens[$startIndex]->isGivenKind([CT::T_CONST_IMPORT])) { + if ($tokens[$startIndex]->isGivenKind(CT::T_CONST_IMPORT)) { $type = self::IMPORT_TYPE_CONST; - } elseif ($tokens[$startIndex]->isGivenKind([CT::T_FUNCTION_IMPORT])) { + $index = $tokens->getNextNonWhitespace($startIndex); + } elseif ($tokens[$startIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { $type = self::IMPORT_TYPE_FUNCTION; + $index = $tokens->getNextNonWhitespace($startIndex); } else { $type = self::IMPORT_TYPE_CLASS; + $index = $startIndex; } $namespaceTokens = []; - $index = $startIndex; while ($index <= $endIndex) { $token = $tokens[$index]; @@ -377,7 +388,7 @@ private function getNewOrder(array $uses, Tokens $tokens) // if group import, sort the items within the group definition // figure out where the list of namespace parts within the group def. starts - $namespaceTokensCount = count($namespaceTokens) - 1; + $namespaceTokensCount = \count($namespaceTokens) - 1; $namespace = ''; for ($k = 0; $k < $namespaceTokensCount; ++$k) { if ($namespaceTokens[$k]->isGivenKind(CT::T_GROUP_IMPORT_BRACE_OPEN)) { @@ -475,7 +486,7 @@ private function getNewOrder(array $uses, Tokens $tokens) } // Is sort types provided, sorting by groups and each group by algorithm - if ($this->configuration['imports_order']) { + if (null !== $this->configuration['imports_order']) { // Grouping indexes by import type. $groupedByTypes = []; foreach ($indexes as $startIndex => $item) { diff --git a/src/Fixer/Import/SingleImportPerStatementFixer.php b/src/Fixer/Import/SingleImportPerStatementFixer.php index 36be8ee64b8..c257e5f1022 100644 --- a/src/Fixer/Import/SingleImportPerStatementFixer.php +++ b/src/Fixer/Import/SingleImportPerStatementFixer.php @@ -201,7 +201,7 @@ private function fixGroupUse(Tokens $tokens, $index, $endIndex) list($groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment) = $this->getGroupDeclaration($tokens, $index); $statements = $this->getGroupStatements($tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment); - if (count($statements) < 2) { + if (\count($statements) < 2) { return; } diff --git a/src/Fixer/Import/SingleLineAfterImportsFixer.php b/src/Fixer/Import/SingleLineAfterImportsFixer.php index 4f6fb0fa8c3..c3500c490a6 100644 --- a/src/Fixer/Import/SingleLineAfterImportsFixer.php +++ b/src/Fixer/Import/SingleLineAfterImportsFixer.php @@ -105,7 +105,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) ++$added; } - if ($semicolonIndex === count($tokens) - 1) { + if ($semicolonIndex === \count($tokens) - 1) { $tokens->insertAt($insertIndex + 1, new Token([T_WHITESPACE, $ending.$ending.$indent])); ++$added; } else { diff --git a/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php b/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php index 0c62161d3d0..e52778d7045 100644 --- a/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php +++ b/src/Fixer/LanguageConstruct/ClassKeywordRemoveFixer.php @@ -39,7 +39,7 @@ public function getDefinition() 'Converts `::class` keywords to FQCN strings.', [ new CodeSample( -'findGivenKind(T_NAMESPACE)); // Namespace blocks - if (count($namespaceIndexes) && isset($namespaceIndexes[$namespaceNumber])) { + if (\count($namespaceIndexes) && isset($namespaceIndexes[$namespaceNumber])) { $startIndex = $namespaceIndexes[$namespaceNumber]; $namespaceBlockStartIndex = $tokens->getNextTokenOfKind($startIndex, [';', '{']); @@ -89,7 +89,7 @@ private function replaceClassKeywords(Tokens $tokens, $namespaceNumber = -1) $endIndex = $endIndex ?: $tokens->count() - 1; } elseif (-1 === $namespaceNumber) { // Out of any namespace block $startIndex = 0; - $endIndex = count($namespaceIndexes) ? $namespaceIndexes[0] : $tokens->count() - 1; + $endIndex = \count($namespaceIndexes) ? $namespaceIndexes[0] : $tokens->count() - 1; } else { return; } @@ -137,7 +137,7 @@ static function ($import) { $groupImportParts = array_map(static function ($import) { return trim($import); }, explode(' as ', $groupImport)); - if (2 === count($groupImportParts)) { + if (2 === \count($groupImportParts)) { $this->imports[$groupImportParts[1]] = $import.$groupImportParts[0]; } else { $this->imports[] = $import.$groupImport; @@ -204,7 +204,7 @@ private function replaceClassKeyword(Tokens $tokens, $classIndex) $classStringArray = explode('\\', $classString); $namespaceToTest = $classStringArray[0]; - if (0 === strcmp($namespaceToTest, substr($import, -strlen($namespaceToTest)))) { + if (0 === strcmp($namespaceToTest, substr($import, -\strlen($namespaceToTest)))) { $classImport = $import; break; @@ -236,16 +236,16 @@ private function makeClassFQN($classImport, $classString) } $classStringArray = explode('\\', $classString); - $classStringLength = count($classStringArray); + $classStringLength = \count($classStringArray); $classImportArray = explode('\\', $classImport); - $classImportLength = count($classImportArray); + $classImportLength = \count($classImportArray); if (1 === $classStringLength) { return $classImport; } return implode('\\', array_merge( - array_slice($classImportArray, 0, $classImportLength - $classStringLength + 1), + \array_slice($classImportArray, 0, $classImportLength - $classStringLength + 1), $classStringArray )); } diff --git a/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php b/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php index f8e4a0db22c..f85564351bc 100644 --- a/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php +++ b/src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php @@ -81,7 +81,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $nextIssetInfo = $this->getIssetInfo($tokens, $issetIndex); // clone what we want to move, do not clone '(' and ')' of the 'isset' statement we're merging - $clones = $this->getTokenClones($tokens, array_slice($nextIssetInfo, 1, -1)); + $clones = $this->getTokenClones($tokens, \array_slice($nextIssetInfo, 1, -1)); // clean up no the tokens of the 'isset' statement we're merging $this->clearTokens($tokens, array_merge($nextIssetInfo, [$issetIndex, $booleanAndTokenIndex])); @@ -91,7 +91,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $tokens->insertAt($insertLocation, $clones); // correct some counts and offset based on # of tokens inserted - $numberOfTokensInserted = count($clones); + $numberOfTokensInserted = \count($clones); $tokenCount += $numberOfTokensInserted; $issetCloseBraceIndex += $numberOfTokensInserted; $insertLocation += $numberOfTokensInserted; diff --git a/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php b/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php index 32f6601f7dd..68b8398aca4 100644 --- a/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php +++ b/src/Fixer/LanguageConstruct/CombineConsecutiveUnsetsFixer.php @@ -39,7 +39,7 @@ public function getDefinition() */ public function getPriority() { - // should be run before SpaceAfterSemicolonFixer, NoWhitespaceInBlankLineFixer, NoTrailingWhitespaceFixer and NoExtraBlankLinesFixerand after NoEmptyStatementFixer. + // should be run before SpaceAfterSemicolonFixer, NoWhitespaceInBlankLineFixer, NoTrailingWhitespaceFixer and NoExtraBlankLinesFixer and after NoEmptyStatementFixer. return 24; } @@ -62,7 +62,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $previousUnsetCall = $this->getPreviousUnsetCall($tokens, $index); - if (is_int($previousUnsetCall)) { + if (\is_int($previousUnsetCall)) { $index = $previousUnsetCall; continue; diff --git a/src/Fixer/LanguageConstruct/DirConstantFixer.php b/src/Fixer/LanguageConstruct/DirConstantFixer.php index 8b608c41b2b..f87c5933236 100644 --- a/src/Fixer/LanguageConstruct/DirConstantFixer.php +++ b/src/Fixer/LanguageConstruct/DirConstantFixer.php @@ -44,6 +44,15 @@ public function isCandidate(Tokens $tokens) return $tokens->isTokenKindFound(T_FILE); } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should run before CombineNestedDirnameFixer + return 4; + } + /** * {@inheritdoc} */ @@ -62,11 +71,23 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $currIndex = $openParenthesis; // ensure __FILE__ is in between (...) + $fileCandidateRightIndex = $tokens->getPrevMeaningfulToken($closeParenthesis); + $trailingCommaIndex = null; + if ($tokens[$fileCandidateRightIndex]->equals(',')) { + $trailingCommaIndex = $fileCandidateRightIndex; + $fileCandidateRightIndex = $tokens->getPrevMeaningfulToken($fileCandidateRightIndex); + } + $fileCandidateRight = $tokens[$fileCandidateRightIndex]; + if (!$fileCandidateRight->isGivenKind(T_FILE)) { + continue; + } + $fileCandidateLeftIndex = $tokens->getNextMeaningfulToken($openParenthesis); $fileCandidateLeft = $tokens[$fileCandidateLeftIndex]; - if (!$fileCandidateRight->isGivenKind([T_FILE]) || !$fileCandidateLeft->isGivenKind([T_FILE])) { + + if (!$fileCandidateLeft->isGivenKind(T_FILE)) { continue; } @@ -78,12 +99,20 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $tokens->clearAt($namespaceCandidateIndex); } + if (null !== $trailingCommaIndex) { + if (!$tokens[$tokens->getNextNonWhitespace($trailingCommaIndex)]->isComment()) { + $tokens->removeTrailingWhitespace($trailingCommaIndex); + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($trailingCommaIndex); + } + // closing parenthesis removed with leading spaces if (!$tokens[$tokens->getNextNonWhitespace($closeParenthesis)]->isComment()) { $tokens->removeLeadingWhitespace($closeParenthesis); } - $tokens->clearAt($closeParenthesis); + $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesis); // opening parenthesis removed with trailing and leading spaces if (!$tokens[$tokens->getNextNonWhitespace($openParenthesis)]->isComment()) { @@ -91,11 +120,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $tokens->removeTrailingWhitespace($openParenthesis); - $tokens->clearAt($openParenthesis); + $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesis); // replace constant and remove function name $tokens[$fileCandidateLeftIndex] = new Token([T_DIR, '__DIR__']); - $tokens->clearAt($functionNameIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($functionNameIndex); } } } diff --git a/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php b/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php index 0f52e11c6fc..8695dccef73 100644 --- a/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php +++ b/src/Fixer/LanguageConstruct/ErrorSuppressionFixer.php @@ -146,7 +146,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } - if ($this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && !in_array($tokens[$functionIndex]->getContent(), $excludedFunctions, true)) { + if ($this->configuration[self::OPTION_NOISE_REMAINING_USAGES] && !\in_array($tokens[$functionIndex]->getContent(), $excludedFunctions, true)) { $tokens->clearAt($index); } } @@ -164,8 +164,13 @@ private function isDeprecationErrorCall(Tokens $tokens, $index) return false; } - $end = $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, '('])); - return $tokens[$tokens->getPrevMeaningfulToken($end)]->equals([T_STRING, 'E_USER_DEPRECATED']); + $prevIndex = $tokens->getPrevMeaningfulToken($endBraceIndex); + if ($tokens[$prevIndex]->equals(',')) { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + } + + return $tokens[$prevIndex]->equals([T_STRING, 'E_USER_DEPRECATED']); } } diff --git a/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php b/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php index 529702632d6..e28e848f523 100644 --- a/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php +++ b/src/Fixer/LanguageConstruct/ExplicitIndirectVariableFixer.php @@ -34,7 +34,7 @@ public function getDefinition() 'Add curly braces to indirect variables to make them clear to understand. Requires PHP >= 7.0.', [ new VersionSpecificCodeSample( -<<<'EOT' + <<<'EOT' getPrevMeaningfulToken($index); - if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION])) { + if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) { return null; } @@ -220,7 +220,7 @@ private function getReplaceCandidate(Tokens $tokens, $index) // test if the function call is to a native PHP function $lowerContent = strtolower($tokens[$index]->getContent()); - if (!array_key_exists($lowerContent, $this->functionsFixMap)) { + if (!\array_key_exists($lowerContent, $this->functionsFixMap)) { return null; } diff --git a/src/Fixer/LanguageConstruct/IsNullFixer.php b/src/Fixer/LanguageConstruct/IsNullFixer.php index 8dedfea64d7..1161d714f60 100644 --- a/src/Fixer/LanguageConstruct/IsNullFixer.php +++ b/src/Fixer/LanguageConstruct/IsNullFixer.php @@ -15,7 +15,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; -use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -70,6 +70,7 @@ public function isRisky() protected function applyFix(\SplFileInfo $file, Tokens $tokens) { static $sequenceNeeded = [[T_STRING, 'is_null'], '(']; + $functionsAnalyzer = new FunctionsAnalyzer(); $currIndex = 0; while (null !== $currIndex) { @@ -86,47 +87,40 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) // move the cursor just after the sequence list($isNullIndex, $currIndex) = $matches; - $next = $tokens->getNextMeaningfulToken($currIndex); - if ($tokens[$next]->equals(')')) { + if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $matches[0])) { continue; } - // skip all expressions which are not a function reference - $inversionCandidateIndex = $prevTokenIndex = $tokens->getPrevMeaningfulToken($matches[0]); - $prevToken = $tokens[$prevTokenIndex]; - if ($prevToken->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION])) { + $next = $tokens->getNextMeaningfulToken($currIndex); + if ($tokens[$next]->equals(')')) { continue; } - // handle function references with namespaces - if ($prevToken->isGivenKind(T_NS_SEPARATOR)) { - $inversionCandidateIndex = $twicePrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex); - /** @var Token $twicePrevToken */ - $twicePrevToken = $tokens[$twicePrevTokenIndex]; - if ($twicePrevToken->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, T_STRING, CT::T_NAMESPACE_OPERATOR])) { - continue; - } + $prevTokenIndex = $tokens->getPrevMeaningfulToken($matches[0]); - // get rid of the root namespace when it used and check if the inversion operator provided + // handle function references with namespaces + if ($tokens[$prevTokenIndex]->isGivenKind(T_NS_SEPARATOR)) { $tokens->removeTrailingWhitespace($prevTokenIndex); $tokens->clearAt($prevTokenIndex); + + $prevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex); } // check if inversion being used, text comparison is due to not existing constant $isInvertedNullCheck = false; - if ($tokens[$inversionCandidateIndex]->equals('!')) { + if ($tokens[$prevTokenIndex]->equals('!')) { $isInvertedNullCheck = true; // get rid of inverting for proper transformations - $tokens->removeTrailingWhitespace($inversionCandidateIndex); - $tokens->clearAt($inversionCandidateIndex); + $tokens->removeTrailingWhitespace($prevTokenIndex); + $tokens->clearAt($prevTokenIndex); } // before getting rind of `()` around a parameter, ensure it's not assignment/ternary invariant $referenceEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $matches[1]); $isContainingDangerousConstructs = false; for ($paramTokenIndex = $matches[1]; $paramTokenIndex <= $referenceEnd; ++$paramTokenIndex) { - if (in_array($tokens[$paramTokenIndex]->getContent(), ['?', '?:', '='], true)) { + if (\in_array($tokens[$paramTokenIndex]->getContent(), ['?', '?:', '='], true)) { $isContainingDangerousConstructs = true; break; @@ -139,12 +133,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $parentOperations = [T_IS_EQUAL, T_IS_NOT_EQUAL, T_IS_IDENTICAL, T_IS_NOT_IDENTICAL]; $wrapIntoParentheses = $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations); + // possible trailing comma removed + $prevIndex = $tokens->getPrevMeaningfulToken($referenceEnd); + if ($tokens[$prevIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex); + } + if (!$isContainingDangerousConstructs) { - if (!$wrapIntoParentheses) { - // closing parenthesis removed with leading spaces - $tokens->removeLeadingWhitespace($referenceEnd); - $tokens->clearAt($referenceEnd); - } + // closing parenthesis removed with leading spaces + $tokens->removeLeadingWhitespace($referenceEnd); + $tokens->clearAt($referenceEnd); // opening parenthesis removed with trailing spaces $tokens->removeLeadingWhitespace($matches[1]); @@ -162,6 +160,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) if ($wrapIntoParentheses) { array_unshift($replacement, new Token('(')); + $tokens->insertAt($referenceEnd + 1, new Token(')')); } $tokens->overrideRange($isNullIndex, $isNullIndex, $replacement); diff --git a/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php b/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php index 6bb4f5ae218..fa639c0e5d6 100644 --- a/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php +++ b/src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php @@ -15,7 +15,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; -use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -71,142 +71,162 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } - $unsetStart = $tokens->getNextTokenOfKind($index, ['(']); - $unsetEnd = $tokens->findBlockEnd( - Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, - $unsetStart - ); - $closingSemiColon = $tokens->getNextTokenOfKind($unsetEnd, [';']); - - $unsetTokensSubset = $this->createTokensSubSet($tokens, $index, $unsetEnd); - $unsetOpening = $unsetTokensSubset->getNextTokenOfKind(0, ['(']); + $unsetsInfo = $this->getUnsetsInfo($tokens, $index); - //We want to remove the `unset` and the `(` - $unsetTokensSubset->clearAt(0); - $unsetTokensSubset->clearAt($unsetOpening); - $unsetTokensSubset->clearEmptyTokens(); - - if (!$this->doTokensInvolveProperty($unsetTokensSubset)) { + if (!$this->isAnyUnsetToTransform($unsetsInfo)) { continue; } - $this->updateUnset($unsetTokensSubset); - - $tokens->overrideRange($index, $closingSemiColon, $unsetTokensSubset); + $isLastUnset = true; // yes, last - we reverse the array below + foreach (array_reverse($unsetsInfo) as $unsetInfo) { + $this->updateTokens($tokens, $unsetInfo, $isLastUnset); + $isLastUnset = false; + } } } /** - * Attempts to change unset into is null where possible. - * * @param Tokens $tokens + * @param int $index + * + * @return array> */ - private function updateUnset(Tokens $tokens) + private function getUnsetsInfo(Tokens $tokens, $index) { - $index = 0; - $atLastUnset = false; - do { - $next = $tokens->getNextTokenOfKind($index, [',']); - if (null === $next) { - $atLastUnset = true; - $next = $tokens->count() - 1; - $nextTokenSet = $this->createTokensSubSet($tokens, $index, $next + 1); - } else { - $nextTokenSet = $this->createTokensSubSet($tokens, $index, $next); - } - - if ($this->doTokensInvolveProperty($nextTokenSet) - && !$this->doTokensInvolveArrayAccess($nextTokenSet) - ) { - $replacement = $this->createReplacementIsNull($nextTokenSet); - } else { - $replacement = $this->createReplacementUnset($nextTokenSet); - } + $argumentsAnalyzer = new ArgumentsAnalyzer(); + + $unsetStart = $tokens->getNextTokenOfKind($index, ['(']); + $unsetEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $unsetStart); + $isFirst = true; + + $unsets = []; + foreach ($argumentsAnalyzer->getArguments($tokens, $unsetStart, $unsetEnd) as $startIndex => $endIndex) { + $startIndex = $tokens->getNextMeaningfulToken($startIndex - 1); + $endIndex = $tokens->getPrevMeaningfulToken($endIndex + 1); + $unsets[] = [ + 'startIndex' => $startIndex, + 'endIndex' => $endIndex, + 'isToTransform' => $this->isProperty($tokens, $startIndex, $endIndex), + 'isFirst' => $isFirst, + ]; + $isFirst = false; + } - $tokens->overrideRange($index, $next, $replacement); - $index = $tokens->getNextTokenOfKind($index, [';']) + 1; - } while (!$atLastUnset); + return $unsets; } /** - * @param Tokens $filteredTokens + * @param Tokens $tokens + * @param int $index + * @param int $endIndex * - * @return Token[] + * @return bool */ - private function createReplacementIsNull(Tokens $filteredTokens) + private function isProperty(Tokens $tokens, $index, $endIndex) { - return array_merge( - $filteredTokens->toArray(), - [ - new Token([T_WHITESPACE, ' ']), - new Token('='), - new Token([T_WHITESPACE, ' ']), - new Token([T_STRING, 'null']), - new Token(';'), - ] - ); - } + if ($tokens[$index]->isGivenKind(T_VARIABLE)) { + $nextIndex = $tokens->getNextMeaningfulToken($index); + if (null === $nextIndex || !$tokens[$nextIndex]->isGivenKind(T_OBJECT_OPERATOR)) { + return false; + } + $nextIndex = $tokens->getNextMeaningfulToken($nextIndex); + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + if (null !== $nextNextIndex && $nextNextIndex < $endIndex) { + return false; + } - /** - * @param Tokens $filteredTokens - * - * @return Token[] - */ - private function createReplacementUnset(Tokens $filteredTokens) - { - if ($filteredTokens[0]->isWhitespace()) { - $filteredTokens->clearAt(0); + return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_STRING); } - $unsetOpeningTokens = []; - if ($filteredTokens[0]->isWhitespace()) { - $unsetOpeningTokens[] = new Token([T_WHITESPACE, ' ']); - } + if ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + $nextIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[T_DOUBLE_COLON], [T_NS_SEPARATOR], [T_STRING]]); + $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex); + if (null !== $nextNextIndex && $nextNextIndex < $endIndex) { + return false; + } - array_push($unsetOpeningTokens, new Token([T_UNSET, 'unset']), new Token('(')); + return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_VARIABLE); + } - return array_merge( - $unsetOpeningTokens, - $filteredTokens->toArray(), - [ - new Token(')'), - new Token(';'), - ] - ); + return false; } /** - * @param Tokens $tokens - * @param int $startIndex - * @param int $endIndex + * @param array> $unsetsInfo * - * @return Tokens + * @return bool */ - private function createTokensSubSet(Tokens $tokens, $startIndex, $endIndex) + private function isAnyUnsetToTransform(array $unsetsInfo) { - $array = $tokens->toArray(); - $toAnalyze = array_splice($array, $startIndex, $endIndex - $startIndex); + foreach ($unsetsInfo as $unsetInfo) { + if ($unsetInfo['isToTransform']) { + return true; + } + } - return Tokens::fromArray($toAnalyze); + return false; } /** - * @param Tokens $tokens - * - * @return bool + * @param Tokens $tokens + * @param array $unsetInfo + * @param bool $isLastUnset */ - private function doTokensInvolveProperty(Tokens $tokens) + private function updateTokens(Tokens $tokens, array $unsetInfo, $isLastUnset) { - return $tokens->isAnyTokenKindsFound([T_PAAMAYIM_NEKUDOTAYIM, T_OBJECT_OPERATOR]); - } + // if entry is first and to be transform we remove leading "unset(" + if ($unsetInfo['isFirst'] && $unsetInfo['isToTransform']) { + $braceIndex = $tokens->getPrevTokenOfKind($unsetInfo['startIndex'], ['(']); + $unsetIndex = $tokens->getPrevTokenOfKind($braceIndex, [[T_UNSET]]); + $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex); + $tokens->clearTokenAndMergeSurroundingWhitespace($unsetIndex); + } - /** - * @param Tokens $tokens - * - * @return bool - */ - private function doTokensInvolveArrayAccess(Tokens $tokens) - { - return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]); + // if entry is last and to be transformed we remove trailing ")" + if ($isLastUnset && $unsetInfo['isToTransform']) { + $braceIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [')']); + $previousIndex = $tokens->getPrevMeaningfulToken($braceIndex); + if ($tokens[$previousIndex]->equals(',')) { + $tokens->clearTokenAndMergeSurroundingWhitespace($previousIndex); // trailing ',' in function call (PHP 7.3) + } + + $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex); + } + + // if entry is not last we replace comma with semicolon (last entry already has semicolon - from original unset) + if (!$isLastUnset) { + $commaIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [',']); + $tokens[$commaIndex] = new Token(';'); + } + + // if entry is to be unset and is not last we add trailing ")" + if (!$unsetInfo['isToTransform'] && !$isLastUnset) { + $tokens->insertAt($unsetInfo['endIndex'] + 1, new Token(')')); + } + + // if entry is to be unset and is not first we add leading "unset(" + if (!$unsetInfo['isToTransform'] && !$unsetInfo['isFirst']) { + $tokens->insertAt( + $unsetInfo['startIndex'], + [ + new Token([T_UNSET, 'unset']), + new Token('('), + ] + ); + } + + // and finally + // if entry is to be transformed we add trailing " = null" + if ($unsetInfo['isToTransform']) { + $tokens->insertAt( + $unsetInfo['endIndex'] + 1, + [ + new Token([T_WHITESPACE, ' ']), + new Token('='), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'null']), + ] + ); + } } } diff --git a/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php b/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php index 29535619e0a..ddb67adf77c 100644 --- a/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php +++ b/src/Fixer/NamespaceNotation/NoLeadingNamespaceWhitespaceFixer.php @@ -56,7 +56,7 @@ public function getDefinition() */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - for ($index = count($tokens) - 1; 0 <= $index; --$index) { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_NAMESPACE)) { diff --git a/src/Fixer/Operator/BinaryOperatorSpacesFixer.php b/src/Fixer/Operator/BinaryOperatorSpacesFixer.php index 87e48887e15..61b5244346d 100644 --- a/src/Fixer/Operator/BinaryOperatorSpacesFixer.php +++ b/src/Fixer/Operator/BinaryOperatorSpacesFixer.php @@ -256,7 +256,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) --$index; } - if (count($this->alignOperatorTokens)) { + if (\count($this->alignOperatorTokens)) { $this->fixAlignment($tokens, $this->alignOperatorTokens); } } @@ -275,23 +275,23 @@ protected function createConfigurationDefinition() ->setAllowedTypes(['array']) ->setAllowedValues([static function ($option) { foreach ($option as $operator => $value) { - if (!in_array($operator, self::$supportedOperators, true)) { + if (!\in_array($operator, self::$supportedOperators, true)) { throw new InvalidOptionsException( sprintf( 'Unexpected "operators" key, expected any of "%s", got "%s".', implode('", "', self::$supportedOperators), - is_object($operator) ? get_class($operator) : gettype($operator).'#'.$operator + \is_object($operator) ? \get_class($operator) : \gettype($operator).'#'.$operator ) ); } - if (!in_array($value, self::$allowedValues, true)) { + if (!\in_array($value, self::$allowedValues, true)) { throw new InvalidOptionsException( sprintf( 'Unexpected value for operator "%s", expected any of "%s", got "%s".', $operator, implode('", "', self::$allowedValues), - is_object($value) ? get_class($value) : (null === $value ? 'null' : gettype($value).'#'.$value) + \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) ) ); } @@ -312,7 +312,7 @@ private function fixWhiteSpaceAroundOperator(Tokens $tokens, $index) { $tokenContent = strtolower($tokens[$index]->getContent()); - if (!array_key_exists($tokenContent, $this->operators)) { + if (!\array_key_exists($tokenContent, $this->operators)) { return; // not configured to be changed } @@ -440,11 +440,11 @@ private function resolveOperatorsFromConfig() } } - if (!defined('T_SPACESHIP')) { + if (!\defined('T_SPACESHIP')) { unset($operators['<=>']); } - if (!defined('T_COALESCE')) { + if (!\defined('T_COALESCE')) { unset($operators['??']); } @@ -472,9 +472,9 @@ private function fixAlignment(Tokens $tokens, array $toAlign) $tokensClone = clone $tokens; if ('=>' === $tokenContent) { - $this->injectAlignmentPlaceholdersForArrow($tokensClone, 0, count($tokens)); + $this->injectAlignmentPlaceholdersForArrow($tokensClone, 0, \count($tokens)); } else { - $this->injectAlignmentPlaceholders($tokensClone, 0, count($tokens), $tokenContent); + $this->injectAlignmentPlaceholders($tokensClone, 0, \count($tokens), $tokenContent); } // for all tokens that should be aligned but do not have anything to align with, fix spacing if needed @@ -711,7 +711,7 @@ private function replacePlaceholders(Tokens $tokens, $alignStrategy) } foreach ($groups as $group) { - if (count($group) < 1) { + if (\count($group) < 1) { continue; } @@ -722,7 +722,7 @@ private function replacePlaceholders(Tokens $tokens, $alignStrategy) $before = substr($lines[$index], 0, $currentPosition); if (self::ALIGN_SINGLE_SPACE === $alignStrategy) { - if (1 > strlen($before) || ' ' !== substr($before, -1)) { // if last char of before-content is not ' '; add it + if (1 > \strlen($before) || ' ' !== substr($before, -1)) { // if last char of before-content is not ' '; add it $before .= ' '; } } elseif (self::ALIGN_SINGLE_SPACE_MINIMAL === $alignStrategy) { diff --git a/src/Fixer/Operator/IncrementStyleFixer.php b/src/Fixer/Operator/IncrementStyleFixer.php index 31d33f208b6..0796880fac7 100644 --- a/src/Fixer/Operator/IncrementStyleFixer.php +++ b/src/Fixer/Operator/IncrementStyleFixer.php @@ -137,6 +137,7 @@ private function findEnd(Tokens $tokens, $index) [CT::T_DYNAMIC_PROP_BRACE_OPEN], [CT::T_DYNAMIC_VAR_BRACE_OPEN], [T_NS_SEPARATOR], + [T_STATIC], [T_STRING], [T_VARIABLE], ])) { @@ -194,11 +195,11 @@ private function findStart(Tokens $tokens, $index) if ($prevToken->isGivenKind(T_PAAMAYIM_NEKUDOTAYIM)) { $prevPrevIndex = $tokens->getPrevMeaningfulToken($prevIndex); - if (!$tokens[$prevPrevIndex]->isGivenKind(T_STRING)) { + if (!$tokens[$prevPrevIndex]->isGivenKind([T_STATIC, T_STRING])) { return $this->findStart($tokens, $prevIndex); } - $index = $tokens->getTokenNotOfKindSibling($prevIndex, -1, [[T_NS_SEPARATOR], [T_STRING]]); + $index = $tokens->getTokenNotOfKindSibling($prevIndex, -1, [[T_NS_SEPARATOR], [T_STATIC], [T_STRING]]); $index = $tokens->getNextMeaningfulToken($index); } diff --git a/src/Fixer/Operator/LogicalOperatorsFixer.php b/src/Fixer/Operator/LogicalOperatorsFixer.php index 2bf030357a2..30d4fc32830 100644 --- a/src/Fixer/Operator/LogicalOperatorsFixer.php +++ b/src/Fixer/Operator/LogicalOperatorsFixer.php @@ -32,7 +32,7 @@ public function getDefinition() 'Use `&&` and `||` logical operators instead of `and` and `or`.', [ new CodeSample( -'getContent()); - $iLast = count($parts) - 1; + $iLast = \count($parts) - 1; foreach ($parts as $i => $part) { $tokenContent .= $part; - $tokenContentLength += strlen($part); + $tokenContentLength += \strlen($part); if ($i !== $iLast) { $originalTokenContent = substr($content, $tokensOldContentLength + $tokenContentLength, 5); @@ -126,7 +126,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokensOrg) } $tokensOldContent .= $token->getContent(); - $tokensOldContentLength += strlen($token->getContent()); + $tokensOldContentLength += \strlen($token->getContent()); } $tokensOrg->overrideRange(0, $tokensOrg->count() - 1, $tokens); diff --git a/src/Fixer/PhpTag/NoClosingTagFixer.php b/src/Fixer/PhpTag/NoClosingTagFixer.php index 1251cc9c99f..1411ef4d7f7 100644 --- a/src/Fixer/PhpTag/NoClosingTagFixer.php +++ b/src/Fixer/PhpTag/NoClosingTagFixer.php @@ -49,7 +49,7 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - if (count($tokens) < 2 || !$tokens->isMonolithicPhp()) { + if (\count($tokens) < 2 || !$tokens->isMonolithicPhp()) { return; } diff --git a/src/Fixer/PhpTag/NoShortEchoTagFixer.php b/src/Fixer/PhpTag/NoShortEchoTagFixer.php index fde49765bb9..b16c29fd344 100644 --- a/src/Fixer/PhpTag/NoShortEchoTagFixer.php +++ b/src/Fixer/PhpTag/NoShortEchoTagFixer.php @@ -47,7 +47,7 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - $i = count($tokens); + $i = \count($tokens); while ($i--) { $token = $tokens[$i]; diff --git a/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php b/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php index a240d60c0ba..7f31c252ccc 100644 --- a/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php @@ -244,11 +244,11 @@ private function fixAssertTrueFalse(Tokens $tokens, array $assertCall) $isPositive = 'asserttrue' === $assertCall['loweredName']; $content = strtolower($tokens[$testIndex]->getContent()); - if (!in_array($content, $this->functions, true)) { + if (!\in_array($content, $this->functions, true)) { return; } - if (is_array(self::$fixMap[$content])) { + if (\is_array(self::$fixMap[$content])) { if (false !== self::$fixMap[$content][$isPositive]) { $tokens[$assertCall['index']] = new Token([T_STRING, self::$fixMap[$content][$isPositive]]); $this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex); @@ -264,6 +264,11 @@ private function fixAssertTrueFalse(Tokens $tokens, array $assertCall) $tokens[$testOpenIndex] = new Token(','); $tokens->clearTokenAndMergeSurroundingWhitespace($testCloseIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($testCloseIndex); + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } if (!$tokens[$testOpenIndex + 1]->isWhitespace()) { $tokens->insertAt($testOpenIndex + 1, new Token([T_WHITESPACE, ' '])); @@ -321,12 +326,19 @@ private function fixAssertSameEquals(Tokens $tokens, array $assertCall) return; } + $countCallCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $countCallOpenBraceIndex); + + $afterCountCallCloseBraceIndex = $tokens->getNextMeaningfulToken($countCallCloseBraceIndex); + if (!$tokens[$afterCountCallCloseBraceIndex]->equalsAny([')', ','])) { + return; + } + $this->removeFunctionCall( $tokens, $defaultNamespaceTokenIndex, $countCallIndex, $countCallOpenBraceIndex, - $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $countCallOpenBraceIndex) + $countCallCloseBraceIndex ); $tokens[$assertCall['index']] = new Token([ @@ -390,6 +402,12 @@ private function removeFunctionCall(Tokens $tokens, $callNSIndex, $callIndex, $o } $tokens->clearTokenAndMergeSurroundingWhitespace($openIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } + $tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex); } } diff --git a/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php b/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php index f114b6a3f59..a45a05fd624 100644 --- a/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitExpectationFixer.php @@ -12,7 +12,7 @@ namespace PhpCsFixer\Fixer\PhpUnit; -use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\AbstractFixer; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; @@ -27,7 +27,7 @@ /** * @author Dariusz Rumiński */ -final class PhpUnitExpectationFixer extends AbstractFunctionReferenceFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +final class PhpUnitExpectationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { /** * @var array @@ -128,6 +128,14 @@ public function isCandidate(Tokens $tokens) return $tokens->isTokenKindFound(T_CLASS); } + /** + * {@inheritdoc} + */ + public function isRisky() + { + return true; + } + /** * {@inheritdoc} */ @@ -153,7 +161,7 @@ protected function createConfigurationDefinition() ]); } - private function fixExpectation($tokens, $startIndex, $endIndex) + private function fixExpectation(Tokens $tokens, $startIndex, $endIndex) { $argumentsAnalyzer = new ArgumentsAnalyzer(); @@ -178,9 +186,14 @@ private function fixExpectation($tokens, $startIndex, $endIndex) $openIndex = $tokens->getNextTokenOfKind($index, ['(']); $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); + $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex); + if ($tokens[$commaIndex]->equals(',')) { + $tokens->removeTrailingWhitespace($commaIndex); + $tokens->clearAt($commaIndex); + } $arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex); - $argumentsCnt = count($arguments); + $argumentsCnt = \count($arguments); $argumentsReplacements = ['expectException', $this->methodMap[$tokens[$index]->getContent()], 'expectExceptionCode']; diff --git a/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php b/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php index e20c86c4579..5834f2d2f89 100644 --- a/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php @@ -15,6 +15,7 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -32,7 +33,7 @@ public function getDefinition() return new FixerDefinition( 'PHPUnit annotations should be a FQCNs including a root namespace.', [new CodeSample( -'isTokenKindFound(T_DOC_COMMENT); + return $tokens->isAllTokenKindsFound([T_CLASS, T_DOC_COMMENT]); } /** @@ -72,12 +73,30 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - foreach ($tokens as $index => $token) { - if ($token->isGivenKind(T_DOC_COMMENT)) { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $startIndex = $indexes[0]; + $prevDocCommentIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_DOC_COMMENT]]); + if (null !== $prevDocCommentIndex) { + $startIndex = $prevDocCommentIndex; + } + $this->fixPhpUnitClass($tokens, $startIndex, $indexes[1]); + } + } + + /** + * @param Tokens $tokens + * @param int $startIndex + * @param int $endIndex + */ + private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex) + { + for ($index = $startIndex; $index < $endIndex; ++$index) { + if ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) { $tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace( - '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(\w.*)$~m', + '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(?!(?:self|static)::)(\w.*)$~m', '$1\\\\$2', - $token->getContent() + $tokens[$index]->getContent() )]); } } diff --git a/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php b/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php index 1e77842bfe0..e2be09d9669 100644 --- a/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php @@ -116,15 +116,15 @@ private function markClassInternal(Tokens $tokens, $startIndex) private function isAllowedByConfiguration(Tokens $tokens, $i) { $typeIndex = $tokens->getPrevMeaningfulToken($i); - if ($tokens[$typeIndex]->isGivenKind([T_FINAL])) { - return in_array('final', $this->configuration['types'], true); + if ($tokens[$typeIndex]->isGivenKind(T_FINAL)) { + return \in_array('final', $this->configuration['types'], true); } - if ($tokens[$typeIndex]->isGivenKind([T_ABSTRACT])) { - return in_array('abstract', $this->configuration['types'], true); + if ($tokens[$typeIndex]->isGivenKind(T_ABSTRACT)) { + return \in_array('abstract', $this->configuration['types'], true); } - return in_array('normal', $this->configuration['types'], true); + return \in_array('normal', $this->configuration['types'], true); } private function createDocBlock(Tokens $tokens, $docBlockIndex) @@ -147,7 +147,7 @@ private function updateDocBlockIfNeeded(Tokens $tokens, $docBlockIndex) } $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex); $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex); - $lines = implode($lines); + $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); } @@ -224,10 +224,10 @@ private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, $docB private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, $docBlockIndex) { $lines = $doc->getLines(); - if (1 === count($lines) && empty($doc->getAnnotationsOfType('internal'))) { + if (1 === \count($lines) && empty($doc->getAnnotationsOfType('internal'))) { $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex); - return new DocBlock(implode($lines)); + return new DocBlock(implode('', $lines)); } return $doc; @@ -256,16 +256,16 @@ private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) } /** - * @param Line []$line + * @param Line[] $line * * @return string */ private function getSingleLineDocBlockEntry($line) { $line = $line[0]; - $line = \str_replace('*/', '', $line); + $line = str_replace('*/', '', $line); $line = trim($line); - $line = \str_split($line); + $line = str_split($line); $i = \count($line); do { --$i; @@ -273,8 +273,8 @@ private function getSingleLineDocBlockEntry($line) if (' ' === $line[$i]) { ++$i; } - $line = array_slice($line, $i); + $line = \array_slice($line, $i); - return implode($line); + return implode('', $line); } } diff --git a/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php b/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php new file mode 100644 index 00000000000..13c949b4c10 --- /dev/null +++ b/src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php @@ -0,0 +1,274 @@ + + * 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\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\DocBlock\Line; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; +use PhpCsFixer\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; +use PhpCsFixer\Tokenizer\TokensAnalyzer; +use PhpCsFixer\Utils; + +/** + * @author Filippo Tessarotto + */ +final class PhpUnitMethodCasingFixer extends AbstractFixer implements ConfigurableFixerInterface +{ + /** + * @internal + */ + const CAMEL_CASE = 'camel_case'; + + /** + * @internal + */ + const SNAKE_CASE = 'snake_case'; + + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Enforce camel (or snake) case for PHPUnit test methods, following configuration.', + [ + new CodeSample( + ' self::SNAKE_CASE] + ), + ] + ); + } + + /** + * {@inheritdoc} + */ + public function isCandidate(Tokens $tokens) + { + return $tokens->isAllTokenKindsFound([T_CLASS, T_FUNCTION]); + } + + /** + * {@inheritdoc} + */ + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator(); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->applyCasing($tokens, $indexes[0], $indexes[1]); + } + } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('case', 'Apply camel or snake case to test methods')) + ->setAllowedValues([self::CAMEL_CASE, self::SNAKE_CASE]) + ->setDefault(self::CAMEL_CASE) + ->getOption(), + ]); + } + + /** + * @param Tokens $tokens + * @param int $startIndex + * @param int $endIndex + */ + private function applyCasing(Tokens $tokens, $startIndex, $endIndex) + { + for ($index = $endIndex - 1; $index > $startIndex; --$index) { + if (!$this->isTestMethod($tokens, $index)) { + continue; + } + + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = $tokens[$functionNameIndex]->getContent(); + $newFunctionName = $this->updateMethodCasing($functionName); + + if ($newFunctionName !== $functionName) { + $tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]); + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + if ($this->hasDocBlock($tokens, $index)) { + $this->updateDocBlock($tokens, $docBlockIndex); + } + } + } + + /** + * @param string $functionName + * + * @return string + */ + private function updateMethodCasing($functionName) + { + if (self::CAMEL_CASE === $this->configuration['case']) { + $newFunctionName = $functionName; + $newFunctionName = ucwords($newFunctionName, '_'); + $newFunctionName = str_replace('_', '', $newFunctionName); + $newFunctionName = lcfirst($newFunctionName); + } else { + $newFunctionName = Utils::camelCaseToUnderscore($functionName); + } + + return $newFunctionName; + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return bool + */ + private function isTestMethod(Tokens $tokens, $index) + { + // Check if we are dealing with a (non abstract, non lambda) function + if (!$this->isMethod($tokens, $index)) { + return false; + } + + // if the function name starts with test it's a test + $functionNameIndex = $tokens->getNextMeaningfulToken($index); + $functionName = $tokens[$functionNameIndex]->getContent(); + + if ($this->startsWith('test', $functionName)) { + return true; + } + // If the function doesn't have test in its name, and no doc block, it's not a test + if (!$this->hasDocBlock($tokens, $index)) { + return false; + } + + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + $doc = $tokens[$docBlockIndex]->getContent(); + if (false === strpos($doc, '@test')) { + return false; + } + + return true; + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return bool + */ + private function isMethod(Tokens $tokens, $index) + { + $tokensAnalyzer = new TokensAnalyzer($tokens); + + return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index); + } + + /** + * @param string $needle + * @param string $haystack + * + * @return bool + */ + private function startsWith($needle, $haystack) + { + return substr($haystack, 0, \strlen($needle)) === $needle; + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return bool + */ + private function hasDocBlock(Tokens $tokens, $index) + { + $docBlockIndex = $this->getDocBlockIndex($tokens, $index); + + return $tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT); + } + + /** + * @param Tokens $tokens + * @param int $index + * + * @return int + */ + private function getDocBlockIndex(Tokens $tokens, $index) + { + do { + $index = $tokens->getPrevNonWhitespace($index); + } while ($tokens[$index]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT])); + + return $index; + } + + /** + * @param Tokens $tokens + * @param int $docBlockIndex + */ + private function updateDocBlock(Tokens $tokens, $docBlockIndex) + { + $doc = new DocBlock($tokens[$docBlockIndex]->getContent()); + $lines = $doc->getLines(); + + $docBlockNeesUpdate = false; + for ($inc = 0; $inc < \count($lines); ++$inc) { + $lineContent = $lines[$inc]->getContent(); + if (false === strpos($lineContent, '@depends')) { + continue; + } + + $newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', function (array $matches) { + return sprintf( + '%s%s%s', + $matches[1], + $this->updateMethodCasing($matches[2]), + $matches[3] + ); + }, $lineContent); + + if ($newLineContent !== $lineContent) { + $lines[$inc] = new Line($newLineContent); + $docBlockNeesUpdate = true; + } + } + + if ($docBlockNeesUpdate) { + $lines = implode('', $lines); + $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); + } + } +} diff --git a/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php b/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php index 602e6122b9c..212e5ac9849 100644 --- a/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitNamespacedFixer.php @@ -41,7 +41,7 @@ public function getDefinition() 'PHPUnit classes MUST be used in namespaced version, eg `\PHPUnit\Framework\TestCase` instead of `\PHPUnit_Framework_TestCase`.', [ new CodeSample( -'' .(isset($annotations['expectedExceptionMessageRegExp']) ? 'setExpectedExceptionRegExp' : 'setExpectedException') .'(' - .implode($paramList, ', ') + .implode(', ', $paramList) .');'; $newMethods = Tokens::fromCode($newMethodsCode); $newMethods[0] = new Token([ @@ -263,7 +263,7 @@ private function extractContentFromAnnotation(Annotation $annotation) { $tag = $annotation->getTag()->getName(); - Preg::match('/^\s*\*\s*@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches); + Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches); $content = $matches[1]; if (Preg::match('/\R/u', $content)) { $content = Preg::replace('/\s*\R+\s*\*\s*/u', ' ', $content); diff --git a/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php b/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php index 68ff09e8fb9..9092b80cdf1 100644 --- a/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php @@ -34,7 +34,7 @@ public function getDefinition() 'Order `@covers` annotation of PHPUnit tests.', [ new CodeSample( -'getNextMeaningfulToken($index); - $functionName = \strtolower($tokens[$functionNameIndex]->getContent()); + $functionName = strtolower($tokens[$functionNameIndex]->getContent()); return 'setup' === $functionName || 'teardown' === $functionName; } diff --git a/src/Fixer/PhpUnit/PhpUnitStrictFixer.php b/src/Fixer/PhpUnit/PhpUnitStrictFixer.php index eef07805327..eb753f46191 100644 --- a/src/Fixer/PhpUnit/PhpUnitStrictFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitStrictFixer.php @@ -44,7 +44,7 @@ public function getDefinition() 'PHPUnit methods like `assertSame` should be used instead of `assertEquals`.', [ new CodeSample( -'setAllowedValues(['prefix', 'annotation']) ->setDefault('prefix') ->getOption(), - (new FixerOptionBuilder('case', 'Whether to camel or snake case when adding the test prefix')) - ->setAllowedValues(['camel', 'snake']) - ->setDefault('camel') - ->getOption(), ]); } @@ -144,7 +140,7 @@ private function applyTestAnnotation(Tokens $tokens, $startIndex, $endIndex) $lines = $this->addTestAnnotation($lines, $tokens, $docBlockIndex); - $lines = implode($lines); + $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); } } @@ -166,7 +162,7 @@ private function applyTestPrefix(Tokens $tokens, $startIndex, $endIndex) $lines = $this->updateDocBlock($tokens, $docBlockIndex); - $lines = implode($lines); + $lines = implode('', $lines); $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]); $functionNameIndex = $tokens->getNextMeaningfulToken($i); @@ -236,7 +232,7 @@ private function isMethod(Tokens $tokens, $index) */ private function startsWith($needle, $haystack) { - $len = strlen($needle); + $len = \strlen($needle); return substr($haystack, 0, $len) === $needle; } @@ -312,10 +308,6 @@ private function removeTestPrefix($functionName) */ private function addTestPrefix($functionName) { - if ('camel' !== $this->configuration['case']) { - return 'test_'.$functionName; - } - return'test'.ucfirst($functionName); } @@ -390,9 +382,9 @@ private function updateLines($lines, Tokens $tokens, $docBlockIndex) } if (!$needsAnnotation && - false !== \strpos($lines[$i]->getContent(), ' @test') && - false === \strpos($lines[$i]->getContent(), '@testWith') && - false === \strpos($lines[$i]->getContent(), '@testdox') + false !== strpos($lines[$i]->getContent(), ' @test') && + false === strpos($lines[$i]->getContent(), '@testWith') && + false === strpos($lines[$i]->getContent(), '@testdox') ) { // We remove @test from the doc block $lines[$i] = new Line(str_replace(' @test', '', $lines[$i]->getContent())); @@ -438,9 +430,9 @@ private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) private function getSingleLineDocBlockEntry($line) { $line = $line[0]; - $line = \str_replace('*/', '', $line); + $line = str_replace('*/', '', $line); $line = trim($line); - $line = \str_split($line); + $line = str_split($line); $i = \count($line); do { --$i; @@ -448,9 +440,9 @@ private function getSingleLineDocBlockEntry($line) if (' ' === $line[$i]) { ++$i; } - $line = array_slice($line, $i); + $line = \array_slice($line, $i); - return implode($line); + return implode('', $line); } /** @@ -476,17 +468,17 @@ private function updateDependsAnnotation(Line $line) */ private function removeTestPrefixFromDependsAnnotation(Line $line) { - $line = \str_split($line->getContent()); + $line = str_split($line->getContent()); $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); - $dependsFunctionName = implode(array_slice($line, $dependsIndex)); + $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); if ($this->startsWith('test', $dependsFunctionName)) { $dependsFunctionName = $this->removeTestPrefix($dependsFunctionName); } array_splice($line, $dependsIndex); - return new Line(implode($line).$dependsFunctionName); + return new Line(implode('', $line).$dependsFunctionName); } /** @@ -496,9 +488,9 @@ private function removeTestPrefixFromDependsAnnotation(Line $line) */ private function addTestPrefixToDependsAnnotation(Line $line) { - $line = \str_split($line->getContent()); + $line = str_split($line->getContent()); $dependsIndex = $this->findWhereDependsFunctionNameStarts($line); - $dependsFunctionName = implode(array_slice($line, $dependsIndex)); + $dependsFunctionName = implode('', \array_slice($line, $dependsIndex)); if (!$this->startsWith('test', $dependsFunctionName)) { $dependsFunctionName = $this->addTestPrefix($dependsFunctionName); @@ -506,7 +498,7 @@ private function addTestPrefixToDependsAnnotation(Line $line) array_splice($line, $dependsIndex); - return new Line(implode($line).$dependsFunctionName); + return new Line(implode('', $line).$dependsFunctionName); } /** diff --git a/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php b/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php index 63b9393f8f0..8b90d671c73 100644 --- a/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php @@ -94,6 +94,9 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractFixer implemen 'assertEmpty' => true, 'assertEqualXMLStructure' => true, 'assertEquals' => true, + 'assertEqualsCanonicalizing' => true, + 'assertEqualsIgnoringCase' => true, + 'assertEqualsWithDelta' => true, 'assertFalse' => true, 'assertFileEquals' => true, 'assertFileExists' => true, @@ -109,7 +112,29 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractFixer implemen 'assertInfinite' => true, 'assertInstanceOf' => true, 'assertInternalType' => true, + 'assertIsArray' => true, + 'assertIsBool' => true, + 'assertIsCallable' => true, + 'assertIsFloat' => true, + 'assertIsInt' => true, + 'assertIsIterable' => true, + 'assertIsNotArray' => true, + 'assertIsNotBool' => true, + 'assertIsNotCallable' => true, + 'assertIsNotFloat' => true, + 'assertIsNotInt' => true, + 'assertIsNotIterable' => true, + 'assertIsNotNumeric' => true, + 'assertIsNotObject' => true, + 'assertIsNotResource' => true, + 'assertIsNotScalar' => true, + 'assertIsNotString' => true, + 'assertIsNumeric' => true, + 'assertIsObject' => true, 'assertIsReadable' => true, + 'assertIsResource' => true, + 'assertIsScalar' => true, + 'assertIsString' => true, 'assertIsWritable' => true, 'assertJson' => true, 'assertJsonFileEqualsJsonFile' => true, @@ -126,6 +151,9 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractFixer implemen 'assertNotCount' => true, 'assertNotEmpty' => true, 'assertNotEquals' => true, + 'assertNotEqualsCanonicalizing' => true, + 'assertNotEqualsIgnoringCase' => true, + 'assertNotEqualsWithDelta' => true, 'assertNotFalse' => true, 'assertNotInstanceOf' => true, 'assertNotInternalType' => true, @@ -142,11 +170,15 @@ final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractFixer implemen 'assertRegExp' => true, 'assertSame' => true, 'assertSameSize' => true, + 'assertStringContainsString' => true, + 'assertStringContainsStringIgnoringCase' => true, 'assertStringEndsNotWith' => true, 'assertStringEndsWith' => true, 'assertStringEqualsFile' => true, 'assertStringMatchesFormat' => true, 'assertStringMatchesFormatFile' => true, + 'assertStringNotContainsString' => true, + 'assertStringNotContainsStringIgnoringCase' => true, 'assertStringNotEqualsFile' => true, 'assertStringNotMatchesFormat' => true, 'assertStringNotMatchesFormatFile' => true, @@ -310,7 +342,7 @@ protected function createConfigurationDefinition() sprintf( 'Unexpected "methods" key, expected any of "%s", got "%s".', implode('", "', array_keys($thisFixer->staticMethods)), - is_object($method) ? get_class($method) : gettype($method).'#'.$method + \is_object($method) ? \get_class($method) : \gettype($method).'#'.$method ) ); } @@ -321,7 +353,7 @@ protected function createConfigurationDefinition() 'Unexpected value for method "%s", expected any of "%s", got "%s".', $method, implode('", "', array_keys($thisFixer->allowedValues)), - is_object($value) ? get_class($value) : (null === $value ? 'null' : gettype($value).'#'.$value) + \is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value) ) ); } diff --git a/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php b/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php index f719eb1758b..a9573f6d37a 100644 --- a/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php @@ -37,7 +37,7 @@ public function getDefinition() 'Adds a default `@coversNothing` annotation to PHPUnit test classes that have no `@covers*` annotation.', [ new CodeSample( -'count() - 1; $index >= 0; --$index) { - if (!$tokens[$index]->isGivenKind(T_CLASS)) { - continue; - } - - $prevIndex = $tokens->getPrevMeaningfulToken($index); + foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) { + $this->addRequiresCover($tokens, $indexes[0]); + } + } - // don't add `@covers` annotation for abstract base classes - if ($tokens[$prevIndex]->isGivenKind(T_ABSTRACT)) { - continue; - } + private function addRequiresCover(Tokens $tokens, $startIndex) + { + $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]); + $prevIndex = $tokens->getPrevMeaningfulToken($classIndex); - if (!$phpUnitTestCaseIndicator->isPhpUnitClass($tokens, $index)) { - continue; - } + // don't add `@covers` annotation for abstract base classes + if ($tokens[$prevIndex]->isGivenKind(T_ABSTRACT)) { + return; + } - $prevIndex = $tokens->getPrevMeaningfulToken($index); - $index = $tokens[$prevIndex]->isGivenKind(T_FINAL) ? $prevIndex : $index; + $index = $tokens[$prevIndex]->isGivenKind(T_FINAL) ? $prevIndex : $classIndex; - $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE) - ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent()) - : ''; + $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE) + ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent()) + : ''; - $prevIndex = $tokens->getPrevNonWhitespace($index); - $doc = null; - $docIndex = null; + $prevIndex = $tokens->getPrevNonWhitespace($index); + $doc = null; + $docIndex = null; - if ($tokens[$prevIndex]->isGivenKind(T_DOC_COMMENT)) { - $docIndex = $prevIndex; - $docContent = $tokens[$docIndex]->getContent(); + if ($tokens[$prevIndex]->isGivenKind(T_DOC_COMMENT)) { + $docIndex = $prevIndex; + $docContent = $tokens[$docIndex]->getContent(); - // ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations - if (false === strpos($docContent, "\n")) { - continue; - } + // ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations + if (false === strpos($docContent, "\n")) { + return; + } - $doc = new DocBlock($docContent); + $doc = new DocBlock($docContent); - // skip if already has annotation - if (!empty($doc->getAnnotationsOfType([ - 'covers', - 'coversDefaultClass', - 'coversNothing', - ]))) { - continue; + // skip if already has annotation + if (!empty($doc->getAnnotationsOfType([ + '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(); } - } 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)]), + new Token([T_WHITESPACE, $extraNewLines.$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()); + ++$docIndex; } - $lines = $doc->getLines(); - array_splice( - $lines, - count($lines) - 1, - 0, - [ - new Line(sprintf( - '%s * @coversNothing%s', - $indent, - $this->whitespacesConfig->getLineEnding() - )), - ] - ); - - $tokens[$docIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); + $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() + )), + ] + ); + + $tokens[$docIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]); } } diff --git a/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php b/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php index 78592252024..891aa50c7d8 100644 --- a/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php +++ b/src/Fixer/Phpdoc/AlignMultilineCommentFixer.php @@ -53,7 +53,7 @@ public function getDefinition() 'Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one.', [ new CodeSample( -' 'phpdocs_like'] ), new CodeSample( -'configuration['annotations'])) { + if (!\count($this->configuration['annotations'])) { return; } diff --git a/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php b/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php index 64e4b3d45a9..2688efc83ae 100644 --- a/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php +++ b/src/Fixer/Phpdoc/NoBlankLinesAfterPhpdocFixer.php @@ -17,7 +17,6 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -use PhpCsFixer\Utils; /** * @author Graham Campbell @@ -107,8 +106,7 @@ private function fixWhitespace(Tokens $tokens, $index) // if there is more than one new line in the whitespace, then we need to fix it if (substr_count($content, "\n") > 1) { // the final bit of the whitespace must be the next statement's indentation - $lines = Utils::splitLines($content); - $tokens[$index] = new Token([T_WHITESPACE, "\n".end($lines)]); + $tokens[$index] = new Token([T_WHITESPACE, substr($content, strrpos($content, "\n"))]); } } } diff --git a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php index 26b9cbfb443..51cfca6b651 100644 --- a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php +++ b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php @@ -15,6 +15,9 @@ use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\Annotation; use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\VersionSpecification; @@ -25,7 +28,7 @@ use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer +final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer implements ConfigurableFixerInterface { /** * {@inheritdoc} @@ -108,7 +111,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) ); foreach ($docBlock->getAnnotationsOfType('param') as $annotation) { - if (0 === Preg::match('/@param\s+(?:\S|\s(?!\$))+\s(\$\S+)/', $annotation->getContent(), $matches)) { + if (0 === Preg::match('/@param(?:\s+[^\$]\S+)?\s+(\$\S+)/', $annotation->getContent(), $matches)) { continue; } @@ -134,12 +137,25 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } } + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('allow_mixed', 'Whether type `mixed` without description is allowed (`true`) or considered superfluous (`false`)')) + ->setAllowedTypes(['bool']) + ->setDefault(false) + ->getOption(), + ]); + } + private function findDocumentedFunction(Tokens $tokens, $index) { do { $index = $tokens->getNextMeaningfulToken($index); - if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + if (null === $index || $tokens[$index]->isGivenKind(T_FUNCTION)) { return $index; } } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL, T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC])); @@ -147,6 +163,13 @@ private function findDocumentedFunction(Tokens $tokens, $index) return null; } + /** + * @param Tokens $tokens + * @param int $start + * @param int $end + * + * @return array + */ private function getArgumentsInfo(Tokens $tokens, $start, $end) { $argumentsInfo = []; @@ -226,6 +249,13 @@ private function parseTypeHint(Tokens $tokens, $index) ]; } + /** + * @param Annotation $annotation + * @param array $info + * @param array $symbolShortNames + * + * @return bool + */ private function annotationIsSuperfluous(Annotation $annotation, array $info, array $symbolShortNames) { if ('param' === $annotation->getTag()->getName()) { @@ -240,11 +270,15 @@ private function annotationIsSuperfluous(Annotation $annotation, array $info, ar $annotationTypes = $this->toComparableNames($annotation->getTypes(), $symbolShortNames); + if (['null'] === $annotationTypes) { + return false; + } + if (['mixed'] === $annotationTypes && null === $info['type']) { - return true; + return !$this->configuration['allow_mixed']; } - $actualTypes = [$info['type']]; + $actualTypes = null === $info['type'] ? [] : [$info['type']]; if ($info['allows_null']) { $actualTypes[] = 'null'; } @@ -258,8 +292,8 @@ 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 array $types The types to normalize - * @param array $symbolShortNames The imports aliases + * @param string[] $types The types to normalize + * @param array $symbolShortNames The imports aliases * * @return array The normalized types */ diff --git a/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php b/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php index a51cdddc943..e3ada872ec5 100644 --- a/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php +++ b/src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php @@ -12,7 +12,7 @@ namespace PhpCsFixer\Fixer\Phpdoc; -use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\AbstractFixer; use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\DocBlock\Line; use PhpCsFixer\Fixer\ConfigurableFixerInterface; @@ -29,7 +29,7 @@ /** * @author Dariusz Rumiński */ -final class PhpdocAddMissingParamAnnotationFixer extends AbstractFunctionReferenceFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface +final class PhpdocAddMissingParamAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { /** * {@inheritdoc} @@ -162,7 +162,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } } - if (!count($arguments)) { + if (!\count($arguments)) { continue; } @@ -179,12 +179,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $lastParamLine = max($lastParamLine, $annotation->getEnd()); } - if (!count($arguments)) { + if (!\count($arguments)) { continue; } $lines = $doc->getLines(); - $linesCount = count($lines); + $linesCount = \count($lines); Preg::match('/^(\s*).*$/', $lines[$linesCount - 1]->getContent(), $matches); $indent = $matches[1]; @@ -268,8 +268,16 @@ private function prepareArgumentInformation(Tokens $tokens, $start, $end) if ($sawName) { $info['default'] .= $token->getContent(); - } else { - $info['type'] .= $token->getContent(); + } elseif ('&' !== $token->getContent()) { + if ($token->isGivenKind(T_ELLIPSIS)) { + if ('' === $info['type']) { + $info['type'] = 'array'; + } else { + $info['type'] .= '[]'; + } + } else { + $info['type'] .= $token->getContent(); + } } } diff --git a/src/Fixer/Phpdoc/PhpdocAlignFixer.php b/src/Fixer/Phpdoc/PhpdocAlignFixer.php index fff157885c9..aaabf021e9f 100644 --- a/src/Fixer/Phpdoc/PhpdocAlignFixer.php +++ b/src/Fixer/Phpdoc/PhpdocAlignFixer.php @@ -13,6 +13,7 @@ namespace PhpCsFixer\Fixer\Phpdoc; use PhpCsFixer\AbstractFixer; +use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; @@ -23,7 +24,6 @@ use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; -use PhpCsFixer\Utils; /** * @author Fabien Potencier @@ -175,7 +175,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $content = $token->getContent(); - $newContent = $this->fixDocBlock($content); + $docBlock = new DocBlock($content); + $this->fixDocBlock($docBlock); + $newContent = $docBlock->getContent(); if ($newContent !== $content) { $tokens[$index] = new Token([T_DOC_COMMENT, $newContent]); } @@ -213,18 +215,15 @@ protected function createConfigurationDefinition() } /** - * @param string $content - * - * @return string + * @param DocBlock $docBlock */ - private function fixDocBlock($content) + private function fixDocBlock(DocBlock $docBlock) { $lineEnding = $this->whitespacesConfig->getLineEnding(); - $lines = Utils::splitLines($content); - for ($i = 0, $l = count($lines); $i < $l; ++$i) { + for ($i = 0, $l = \count($docBlock->getLines()); $i < $l; ++$i) { $items = []; - $matches = $this->getMatches($lines[$i]); + $matches = $this->getMatches($docBlock->getLine($i)->getContent()); if (null === $matches) { continue; @@ -234,11 +233,11 @@ private function fixDocBlock($content) $items[] = $matches; while (true) { - if (!isset($lines[++$i])) { + if (null === $docBlock->getLine(++$i)) { break 2; } - $matches = $this->getMatches($lines[$i], true); + $matches = $this->getMatches($docBlock->getLine($i)->getContent(), true); if (null === $matches) { break; } @@ -256,9 +255,9 @@ private function fixDocBlock($content) continue; } - $tagMax = max($tagMax, strlen($item['tag'])); - $hintMax = max($hintMax, strlen($item['hint'])); - $varMax = max($varMax, strlen($item['var'])); + $tagMax = max($tagMax, \strlen($item['tag'])); + $hintMax = max($hintMax, \strlen($item['hint'])); + $varMax = max($varMax, \strlen($item['var'])); } $currTag = null; @@ -267,14 +266,14 @@ private function fixDocBlock($content) foreach ($items as $j => $item) { if (null === $item['tag']) { if ('@' === $item['desc'][0]) { - $lines[$current + $j] = $item['indent'].' * '.$item['desc'].$lineEnding; + $docBlock->getLine($current + $j)->setContent($item['indent'].' * '.$item['desc'].$lineEnding); continue; } $extraIndent = 2; - if (in_array($currTag, self::$tagsWithName, true) || in_array($currTag, self::$tagsWithMethodSignature, true)) { + if (\in_array($currTag, self::$tagsWithName, true) || \in_array($currTag, self::$tagsWithMethodSignature, true)) { $extraIndent = 3; } @@ -288,7 +287,7 @@ private function fixDocBlock($content) .$item['desc'] .$lineEnding; - $lines[$current + $j] = $line; + $docBlock->getLine($current + $j)->setContent($line); continue; } @@ -300,7 +299,7 @@ private function fixDocBlock($content) .' * @' .$item['tag'] .$this->getIndent( - $tagMax - strlen($item['tag']) + 1, + $tagMax - \strlen($item['tag']) + 1, $item['hint'] ? 1 : 0 ) .$item['hint'] @@ -308,25 +307,23 @@ private function fixDocBlock($content) if (!empty($item['var'])) { $line .= - $this->getIndent(($hintMax ?: -1) - strlen($item['hint']) + 1) + $this->getIndent(($hintMax ?: -1) - \strlen($item['hint']) + 1) .$item['var'] .( !empty($item['desc']) - ? $this->getIndent($varMax - strlen($item['var']) + 1).$item['desc'].$lineEnding + ? $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; + $line .= $this->getIndent($hintMax - \strlen($item['hint']) + 1).$item['desc'].$lineEnding; } else { $line .= $lineEnding; } - $lines[$current + $j] = $line; + $docBlock->getLine($current + $j)->setContent($line); } } - - return implode($lines); } /** @@ -376,7 +373,7 @@ private function getIndent($verticalAlignIndent, $leftAlignIndent = 1) { $indent = self::ALIGN_VERTICAL === $this->align ? $verticalAlignIndent : $leftAlignIndent; - return \str_repeat(' ', $indent); + return str_repeat(' ', $indent); } /** @@ -425,7 +422,7 @@ private function getSentenceIndent($sentence) return 0; } - $length = strlen($sentence); + $length = \strlen($sentence); return 0 === $length ? 0 : $length + 1; } diff --git a/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php b/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php index 0db5a293a29..2be09120a69 100644 --- a/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php +++ b/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php @@ -70,22 +70,31 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) foreach ($annotations as $annotation) { if ( - !$annotation->getTag()->valid() || !in_array($annotation->getTag()->getName(), $this->tags, true) + !$annotation->getTag()->valid() || !\in_array($annotation->getTag()->getName(), $this->tags, true) ) { continue; } + $lineAfterAnnotation = $doc->getLine($annotation->getEnd() + 1); + if (null !== $lineAfterAnnotation) { + $lineAfterAnnotationTrimmed = ltrim($lineAfterAnnotation->getContent()); + if ('' === $lineAfterAnnotationTrimmed || '*' !== $lineAfterAnnotationTrimmed[0]) { + // malformed PHPDoc, missing asterisk ! + continue; + } + } + $content = $annotation->getContent(); if ( - 1 !== Preg::match('/[.。]$/u', $content) - || 0 !== Preg::match('/[.。](?!$)/u', $content, $matches) + 1 !== Preg::match('/[.。]\h*$/u', $content) + || 0 !== Preg::match('/[.。](?!\h*$)/u', $content, $matches) ) { continue; } $endLine = $doc->getLine($annotation->getEnd()); - $endLine->setContent(Preg::replace('/(?getContent())); + $endLine->setContent(Preg::replace('/(?getContent())); $startLine = $doc->getLine($annotation->getStart()); $optionalTypeRegEx = $annotation->supportTypes() diff --git a/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php b/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php index 73d412306c4..716d7e988c3 100644 --- a/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php +++ b/src/Fixer/Phpdoc/PhpdocInlineTagFixer.php @@ -32,7 +32,7 @@ public function getDefinition() return new FixerDefinition( 'Fix PHPDoc inline tags, make `@inheritdoc` always inline.', [new CodeSample( -' $to) { - if (!is_string($from)) { + if (!\is_string($from)) { throw new InvalidOptionsException('Tag to replace must be a string.'); } - if (!is_string($to)) { + if (!\is_string($to)) { throw new InvalidOptionsException(sprintf( 'Tag to replace to from "%s" must be a string.', $from diff --git a/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php b/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php index ca713f46e5b..da266f446ec 100644 --- a/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php +++ b/src/Fixer/Phpdoc/PhpdocNoEmptyReturnFixer.php @@ -17,7 +17,6 @@ use PhpCsFixer\DocBlock\DocBlock; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; -use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; @@ -93,6 +92,18 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $this->fixAnnotation($doc, $annotation); } + $newContent = $doc->getContent(); + + if ($newContent === $token->getContent()) { + continue; + } + + if ('' === $newContent) { + $tokens->clearTokenAndMergeSurroundingWhitespace($index); + + continue; + } + $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); } } @@ -105,7 +116,9 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) */ private function fixAnnotation(DocBlock $doc, Annotation $annotation) { - if (1 === Preg::match('/@return\s+(void|null)(?!\|)/', $doc->getLine($annotation->getStart())->getContent())) { + $types = $annotation->getNormalizedTypes(); + + if (1 === \count($types) && ('null' === $types[0] || 'void' === $types[0])) { $annotation->remove(); } } diff --git a/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php b/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php index b46f01a10a6..78ebb4c244b 100644 --- a/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php +++ b/src/Fixer/Phpdoc/PhpdocNoUselessInheritdocFixer.php @@ -65,7 +65,7 @@ public function isCandidate(Tokens $tokens) protected function applyFix(\SplFileInfo $file, Tokens $tokens) { // min. offset 4 as minimal candidate is @: isGivenKind([T_CLASS, T_INTERFACE])) { $index = $this->fixClassy($tokens, $index); } diff --git a/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php b/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php index 79701f585c3..d4913f8aab6 100644 --- a/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php +++ b/src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php @@ -98,7 +98,7 @@ public function test2() */ public function isCandidate(Tokens $tokens) { - return count($tokens) > 10 && $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); + return \count($tokens) > 10 && $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]); } /** @@ -143,22 +143,22 @@ protected function createConfigurationDefinition() ->setNormalizer(static function (Options $options, $value) use ($default) { $normalizedValue = []; foreach ($value as $from => $to) { - if (is_string($from)) { + if (\is_string($from)) { $from = strtolower($from); } if (!isset($default[$from])) { throw new InvalidOptionsException(sprintf( 'Unknown key "%s", expected any of "%s".', - is_object($from) ? get_class($from) : gettype($from).(is_resource($from) ? '' : '#'.$from), + \is_object($from) ? \get_class($from) : \gettype($from).(\is_resource($from) ? '' : '#'.$from), implode('", "', array_keys($default)) )); } - if (!in_array($to, self::$toTypes, true)) { + if (!\in_array($to, self::$toTypes, true)) { throw new InvalidOptionsException(sprintf( 'Unknown value "%s", expected any of "%s".', - is_object($to) ? get_class($to) : gettype($to).(is_resource($to) ? '' : '#'.$to), + \is_object($to) ? \get_class($to) : \gettype($to).(\is_resource($to) ? '' : '#'.$to), implode('", "', self::$toTypes) )); } @@ -200,14 +200,14 @@ private function fixMethod(Tokens $tokens, $index) $docBlock = new DocBlock($tokens[$docIndex]->getContent()); $returnsBlock = $docBlock->getAnnotationsOfType('return'); - if (!count($returnsBlock)) { + if (!\count($returnsBlock)) { return; // no return annotation found } $returnsBlock = $returnsBlock[0]; $types = $returnsBlock->getTypes(); - if (!count($types)) { + if (!\count($types)) { return; // no return type(s) found } diff --git a/src/Fixer/Phpdoc/PhpdocScalarFixer.php b/src/Fixer/Phpdoc/PhpdocScalarFixer.php index b581f13b671..ed02d68e53c 100644 --- a/src/Fixer/Phpdoc/PhpdocScalarFixer.php +++ b/src/Fixer/Phpdoc/PhpdocScalarFixer.php @@ -94,7 +94,7 @@ protected function createConfigurationDefinition() */ protected function normalize($type) { - if (in_array($type, $this->configuration['types'], true)) { + if (\in_array($type, $this->configuration['types'], true)) { return self::$types[$type]; } diff --git a/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php b/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php index 2e9d52402a7..d042ac08cfc 100644 --- a/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php +++ b/src/Fixer/Phpdoc/PhpdocSingleLineVarSpacingFixer.php @@ -90,7 +90,7 @@ private function fixTokenContent($content) '#^/\*\*[ \t]*@var[ \t]+(\S+)[ \t]*(\$\S+)?[ \t]*([^\n]*)\*/$#', static function (array $matches) { $content = '/** @var'; - for ($i = 1, $m = count($matches); $i < $m; ++$i) { + for ($i = 1, $m = \count($matches); $i < $m; ++$i) { if ('' !== $matches[$i]) { $content .= ' '.$matches[$i]; } diff --git a/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php b/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php index 349c9534d7f..872b790cd2d 100644 --- a/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php +++ b/src/Fixer/Phpdoc/PhpdocTrimConsecutiveBlankLineSeparationFixer.php @@ -117,7 +117,7 @@ private function fixDescription(DocBlock $doc, $summaryEnd) return; // no Description } - if ($annotationStart === count($doc->getLines()) - 1) { + if ($annotationStart === \count($doc->getLines()) - 1) { return; // no content after Description } @@ -127,7 +127,7 @@ private function fixDescription(DocBlock $doc, $summaryEnd) private function fixAllTheRest(DocBlock $doc) { $annotationStart = $this->findFirstAnnotationOrEnd($doc); - $lastLine = $this->reverseFindLastUsefulContent($doc, count($doc->getLines()) - 1); + $lastLine = $this->reverseFindLastUsefulContent($doc, \count($doc->getLines()) - 1); if (null !== $lastLine && $annotationStart !== $lastLine) { $this->removeExtraBlankLinesBetween($doc, $annotationStart, $lastLine); diff --git a/src/Fixer/Phpdoc/PhpdocTypesFixer.php b/src/Fixer/Phpdoc/PhpdocTypesFixer.php index d87ba839e06..fa06fd4fec4 100644 --- a/src/Fixer/Phpdoc/PhpdocTypesFixer.php +++ b/src/Fixer/Phpdoc/PhpdocTypesFixer.php @@ -13,46 +13,74 @@ namespace PhpCsFixer\Fixer\Phpdoc; use PhpCsFixer\AbstractPhpdocTypesFixer; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; +use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerConfiguration\FixerOptionBuilder; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; /** * @author Graham Campbell + * @author Dariusz Rumiński */ -final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer +final class PhpdocTypesFixer extends AbstractPhpdocTypesFixer implements ConfigurableFixerInterface { /** - * The types to process. + * Available types, grouped. * - * @var string[] + * @var array */ - private static $types = [ - 'array', - 'bool', - 'boolean', - 'callable', - 'callback', - 'double', - 'false', - 'float', - 'int', - 'integer', - 'iterable', - 'mixed', - 'null', - 'object', - 'parent', - 'real', - 'resource', - 'scalar', - 'self', - 'static', - 'string', - 'true', - 'void', - '$this', + private static $possibleTypes = [ + 'simple' => [ + 'array', + 'bool', + 'callable', + 'float', + 'int', + 'iterable', + 'null', + 'object', + 'string', + ], + 'alias' => [ + 'boolean', + 'callback', + 'double', + 'integer', + 'real', + ], + 'meta' => [ + '$this', + 'false', + 'mixed', + 'parent', + 'resource', + 'scalar', + 'self', + 'static', + 'true', + 'void', + ], ]; + /** + * @var array string[] + */ + private $typesToFix = []; + + /** + * {@inheritdoc} + */ + public function configure(array $configuration = null) + { + parent::configure($configuration); + + $this->typesToFix = array_merge(...array_map(function ($group) { + return self::$possibleTypes[$group]; + }, $this->configuration['groups'])); + } + /** * {@inheritdoc} */ @@ -94,10 +122,26 @@ protected function normalize($type) { $lower = strtolower($type); - if (in_array($lower, self::$types, true)) { + if (\in_array($lower, $this->typesToFix, true)) { return $lower; } return $type; } + + /** + * {@inheritdoc} + */ + protected function createConfigurationDefinition() + { + $possibleGroups = array_keys(self::$possibleTypes); + + return new FixerConfigurationResolver([ + (new FixerOptionBuilder('groups', 'Type groups to fix.')) + ->setAllowedTypes(['array']) + ->setAllowedValues([new AllowedValueSubset($possibleGroups)]) + ->setDefault($possibleGroups) + ->getOption(), + ]); + } } diff --git a/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php b/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php index adf46def19c..7030541e331 100644 --- a/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php +++ b/src/Fixer/Phpdoc/PhpdocTypesOrderFixer.php @@ -119,7 +119,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $doc = new DocBlock($token->getContent()); $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes()); - if (!count($annotations)) { + if (!\count($annotations)) { continue; } @@ -181,7 +181,7 @@ static function ($typeA, $typeB) { } } - if (count($nulls)) { + if (\count($nulls)) { if ('always_last' === $this->configuration['null_adjustment']) { array_push($types, ...$nulls); } else { diff --git a/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php b/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php new file mode 100644 index 00000000000..95385e097d5 --- /dev/null +++ b/src/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixer.php @@ -0,0 +1,67 @@ + + * 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\FixerDefinition\CodeSample; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Kuba Werłos + */ +final class PhpdocVarAnnotationCorrectOrderFixer extends AbstractFixer +{ + public function getDefinition() + { + return new FixerDefinition( + '`@var` and `@type` annotations must have type and name in the correct order.', + [new CodeSample('isTokenKindFound(T_DOC_COMMENT); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_DOC_COMMENT)) { + continue; + } + + if (false === stripos($token->getContent(), '@var') && false === stripos($token->getContent(), '@type')) { + continue; + } + + $newContent = Preg::replace( + '/(@(?:type|var)\s*)(\$\S+)(\s+)([^\$](?:[^<\s]|<[^>]*>)*)(\s|\*)/i', + '$1$4$3$2$5', + $token->getContent() + ); + + if ($newContent === $token->getContent()) { + continue; + } + + $tokens[$index] = new Token([$token->getId(), $newContent]); + } + } +} diff --git a/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php b/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php index 0085e767321..0ce1dcc51c3 100644 --- a/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php +++ b/src/Fixer/Phpdoc/PhpdocVarWithoutNameFixer.php @@ -71,14 +71,14 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $doc = new DocBlock($token->getContent()); // don't process single line docblocks - if (1 === count($doc->getLines())) { + if (1 === \count($doc->getLines())) { continue; } $annotations = $doc->getAnnotationsOfType(['param', 'return', 'type', 'var']); // only process docblocks where the first meaningful annotation is @type or @var - if (!isset($annotations[0]) || !in_array($annotations[0]->getTag()->getName(), ['type', 'var'], true)) { + if (!isset($annotations[0]) || !\in_array($annotations[0]->getTag()->getName(), ['type', 'var'], true)) { continue; } diff --git a/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php b/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php index f641acc5efc..d2fd8f902e2 100644 --- a/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php +++ b/src/Fixer/ReturnNotation/ReturnAssignmentFixer.php @@ -58,7 +58,7 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - $tokenCount = count($tokens); + $tokenCount = \count($tokens); for ($index = 1; $index < $tokenCount; ++$index) { if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { @@ -153,6 +153,12 @@ private function fixFunction(Tokens $tokens, $functionIndex, $functionOpenIndex, continue; // don't bother to look into anything else than nested functions as the current is risky already } + if ($tokens[$index]->equals('&')) { + $isRisky = true; + + continue; + } + if ($tokens[$index]->isGivenKind(T_RETURN)) { $candidates[] = $index; @@ -189,7 +195,7 @@ private function fixFunction(Tokens $tokens, $functionIndex, $functionOpenIndex, } // fix the candidates in reverse order when applicable - for ($i = count($candidates) - 1; $i >= 0; --$i) { + for ($i = \count($candidates) - 1; $i >= 0; --$i) { $index = $candidates[$i]; // Check if returning only a variable (i.e. not the result of an expression, function call etc.) diff --git a/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php b/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php index 832a3060899..7e9f10a0af1 100644 --- a/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php +++ b/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php @@ -35,7 +35,7 @@ public function getDefinition() [ new CodeSample("setAllowedValues([self::STRATEGY_NO_MULTI_LINE, self::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS]) ->setDefault(self::STRATEGY_NO_MULTI_LINE) @@ -134,7 +134,7 @@ private function applyNoMultiLineFix(Tokens $tokens) private function applyChainedCallsFix(Tokens $tokens) { - for ($index = count($tokens) - 1; $index >= 0; --$index) { + for ($index = \count($tokens) - 1; $index >= 0; --$index) { // continue if token is not a semicolon if (!$tokens[$index]->equals(';')) { continue; @@ -175,7 +175,7 @@ private function getNewLineIndex($index, Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); - for ($index, $count = count($tokens); $index < $count; ++$index) { + for ($index, $count = \count($tokens); $index < $count; ++$index) { if (false !== strstr($tokens[$index]->getContent(), $lineEnding)) { return $index; } diff --git a/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php b/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php index 619c1666135..35ee221af5c 100644 --- a/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php +++ b/src/Fixer/Semicolon/SemicolonAfterInstructionFixer.php @@ -47,7 +47,7 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - for ($index = count($tokens) - 1; $index > 1; --$index) { + for ($index = \count($tokens) - 1; $index > 1; --$index) { if (!$tokens[$index]->isGivenKind(T_CLOSE_TAG)) { continue; } diff --git a/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php b/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php index 2dd9cd5b5df..aaf547c1109 100644 --- a/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php +++ b/src/Fixer/Semicolon/SpaceAfterSemicolonFixer.php @@ -85,7 +85,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $insideForParenthesesUntil = null; - for ($index = 0, $max = count($tokens) - 1; $index < $max; ++$index) { + for ($index = 0, $max = \count($tokens) - 1; $index < $max; ++$index) { if ($this->configuration['remove_in_empty_for_expressions']) { if ($tokens[$index]->isGivenKind(T_FOR)) { $index = $tokens->getNextMeaningfulToken($index); diff --git a/src/Fixer/Strict/DeclareStrictTypesFixer.php b/src/Fixer/Strict/DeclareStrictTypesFixer.php index 2d1b1f5d978..013a6c7bd2c 100644 --- a/src/Fixer/Strict/DeclareStrictTypesFixer.php +++ b/src/Fixer/Strict/DeclareStrictTypesFixer.php @@ -139,7 +139,7 @@ private function insertSequence(Tokens $tokens) { $sequence = $this->getDeclareStrictTypeSequence(); $sequence[] = new Token(';'); - $endIndex = count($sequence); + $endIndex = \count($sequence); $tokens->insertAt(1, $sequence); @@ -149,7 +149,7 @@ private function insertSequence(Tokens $tokens) $tokens[0] = new Token([$tokens[0]->getId(), trim($tokens[0]->getContent()).' ']); } - if ($endIndex === count($tokens) - 1) { + if ($endIndex === \count($tokens) - 1) { return; // no more tokens afters sequence, single_blank_line_at_eof might add a line } diff --git a/src/Fixer/Strict/StrictParamFixer.php b/src/Fixer/Strict/StrictParamFixer.php index f0e944e4ac3..8fdef9d8b98 100644 --- a/src/Fixer/Strict/StrictParamFixer.php +++ b/src/Fixer/Strict/StrictParamFixer.php @@ -77,7 +77,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $previousIndex = $tokens->getPrevMeaningfulToken($index); if (null !== $previousIndex && $tokens[$previousIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { - return; + continue; } $lowercaseContent = strtolower($token->getContent()); @@ -91,14 +91,15 @@ private function fixFunction(Tokens $tokens, $functionIndex, array $functionPara { $startBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']); $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startBraceIndex); - $commaCounter = 0; - $sawParameter = false; + $paramsQuantity = 0; + $expectParam = true; for ($index = $startBraceIndex + 1; $index < $endBraceIndex; ++$index) { $token = $tokens[$index]; - if (!$token->isWhitespace() && !$token->isComment()) { - $sawParameter = true; + if ($expectParam && !$token->isWhitespace() && !$token->isComment()) { + ++$paramsQuantity; + $expectParam = false; } if ($token->equals('(')) { @@ -114,14 +115,13 @@ private function fixFunction(Tokens $tokens, $functionIndex, array $functionPara } if ($token->equals(',')) { - ++$commaCounter; + $expectParam = true; continue; } } - $functionParamsQuantity = count($functionParams); - $paramsQuantity = ($sawParameter ? 1 : 0) + $commaCounter; + $functionParamsQuantity = \count($functionParams); if ($paramsQuantity === $functionParamsQuantity) { return; @@ -137,7 +137,7 @@ private function fixFunction(Tokens $tokens, $functionIndex, array $functionPara $tokensToInsert[] = new Token(','); $tokensToInsert[] = new Token([T_WHITESPACE, ' ']); - if (!is_array($functionParams[$i])) { + if (!\is_array($functionParams[$i])) { $tokensToInsert[] = clone $functionParams[$i]; continue; @@ -148,7 +148,7 @@ private function fixFunction(Tokens $tokens, $functionIndex, array $functionPara } } - $beforeEndBraceIndex = $tokens->getPrevNonWhitespace($endBraceIndex); + $beforeEndBraceIndex = $tokens->getTokenNotOfKindSibling($endBraceIndex, -1, [[T_WHITESPACE], ',']); $tokens->insertAt($beforeEndBraceIndex + 1, $tokensToInsert); } } diff --git a/src/Fixer/StringNotation/ExplicitStringVariableFixer.php b/src/Fixer/StringNotation/ExplicitStringVariableFixer.php index dca33542ec7..078dbbb0062 100644 --- a/src/Fixer/StringNotation/ExplicitStringVariableFixer.php +++ b/src/Fixer/StringNotation/ExplicitStringVariableFixer.php @@ -45,6 +45,7 @@ public function getDefinition() ."\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/hightlight 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' ); } @@ -61,9 +62,16 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - for ($index = count($tokens) - 1; $index > 0; --$index) { + $backtickStarted = false; + for ($index = \count($tokens) - 1; $index > 0; --$index) { $token = $tokens[$index]; - if (!$token->isGivenKind(T_VARIABLE)) { + if ($token->equals('`')) { + $backtickStarted = !$backtickStarted; + + continue; + } + + if ($backtickStarted || !$token->isGivenKind(T_VARIABLE)) { continue; } @@ -72,34 +80,58 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } + $distinctVariableIndex = $index; $variableTokens = [ - $index => $token, + $distinctVariableIndex => [ + 'tokens' => [$index => $token], + 'firstVariableTokenIndex' => $index, + 'lastVariableTokenIndex' => $index, + ], ]; - $firstVariableTokenIndex = $index; - $lastVariableTokenIndex = $index; $nextIndex = $index + 1; while (!$this->isStringPartToken($tokens[$nextIndex])) { - $variableTokens[$nextIndex] = $tokens[$nextIndex]; - $lastVariableTokenIndex = $nextIndex; + if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) { + $distinctVariableIndex = $nextIndex; + $variableTokens[$distinctVariableIndex] = [ + 'tokens' => [$nextIndex => $tokens[$nextIndex]], + 'firstVariableTokenIndex' => $nextIndex, + 'lastVariableTokenIndex' => $nextIndex, + ]; + } else { + $variableTokens[$distinctVariableIndex]['tokens'][$nextIndex] = $tokens[$nextIndex]; + $variableTokens[$distinctVariableIndex]['lastVariableTokenIndex'] = $nextIndex; + } + ++$nextIndex; } + krsort($variableTokens, \SORT_NUMERIC); + + foreach ($variableTokens as $distinctVariableSet) { + if (1 === \count($distinctVariableSet['tokens'])) { + $singleVariableIndex = key($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, '}']), + ]); + } else { + foreach ($distinctVariableSet['tokens'] as $variablePartIndex => $variablePartToken) { + if ($variablePartToken->isGivenKind(T_NUM_STRING)) { + $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]); + + continue; + } - if (1 === count($variableTokens)) { - $tokens->overrideRange($index, $index, [ - new Token([T_DOLLAR_OPEN_CURLY_BRACES, '${']), - new Token([T_STRING_VARNAME, substr($token->getContent(), 1)]), - new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']), - ]); - } else { - foreach ($variableTokens as $variablePartIndex => $variablePartToken) { - if ($variablePartToken->isGivenKind(T_NUM_STRING)) { - $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]); + if ($variablePartToken->isGivenKind(T_STRING) && $tokens[$variablePartIndex + 1]->equals(']')) { + $tokens[$variablePartIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$variablePartToken->getContent()."'"]); + } } - } - $tokens->insertAt($lastVariableTokenIndex + 1, new Token([CT::T_CURLY_CLOSE, '}'])); - $tokens->insertAt($firstVariableTokenIndex, new Token([T_CURLY_OPEN, '{'])); + $tokens->insertAt($distinctVariableSet['lastVariableTokenIndex'] + 1, new Token([CT::T_CURLY_CLOSE, '}'])); + $tokens->insertAt($distinctVariableSet['firstVariableTokenIndex'], new Token([T_CURLY_OPEN, '{'])); + } } } } diff --git a/src/Fixer/StringNotation/HeredocToNowdocFixer.php b/src/Fixer/StringNotation/HeredocToNowdocFixer.php index 7b448ba0f4b..610b1e22835 100644 --- a/src/Fixer/StringNotation/HeredocToNowdocFixer.php +++ b/src/Fixer/StringNotation/HeredocToNowdocFixer.php @@ -33,7 +33,7 @@ public function getDefinition() 'Convert `heredoc` to `nowdoc` where possible.', [ new CodeSample( -<<<'EOF' + <<<'EOF' findArrayTokenRanges($tokens, 0, count($tokens) - 1) as $arrayTokenRanges) { + foreach ($this->findArrayTokenRanges($tokens, 0, \count($tokens) - 1) as $arrayTokenRanges) { $array = [ 'start' => $arrayTokenRanges[0][0], - 'end' => $arrayTokenRanges[count($arrayTokenRanges) - 1][1], + 'end' => $arrayTokenRanges[\count($arrayTokenRanges) - 1][1], 'token_ranges' => $arrayTokenRanges, ]; @@ -300,7 +300,7 @@ private function getLineIndentation(Tokens $tokens, $index) private function extractIndent($content) { - if (Preg::match('/\R([\t ]*)[^\r\n]*$/', $content, $matches)) { + if (Preg::match('/\R([\t ]*)[^\r\n]*$/D', $content, $matches)) { return $matches[1]; } diff --git a/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php b/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php index 89bc1f638f4..9dce11b47b0 100644 --- a/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php +++ b/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php @@ -209,7 +209,7 @@ function A() { ] ), new CodeSample( -'bar(); throw new \UnexpectedValueException("A cannot be null"); @@ -220,7 +220,7 @@ function A() { ] ), new CodeSample( -'bar(); diff --git a/src/Fixer/Whitespace/HeredocIndentationFixer.php b/src/Fixer/Whitespace/HeredocIndentationFixer.php new file mode 100644 index 00000000000..b063763fee5 --- /dev/null +++ b/src/Fixer/Whitespace/HeredocIndentationFixer.php @@ -0,0 +1,150 @@ + + * 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\FixerDefinition; +use PhpCsFixer\FixerDefinition\VersionSpecification; +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; +use PhpCsFixer\Preg; +use PhpCsFixer\Tokenizer\Token; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Gregor Harlan + */ +final class HeredocIndentationFixer extends AbstractFixer implements WhitespacesAwareFixerInterface +{ + /** + * {@inheritdoc} + */ + public function getDefinition() + { + return new FixerDefinition( + 'Heredoc/nowdoc content must be properly indented. Requires PHP >= 7.3.', + [ + new VersionSpecificCodeSample( + <<<'SAMPLE' += 70300 && $tokens->isTokenKindFound(T_START_HEREDOC); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + for ($index = \count($tokens) - 1; 0 <= $index; --$index) { + if (!$tokens[$index]->isGivenKind(T_END_HEREDOC)) { + continue; + } + + $end = $index; + $index = $tokens->getPrevTokenOfKind($index, [[T_START_HEREDOC]]); + + $this->fixIndentation($tokens, $index, $end); + } + } + + /** + * @param int $start + * @param int $end + */ + private function fixIndentation(Tokens $tokens, $start, $end) + { + $indent = $this->getIndentAt($tokens, $start).$this->whitespacesConfig->getIndent(); + + Preg::match('/^[ \t]*/', $tokens[$end]->getContent(), $matches); + $currentIndent = $matches[0]; + + $content = $indent.substr($tokens[$end]->getContent(), \strlen($currentIndent)); + $tokens[$end] = new Token([T_END_HEREDOC, $content]); + + if ($end === $start + 1) { + return; + } + + for ($index = $end - 1, $last = true; $index > $start; --$index, $last = false) { + if (!$tokens[$index]->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_WHITESPACE])) { + continue; + } + + $regexEnd = $last && !$currentIndent ? '(?!$)' : ''; + $content = Preg::replace('/(?<=\v)'.$currentIndent.$regexEnd.'/', $indent, $tokens[$index]->getContent()); + $tokens[$index] = new Token([$tokens[$index]->getId(), $content]); + } + + ++$index; + + if ($tokens[$index]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { + $content = $indent.substr($tokens[$index]->getContent(), \strlen($currentIndent)); + $tokens[$index] = new Token([T_ENCAPSED_AND_WHITESPACE, $content]); + } else { + $tokens->insertAt($index, new Token([T_ENCAPSED_AND_WHITESPACE, $indent])); + } + } + + /** + * @param int $index + * + * @return string + */ + private function getIndentAt(Tokens $tokens, $index) + { + for (; $index >= 0; --$index) { + if (!$tokens[$index]->isGivenKind([T_WHITESPACE, T_INLINE_HTML, T_OPEN_TAG])) { + continue; + } + + $content = $tokens[$index]->getContent(); + + if ($tokens[$index]->isWhitespace() && $tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) { + $content = $tokens[$index - 1]->getContent().$content; + } + + if (1 === Preg::match('/\R([ \t]*)$/', $content, $matches)) { + return $matches[1]; + } + } + + return ''; + } +} diff --git a/src/Fixer/Whitespace/LineEndingFixer.php b/src/Fixer/Whitespace/LineEndingFixer.php index 441a421adef..95ea037052f 100644 --- a/src/Fixer/Whitespace/LineEndingFixer.php +++ b/src/Fixer/Whitespace/LineEndingFixer.php @@ -59,7 +59,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $ending = $this->whitespacesConfig->getLineEnding(); - for ($index = 0, $count = count($tokens); $index < $count; ++$index) { + for ($index = 0, $count = \count($tokens); $index < $count; ++$index) { $token = $tokens[$index]; if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { diff --git a/src/Fixer/Whitespace/MethodChainingIndentationFixer.php b/src/Fixer/Whitespace/MethodChainingIndentationFixer.php index 29a9c5f4cad..d834aa7e1f2 100644 --- a/src/Fixer/Whitespace/MethodChainingIndentationFixer.php +++ b/src/Fixer/Whitespace/MethodChainingIndentationFixer.php @@ -37,6 +37,16 @@ public function getDefinition() ); } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // Should run after BracesFixer + // Should run before ArrayIndentationFixer + return -29; + } + /** * {@inheritdoc} */ @@ -52,7 +62,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { $lineEnding = $this->whitespacesConfig->getLineEnding(); - for ($index = 1, $count = count($tokens); $index < $count; ++$index) { + for ($index = 1, $count = \count($tokens); $index < $count; ++$index) { if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) { continue; } diff --git a/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php b/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php index 21ed25c0054..00604e9f0b8 100644 --- a/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php +++ b/src/Fixer/Whitespace/NoExtraBlankLinesFixer.php @@ -128,7 +128,7 @@ public function getDefinition() 'Removes extra blank lines and/or blank lines following configuration.', [ new CodeSample( -' ['break']] ), new CodeSample( -' ['continue']] ), new CodeSample( -' ['curly_brace_block']] ), new CodeSample( -' ['extra']] ), new CodeSample( -' ['parenthesis_brace_block']] ), new CodeSample( -' ['return']] ), new CodeSample( -' ['square_brace_block']] ), new CodeSample( -' ['throw']] ), new CodeSample( -' ['use']] ), new CodeSample( -' ['use_trait']] ), new CodeSample( -' $expected) { - $this->tokens[$index] = new Token([T_WHITESPACE, implode(array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]); + $this->tokens[$index] = new Token([T_WHITESPACE, implode('', \array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]); } } @@ -407,7 +407,7 @@ private function fixStructureOpenCloseIfMultiLine($index) private function removeEmptyLinesAfterLineWithTokenAt($index) { // find the line break - $tokenCount = count($this->tokens); + $tokenCount = \count($this->tokens); for ($end = $index; $end < $tokenCount; ++$end) { if ( $this->tokens[$end]->equals('}') @@ -430,7 +430,7 @@ private function removeEmptyLinesAfterLineWithTokenAt($index) } $pos = strrpos($content, "\n"); - if ($pos + 2 <= strlen($content)) { // preserve indenting where possible + if ($pos + 2 <= \strlen($content)) { // preserve indenting where possible $newContent = $ending.substr($content, $pos + 1); } else { $newContent = $ending; diff --git a/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php b/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php index ea83deb20d2..764b53de8f7 100644 --- a/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php +++ b/src/Fixer/Whitespace/NoSpacesAroundOffsetFixer.php @@ -60,7 +60,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) continue; } - if (in_array('inside', $this->configuration['positions'], true)) { + if (\in_array('inside', $this->configuration['positions'], true)) { if ($token->equals('[')) { $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index); } else { @@ -78,7 +78,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } } - if (in_array('outside', $this->configuration['positions'], true)) { + if (\in_array('outside', $this->configuration['positions'], true)) { $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($index); if ($tokens[$prevNonWhitespaceIndex]->isComment()) { continue; diff --git a/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php b/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php index 2dc7acb00d9..2d13c282471 100644 --- a/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php +++ b/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php @@ -62,17 +62,37 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - foreach ($tokens as $index => $token) { + for ($index = \count($tokens) - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + if ( + $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) + ) { + $tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]); + if ('' === $whitespaceMatches[2]) { + $tokens->clearAt($index + 1); + } else { + $tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaceMatches[2]]); + } + + continue; + } + if (!$token->isWhitespace()) { continue; } - $lines = Preg::split("/([\r\n]+)/", $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); - $linesSize = count($lines); + $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])) { - $lines[0] = rtrim($lines[0], " \t"); + if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || 1 !== Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) { + $lines[0] = rtrim($lines[0], " \t"); + } for ($i = 1; $i < $linesSize; ++$i) { $trimmedLine = rtrim($lines[$i], " \t"); @@ -81,7 +101,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } } - $content = implode($lines); + $content = implode('', $lines); if ('' !== $content) { $tokens[$index] = new Token([$token->getId(), $content]); } else { diff --git a/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php b/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php index b5616a0e996..297be61a65e 100644 --- a/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php +++ b/src/Fixer/Whitespace/NoWhitespaceInBlankLineFixer.php @@ -59,7 +59,7 @@ public function isCandidate(Tokens $tokens) protected function applyFix(\SplFileInfo $file, Tokens $tokens) { // skip first as it cannot be a white space token - for ($i = 1, $count = count($tokens); $i < $count; ++$i) { + for ($i = 1, $count = \count($tokens); $i < $count; ++$i) { if ($tokens[$i]->isWhitespace()) { $this->fixWhitespaceToken($tokens, $i); } @@ -74,7 +74,7 @@ private function fixWhitespaceToken(Tokens $tokens, $index) { $content = $tokens[$index]->getContent(); $lines = Preg::split("/(\r\n|\n)/", $content); - $lineCount = count($lines); + $lineCount = \count($lines); if ( // fix T_WHITESPACES with at least 3 lines (eg `\n \n`) diff --git a/src/FixerConfiguration/AllowedValueSubset.php b/src/FixerConfiguration/AllowedValueSubset.php index ffaa8bde5ea..bc4410b7821 100644 --- a/src/FixerConfiguration/AllowedValueSubset.php +++ b/src/FixerConfiguration/AllowedValueSubset.php @@ -33,12 +33,12 @@ public function __construct(array $allowedValues) */ public function __invoke($values) { - if (!is_array($values)) { + if (!\is_array($values)) { return false; } foreach ($values as $value) { - if (!in_array($value, $this->allowedValues, true)) { + if (!\in_array($value, $this->allowedValues, true)) { return false; } } diff --git a/src/FixerConfiguration/FixerConfigurationResolver.php b/src/FixerConfiguration/FixerConfigurationResolver.php index 99b854bac8d..40109958d0d 100644 --- a/src/FixerConfiguration/FixerConfigurationResolver.php +++ b/src/FixerConfiguration/FixerConfigurationResolver.php @@ -62,9 +62,9 @@ public function resolve(array $options) if ($option instanceof AliasedFixerOption) { $alias = $option->getAlias(); - if (array_key_exists($alias, $options)) { + if (\array_key_exists($alias, $options)) { // @TODO 2.12 Trigger a deprecation notice and add a test for it - if (array_key_exists($name, $options)) { + if (\array_key_exists($name, $options)) { throw new InvalidOptionsException(sprintf('Aliased option %s/%s is passed multiple times.', $name, $alias)); } @@ -82,7 +82,7 @@ public function resolve(array $options) $allowedValues = $option->getAllowedValues(); if (null !== $allowedValues) { foreach ($allowedValues as &$allowedValue) { - if (is_object($allowedValue) && is_callable($allowedValue)) { + if (\is_object($allowedValue) && \is_callable($allowedValue)) { $allowedValue = function ($values) use ($allowedValue) { return $allowedValue($values); }; @@ -117,7 +117,7 @@ private function addOption(FixerOptionInterface $option) { $name = $option->getName(); - if (in_array($name, $this->registeredNames, true)) { + if (\in_array($name, $this->registeredNames, true)) { throw new \LogicException(sprintf('The "%s" option is defined multiple times.', $name)); } diff --git a/src/FixerConfiguration/FixerOptionBuilder.php b/src/FixerConfiguration/FixerOptionBuilder.php index 96fba3a2b79..0e2882447b9 100644 --- a/src/FixerConfiguration/FixerOptionBuilder.php +++ b/src/FixerConfiguration/FixerOptionBuilder.php @@ -126,7 +126,7 @@ public function setDeprecationMessage($deprecationMessage) } /** - * @return FixerOption + * @return FixerOptionInterface */ public function getOption() { diff --git a/src/FixerDefinition/VersionSpecification.php b/src/FixerDefinition/VersionSpecification.php index 47bfd58e239..d1459d14008 100644 --- a/src/FixerDefinition/VersionSpecification.php +++ b/src/FixerDefinition/VersionSpecification.php @@ -39,12 +39,12 @@ public function __construct($minimum = null, $maximum = null) throw new \InvalidArgumentException('Minimum or maximum need to be specified.'); } - if (null !== $minimum && (!is_int($minimum) || 1 > $minimum)) { + if (null !== $minimum && (!\is_int($minimum) || 1 > $minimum)) { throw new \InvalidArgumentException('Minimum needs to be either null or an integer greater than 0.'); } if (null !== $maximum) { - if (!is_int($maximum) || 1 > $maximum) { + if (!\is_int($maximum) || 1 > $maximum) { throw new \InvalidArgumentException('Maximum needs to be either null or an integer greater than 0.'); } diff --git a/src/FixerFactory.php b/src/FixerFactory.php index 648e3bbd72a..419686702ea 100644 --- a/src/FixerFactory.php +++ b/src/FixerFactory.php @@ -163,7 +163,7 @@ public function useRuleSet(RuleSetInterface $ruleSet) $fixerNames = array_keys($ruleSet->getRules()); foreach ($fixerNames as $name) { - if (!array_key_exists($name, $this->fixersByName)) { + if (!\array_key_exists($name, $this->fixersByName)) { throw new \UnexpectedValueException(sprintf('Rule "%s" does not exist.', $name)); } @@ -172,7 +172,7 @@ public function useRuleSet(RuleSetInterface $ruleSet) $config = $ruleSet->getRuleConfiguration($name); if (null !== $config) { if ($fixer instanceof ConfigurableFixerInterface) { - if (!is_array($config) || !count($config)) { + if (!\is_array($config) || !\count($config)) { throw new InvalidFixerConfigurationException($fixer->getName(), 'Configuration must be an array and may not be empty.'); } @@ -186,12 +186,12 @@ public function useRuleSet(RuleSetInterface $ruleSet) $fixersByName[$name] = $fixer; $conflicts = array_intersect($this->getFixersConflicts($fixer), $fixerNames); - if (count($conflicts) > 0) { + if (\count($conflicts) > 0) { $fixerConflicts[$name] = $conflicts; } } - if (count($fixerConflicts) > 0) { + if (\count($fixerConflicts) > 0) { throw new \UnexpectedValueException($this->generateConflictMessage($fixerConflicts)); } @@ -226,7 +226,7 @@ private function getFixersConflicts(FixerInterface $fixer) $fixerName = $fixer->getName(); - return array_key_exists($fixerName, $conflictMap) ? $conflictMap[$fixerName] : []; + return \array_key_exists($fixerName, $conflictMap) ? $conflictMap[$fixerName] : []; } /** @@ -243,11 +243,11 @@ private function generateConflictMessage(array $fixerConflicts) $report[$fixer] = array_filter( $fixers, static function ($candidate) use ($report, $fixer) { - return !array_key_exists($candidate, $report) || !in_array($fixer, $report[$candidate], true); + return !\array_key_exists($candidate, $report) || !\in_array($fixer, $report[$candidate], true); } ); - if (count($report[$fixer]) > 0) { + if (\count($report[$fixer]) > 0) { $message .= sprintf("\n- \"%s\" with \"%s\"", $fixer, implode('", "', $report[$fixer])); } } diff --git a/src/Indicator/PhpUnitTestCaseIndicator.php b/src/Indicator/PhpUnitTestCaseIndicator.php index 583ac0d00e4..d3e6bce03c7 100644 --- a/src/Indicator/PhpUnitTestCaseIndicator.php +++ b/src/Indicator/PhpUnitTestCaseIndicator.php @@ -31,8 +31,7 @@ public function isPhpUnitClass(Tokens $tokens, $index) return true; } - do { - $index = $tokens->getNextMeaningfulToken($index); + while (null !== $index = $tokens->getNextMeaningfulToken($index)) { if ($tokens[$index]->equals('{')) { break; // end of class signature } @@ -44,7 +43,7 @@ public function isPhpUnitClass(Tokens $tokens, $index) if (0 !== Preg::match('/(?:Test|TestCase)(?:Interface)?$/', $tokens[$index]->getContent())) { return true; } - } while (true); // safe `true` as we will always hit an `{` after a T_CLASS + } return false; } diff --git a/src/Linter/ProcessLinterProcessBuilder.php b/src/Linter/ProcessLinterProcessBuilder.php index a1e9e08741d..5cce9e12d48 100644 --- a/src/Linter/ProcessLinterProcessBuilder.php +++ b/src/Linter/ProcessLinterProcessBuilder.php @@ -41,10 +41,10 @@ public function __construct($executable) */ public function build($path) { - return new Process(sprintf( - '"%s" -l "%s"', + return new Process([ $this->executable, - $path - )); + '-l', + $path, + ]); } } diff --git a/src/Linter/TokenizerLinter.php b/src/Linter/TokenizerLinter.php index d526f9243de..90d31d52ca4 100644 --- a/src/Linter/TokenizerLinter.php +++ b/src/Linter/TokenizerLinter.php @@ -27,7 +27,7 @@ final class TokenizerLinter implements LinterInterface { public function __construct() { - if (false === defined('TOKEN_PARSE')) { + if (false === \defined('TOKEN_PARSE')) { throw new UnavailableLinterException('Cannot use tokenizer as linter.'); } } diff --git a/src/Preg.php b/src/Preg.php index 9326e697c41..faee5ade239 100644 --- a/src/Preg.php +++ b/src/Preg.php @@ -36,12 +36,12 @@ final class Preg public static function match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) { $result = @preg_match(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset); - if (false !== $result) { + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } $result = @preg_match(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset); - if (false !== $result) { + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } @@ -62,12 +62,12 @@ public static function match($pattern, $subject, &$matches = null, $flags = 0, $ public static function matchAll($pattern, $subject, &$matches = null, $flags = PREG_PATTERN_ORDER, $offset = 0) { $result = @preg_match_all(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset); - if (false !== $result) { + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } $result = @preg_match_all(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset); - if (false !== $result) { + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } @@ -88,12 +88,12 @@ public static function matchAll($pattern, $subject, &$matches = null, $flags = P public static function replace($pattern, $replacement, $subject, $limit = -1, &$count = null) { $result = @preg_replace(self::addUtf8Modifier($pattern), $replacement, $subject, $limit, $count); - if (null !== $result) { + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } $result = @preg_replace(self::removeUtf8Modifier($pattern), $replacement, $subject, $limit, $count); - if (null !== $result) { + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } @@ -114,12 +114,12 @@ public static function replace($pattern, $replacement, $subject, $limit = -1, &$ public static function replaceCallback($pattern, $callback, $subject, $limit = -1, &$count = null) { $result = @preg_replace_callback(self::addUtf8Modifier($pattern), $callback, $subject, $limit, $count); - if (null !== $result) { + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } $result = @preg_replace_callback(self::removeUtf8Modifier($pattern), $callback, $subject, $limit, $count); - if (null !== $result) { + if (null !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } @@ -139,12 +139,12 @@ public static function replaceCallback($pattern, $callback, $subject, $limit = - public static function split($pattern, $subject, $limit = -1, $flags = 0) { $result = @preg_split(self::addUtf8Modifier($pattern), $subject, $limit, $flags); - if (false !== $result) { + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } $result = @preg_split(self::removeUtf8Modifier($pattern), $subject, $limit, $flags); - if (false !== $result) { + if (false !== $result && PREG_NO_ERROR === preg_last_error()) { return $result; } @@ -158,7 +158,7 @@ public static function split($pattern, $subject, $limit = -1, $flags = 0) */ private static function addUtf8Modifier($pattern) { - if (is_array($pattern)) { + if (\is_array($pattern)) { return array_map(__METHOD__, $pattern); } @@ -172,7 +172,7 @@ private static function addUtf8Modifier($pattern) */ private static function removeUtf8Modifier($pattern) { - if (is_array($pattern)) { + if (\is_array($pattern)) { return array_map(__METHOD__, $pattern); } diff --git a/src/Report/CheckstyleReporter.php b/src/Report/CheckstyleReporter.php index c33e98574f3..e5f506f74f9 100644 --- a/src/Report/CheckstyleReporter.php +++ b/src/Report/CheckstyleReporter.php @@ -34,7 +34,7 @@ public function getFormat() */ public function generate(ReportSummary $reportSummary) { - if (!extension_loaded('dom')) { + if (!\extension_loaded('dom')) { throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); } diff --git a/src/Report/JunitReporter.php b/src/Report/JunitReporter.php index ac0a7f45e49..42c5ea3c2cc 100644 --- a/src/Report/JunitReporter.php +++ b/src/Report/JunitReporter.php @@ -35,7 +35,7 @@ public function getFormat() */ public function generate(ReportSummary $reportSummary) { - if (!extension_loaded('dom')) { + if (!\extension_loaded('dom')) { throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); } @@ -45,7 +45,7 @@ public function generate(ReportSummary $reportSummary) $testsuite = $testsuites->appendChild($dom->createElement('testsuite')); $testsuite->setAttribute('name', 'PHP CS Fixer'); - if (count($reportSummary->getChanged())) { + if (\count($reportSummary->getChanged())) { $this->createFailedTestCases($dom, $testsuite, $reportSummary); } else { $this->createSuccessTestCase($dom, $testsuite); @@ -98,7 +98,7 @@ private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite $assertionsCount += (int) $testcase->getAttribute('assertions'); } - $testsuite->setAttribute('tests', (string) count($reportSummary->getChanged())); + $testsuite->setAttribute('tests', (string) \count($reportSummary->getChanged())); $testsuite->setAttribute('assertions', (string) $assertionsCount); $testsuite->setAttribute('failures', (string) $assertionsCount); $testsuite->setAttribute('errors', '0'); @@ -114,7 +114,7 @@ private function createFailedTestCases(\DOMDocument $dom, \DOMElement $testsuite */ private function createFailedTestCase(\DOMDocument $dom, $file, array $fixResult, $shouldAddAppliedFixers) { - $appliedFixersCount = count($fixResult['appliedFixers']); + $appliedFixersCount = \count($fixResult['appliedFixers']); $testName = str_replace('.', '_DOT_', Preg::replace('@\.'.pathinfo($file, PATHINFO_EXTENSION).'$@', '', $file)); diff --git a/src/Report/XmlReporter.php b/src/Report/XmlReporter.php index d3e072bbbd1..16352ec54fc 100644 --- a/src/Report/XmlReporter.php +++ b/src/Report/XmlReporter.php @@ -34,7 +34,7 @@ public function getFormat() */ public function generate(ReportSummary $reportSummary) { - if (!extension_loaded('dom')) { + if (!\extension_loaded('dom')) { throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!'); } diff --git a/src/RuleSet.php b/src/RuleSet.php index 6f1495cfd58..e3490902d0f 100644 --- a/src/RuleSet.php +++ b/src/RuleSet.php @@ -41,7 +41,7 @@ final class RuleSet implements RuleSetInterface 'line_ending' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, - 'method_argument_space' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 'no_break_comment' => true, 'no_closing_tag' => true, 'no_spaces_after_function_name' => true, @@ -77,6 +77,7 @@ final class RuleSet implements RuleSetInterface 'lowercase_cast' => true, 'lowercase_static_reference' => true, 'magic_constant_casing' => true, + 'magic_method_casing' => true, 'method_argument_space' => [ 'on_multiline' => 'ignore', ], @@ -130,6 +131,10 @@ final class RuleSet implements RuleSetInterface 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_types' => true, + 'phpdoc_types_order' => [ + 'null_adjustment' => 'always_last', + 'sort_algorithm' => 'none', + ], 'phpdoc_var_without_name' => true, 'protected_to_private' => true, 'return_type_declaration' => true, @@ -157,7 +162,10 @@ final class RuleSet implements RuleSetInterface 'dir_constant' => true, 'ereg_to_preg' => true, 'error_suppression' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => ['b_mode' => false], 'function_to_constant' => true, + 'implode_call' => true, 'is_null' => true, 'modernize_types_casting' => true, 'native_constant_invocation' => [ @@ -167,10 +175,12 @@ final class RuleSet implements RuleSetInterface 'PHP_SAPI', 'PHP_VERSION_ID', ], + 'scope' => 'namespaced', ], 'native_function_invocation' => [ 'include' => [NativeFunctionInvocationFixer::SET_COMPILER_OPTIMIZED], 'scope' => 'namespaced', + 'strict' => true, ], 'no_alias_functions' => true, 'no_homoglyph_names' => true, @@ -182,6 +192,75 @@ final class RuleSet implements RuleSetInterface 'self_accessor' => true, 'set_type_to_cast' => true, ], + '@PhpCsFixer' => [ + '@Symfony' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_before_statement' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'compact_nullable_typehint' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fully_qualified_strict_types' => true, + 'heredoc_to_nowdoc' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'method_chaining_indentation' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_extra_blank_lines' => ['tokens' => [ + 'break', + 'continue', + 'curly_brace_block', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'throw', + 'use', + ]], + 'no_null_property_initialization' => true, + 'no_short_echo_tag' => true, + 'no_superfluous_elseif' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unset_cast' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'php_unit_internal_class' => true, + 'php_unit_method_casing' => true, + 'php_unit_ordered_covers' => true, + 'php_unit_test_class_requires_covers' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_order' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types_order' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'return_assignment' => true, + 'single_line_comment_style' => true, + ], + '@PhpCsFixer:risky' => [ + '@Symfony:risky' => true, + 'comment_to_phpdoc' => true, + 'final_internal_class' => true, + 'function_to_constant' => true, + 'logical_operators' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_on_property' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_strict' => true, + 'php_unit_test_annotation' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], + 'strict_comparison' => true, + 'strict_param' => true, + 'string_line_ending' => true, + ], '@DoctrineAnnotation' => [ 'doctrine_annotation_array_assignment' => [ 'operator' => ':', @@ -202,6 +281,7 @@ final class RuleSet implements RuleSetInterface ], '@PHP70Migration:risky' => [ '@PHP56Migration:risky' => true, + 'combine_nested_dirname' => true, 'declare_strict_types' => true, 'non_printable_character' => [ 'use_escape_sequences_in_strings' => true, @@ -295,7 +375,7 @@ final class RuleSet implements RuleSetInterface public function __construct(array $set = []) { foreach ($set as $key => $value) { - if (is_int($key)) { + if (\is_int($key)) { throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value)); } } @@ -314,7 +394,7 @@ public static function create(array $set = []) */ public function hasRule($rule) { - return array_key_exists($rule, $this->rules); + return \array_key_exists($rule, $this->rules); } /** @@ -376,7 +456,7 @@ private function resolveSet() // expand sets foreach ($rules as $name => $value) { if ('@' === $name[0]) { - if (!is_bool($value)) { + if (!\is_bool($value)) { throw new \UnexpectedValueException(sprintf('Nested rule set "%s" configuration must be a boolean.', $name)); } diff --git a/src/Runner/FileFilterIterator.php b/src/Runner/FileFilterIterator.php index 21c238f1afa..fe4f11ee081 100644 --- a/src/Runner/FileFilterIterator.php +++ b/src/Runner/FileFilterIterator.php @@ -58,12 +58,12 @@ public function accept() throw new \RuntimeException( sprintf( 'Expected instance of "\SplFileInfo", got "%s".', - is_object($file) ? get_class($file) : gettype($file) + \is_object($file) ? \get_class($file) : \gettype($file) ) ); } - $path = $file->getRealPath(); + $path = $file->isLink() ? $file->getPathname() : $file->getRealPath(); if (isset($this->visitedElements[$path])) { return false; @@ -75,16 +75,7 @@ public function accept() return false; } - $content = @FileReader::createSingleton()->read($path); - if (false === $content) { - $error = error_get_last(); - - throw new \RuntimeException(sprintf( - 'Failed to read content from "%s".%s', - $path, - $error ? ' '.$error['message'] : '' - )); - } + $content = FileReader::createSingleton()->read($path); // mark as skipped: if ( diff --git a/src/Tokenizer/AbstractTransformer.php b/src/Tokenizer/AbstractTransformer.php index c5e000c2fe6..5cca9a81d6b 100644 --- a/src/Tokenizer/AbstractTransformer.php +++ b/src/Tokenizer/AbstractTransformer.php @@ -27,7 +27,7 @@ abstract class AbstractTransformer implements TransformerInterface public function getName() { $nameParts = explode('\\', static::class); - $name = substr(end($nameParts), 0, -strlen('Transformer')); + $name = substr(end($nameParts), 0, -\strlen('Transformer')); return Utils::camelCaseToUnderscore($name); } diff --git a/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php b/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php index 65972ca941e..9d62a03411d 100644 --- a/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php +++ b/src/Tokenizer/Analyzer/Analysis/TypeAnalysis.php @@ -33,7 +33,7 @@ final class TypeAnalysis implements StartEndTokenAwareAnalysis 'bool', 'callable', 'int', - 'iteratable', + 'iterable', 'float', 'mixed', 'numeric', diff --git a/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php b/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php index 460bfd36795..a3cf9199185 100644 --- a/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php +++ b/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php @@ -35,7 +35,7 @@ final class ArgumentsAnalyzer */ public function countArguments(Tokens $tokens, $openParenthesis, $closeParenthesis) { - return count($this->getArguments($tokens, $openParenthesis, $closeParenthesis)); + return \count($this->getArguments($tokens, $openParenthesis, $closeParenthesis)); } /** @@ -76,6 +76,10 @@ public function getArguments(Tokens $tokens, $openParenthesis, $closeParenthesis // if comma matched, increase arguments counter if ($token->equals(',')) { + if ($tokens->getNextMeaningfulToken($paramContentIndex) === $closeParenthesis) { + break; // trailing ',' in function call (PHP 7.3) + } + $arguments[$argumentsStart] = $paramContentIndex - 1; $argumentsStart = $paramContentIndex + 1; } @@ -107,7 +111,7 @@ public function getArgumentInfo(Tokens $tokens, $argumentStart, $argumentEnd) $sawName = false; for ($index = $argumentStart; $index <= $argumentEnd; ++$index) { $token = $tokens[$index]; - if ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS)) { + if ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS) || $token->equals('&')) { continue; } if ($token->isGivenKind(T_VARIABLE)) { diff --git a/src/Tokenizer/Analyzer/CommentsAnalyzer.php b/src/Tokenizer/Analyzer/CommentsAnalyzer.php index bcb12b207d5..6fb59bde78a 100644 --- a/src/Tokenizer/Analyzer/CommentsAnalyzer.php +++ b/src/Tokenizer/Analyzer/CommentsAnalyzer.php @@ -43,7 +43,7 @@ public function isHeaderComment(Tokens $tokens, $index) $prevIndex = $tokens->getPrevMeaningfulToken($index); - return $tokens[$prevIndex]->isGivenKind([T_OPEN_TAG]) && null !== $tokens->getNextMeaningfulToken($index); + return $tokens[$prevIndex]->isGivenKind(T_OPEN_TAG) && null !== $tokens->getNextMeaningfulToken($index); } /** @@ -115,7 +115,7 @@ public function getCommentBlockIndices(Tokens $tokens, $index) return $indices; } - $count = count($tokens); + $count = \count($tokens); ++$index; for (; $index < $count; ++$index) { diff --git a/src/Tokenizer/Analyzer/FunctionsAnalyzer.php b/src/Tokenizer/Analyzer/FunctionsAnalyzer.php index d5ed2cc81c5..46483a0265e 100644 --- a/src/Tokenizer/Analyzer/FunctionsAnalyzer.php +++ b/src/Tokenizer/Analyzer/FunctionsAnalyzer.php @@ -14,6 +14,7 @@ use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis; use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis; +use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Tokens; /** @@ -40,7 +41,7 @@ public function isGlobalFunctionCall(Tokens $tokens, $index) $nextIndex = $tokens->getNextMeaningfulToken($index); - return !$tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_FUNCTION, T_NEW, T_OBJECT_OPERATOR, T_STRING]) + return !$tokens[$prevIndex]->isGivenKind([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, T_OBJECT_OPERATOR, CT::T_RETURN_REF, T_STRING]) && $tokens[$nextIndex]->equals('('); } diff --git a/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php b/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php index c6b154fab85..7b3f4412bfa 100644 --- a/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php +++ b/src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php @@ -83,7 +83,7 @@ private function parseDeclaration(Tokens $tokens, $startIndex, $endIndex) $type = NamespaceUseAnalysis::TYPE_CONSTANT; } - if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind([T_USE])) { + if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) { continue; } diff --git a/src/Tokenizer/Analyzer/NamespacesAnalyzer.php b/src/Tokenizer/Analyzer/NamespacesAnalyzer.php index 75302b2d859..f1668d40131 100644 --- a/src/Tokenizer/Analyzer/NamespacesAnalyzer.php +++ b/src/Tokenizer/Analyzer/NamespacesAnalyzer.php @@ -46,7 +46,7 @@ public function getDeclarations(Tokens $tokens) } else { $scopeEndIndex = $tokens->getNextTokenOfKind($declarationEndIndex, [[T_NAMESPACE]]); if (null === $scopeEndIndex) { - $scopeEndIndex = count($tokens); + $scopeEndIndex = \count($tokens); } --$scopeEndIndex; } @@ -64,8 +64,8 @@ public function getDeclarations(Tokens $tokens) $index = $scopeEndIndex; } - if (0 === count($namespaces)) { - $namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, count($tokens) - 1); + if (0 === \count($namespaces)) { + $namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1); } return $namespaces; diff --git a/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php b/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php index c939cc9fa2f..aeb50c43c84 100644 --- a/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php +++ b/src/Tokenizer/Generator/NamespacedStringTokenGenerator.php @@ -33,7 +33,7 @@ public function generate($input) foreach ($parts as $index => $part) { $tokens[] = new Token([T_STRING, $part]); - if ($index !== count($parts) - 1) { + if ($index !== \count($parts) - 1) { $tokens[] = new Token([T_NS_SEPARATOR, '\\']); } } diff --git a/src/Tokenizer/Resolver/TypeShortNameResolver.php b/src/Tokenizer/Resolver/TypeShortNameResolver.php index 2db4d4d583d..5b26337f943 100644 --- a/src/Tokenizer/Resolver/TypeShortNameResolver.php +++ b/src/Tokenizer/Resolver/TypeShortNameResolver.php @@ -46,7 +46,7 @@ public function resolve(Tokens $tokens, $typeName) // Next try to match (partial) classes inside the same namespace // For now only support one namespace per file: $namespaces = $this->getNamespacesFromTokens($tokens); - if (1 === count($namespaces)) { + if (1 === \count($namespaces)) { foreach ($namespaces as $fullName) { $matches = []; $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P.+)$/'; diff --git a/src/Tokenizer/Token.php b/src/Tokenizer/Token.php index 2a2553630fd..56a18c6f8e3 100644 --- a/src/Tokenizer/Token.php +++ b/src/Tokenizer/Token.php @@ -55,18 +55,18 @@ final class Token */ public function __construct($token) { - if (is_array($token)) { - if (!is_int($token[0])) { + if (\is_array($token)) { + if (!\is_int($token[0])) { throw new \InvalidArgumentException(sprintf( 'Id must be an int, got "%s".', - is_object($token[0]) ? get_class($token[0]) : gettype($token[0]) + \is_object($token[0]) ? \get_class($token[0]) : \gettype($token[0]) )); } - if (!is_string($token[1])) { + if (!\is_string($token[1])) { throw new \InvalidArgumentException(sprintf( 'Content must be a string, got "%s".', - is_object($token[1]) ? get_class($token[1]) : gettype($token[1]) + \is_object($token[1]) ? \get_class($token[1]) : \gettype($token[1]) )); } @@ -81,13 +81,13 @@ public function __construct($token) if ($token[0] && '' === $token[1]) { throw new \InvalidArgumentException('Cannot set empty content for id-based Token.'); } - } elseif (is_string($token)) { + } elseif (\is_string($token)) { $this->isArray = false; $this->content = $token; } else { throw new \InvalidArgumentException(sprintf( 'Cannot recognize input value as valid Token prototype, got "%s".', - is_object($token) ? get_class($token) : gettype($token) + \is_object($token) ? \get_class($token) : \gettype($token) )); } } @@ -126,9 +126,23 @@ public static function getClassyTokenKinds() */ public function equals($other, $caseSensitive = true) { - $otherPrototype = $other instanceof self ? $other->getPrototype() : $other; + if ($other instanceof self) { + // Inlined getPrototype() on this very hot path. + // We access the private properties of $other directly to save function call overhead. + // This is only possible because $other is of the same class as `self`. + if (!$other->isArray) { + $otherPrototype = $other->content; + } else { + $otherPrototype = [ + $other->id, + $other->content, + ]; + } + } else { + $otherPrototype = $other; + } - if ($this->isArray !== is_array($otherPrototype)) { + if ($this->isArray !== \is_array($otherPrototype)) { return false; } @@ -136,11 +150,11 @@ public function equals($other, $caseSensitive = true) return $this->content === $otherPrototype; } - if (array_key_exists(0, $otherPrototype) && $this->id !== $otherPrototype[0]) { + if ($this->id !== $otherPrototype[0]) { return false; } - if (array_key_exists(1, $otherPrototype)) { + if (isset($otherPrototype[1])) { if ($caseSensitive) { if ($this->content !== $otherPrototype[1]) { return false; @@ -187,7 +201,7 @@ public function equalsAny(array $others, $caseSensitive = true) */ public static function isKeyCaseSensitive($caseSensitive, $key) { - if (is_array($caseSensitive)) { + if (\is_array($caseSensitive)) { return isset($caseSensitive[$key]) ? $caseSensitive[$key] : true; } @@ -372,7 +386,7 @@ public function isComment() */ public function isGivenKind($possibleKind) { - return $this->isArray && (is_array($possibleKind) ? in_array($this->id, $possibleKind, true) : $this->id === $possibleKind); + return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind); } /** @@ -396,7 +410,7 @@ public function isNativeConstant() { static $nativeConstantStrings = ['true', 'false', 'null']; - return $this->isArray && in_array(strtolower($this->content), $nativeConstantStrings, true); + return $this->isArray && \in_array(strtolower($this->content), $nativeConstantStrings, true); } /** @@ -475,8 +489,8 @@ private static function getTokenKindsForNames(array $tokenNames) { $keywords = []; foreach ($tokenNames as $keywordName) { - if (defined($keywordName)) { - $keyword = constant($keywordName); + if (\defined($keywordName)) { + $keyword = \constant($keywordName); $keywords[$keyword] = $keyword; } } diff --git a/src/Tokenizer/Tokens.php b/src/Tokenizer/Tokens.php index 1e4c452b56d..ffe557bf3ab 100644 --- a/src/Tokenizer/Tokens.php +++ b/src/Tokenizer/Tokens.php @@ -139,7 +139,7 @@ public static function detectBlockType(Token $token) */ public static function fromArray($array, $saveIndexes = null) { - $tokens = new self(count($array)); + $tokens = new self(\count($array)); if (null === $saveIndexes || $saveIndexes) { foreach ($array as $key => $val) { @@ -278,13 +278,14 @@ public function offsetSet($index, $newval) if (!$this[$index] || !$this[$index]->equals($newval)) { $this->changed = true; - } - if (isset($this[$index])) { - $this->unregisterFoundToken($this[$index]); + if (isset($this[$index])) { + $this->unregisterFoundToken($this[$index]); + } + + $this->registerFoundToken($newval); } - $this->registerFoundToken($newval); parent::offsetSet($index, $newval); } @@ -347,7 +348,7 @@ public function ensureWhitespaceAtIndex($index, $indexOffset, $whitespace) if (0 === strpos($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 // can be removed on PHP 7; https://php.net/manual/en/function.substr.php ? substr($whitespace, 2) : '' ; @@ -355,7 +356,7 @@ public function ensureWhitespaceAtIndex($index, $indexOffset, $whitespace) $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 // can be removed on PHP 7; https://php.net/manual/en/function.substr.php ? substr($whitespace, 1) : '' ; @@ -400,7 +401,7 @@ public function ensureWhitespaceAtIndex($index, $indexOffset, $whitespace) */ public function findBlockEnd($type, $searchIndex, $findEnd = true) { - if (3 === func_num_args()) { + if (3 === \func_num_args()) { if ($findEnd) { @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, you can safely drop the argument.', E_USER_DEPRECATED); } else { @@ -447,7 +448,7 @@ public function findGivenKind($possibleKind, $start = 0, $end = null) return $this->isTokenKindFound($kind); }); - if (count($possibleKinds)) { + if (\count($possibleKinds)) { for ($i = $start; $i < $end; ++$i) { $token = $this[$i]; if ($token->isGivenKind($possibleKinds)) { @@ -456,7 +457,7 @@ public function findGivenKind($possibleKind, $start = 0, $end = null) } } - return is_array($possibleKind) ? $elements : $elements[$possibleKind]; + return \is_array($possibleKind) ? $elements : $elements[$possibleKind]; } /** @@ -464,7 +465,7 @@ public function findGivenKind($possibleKind, $start = 0, $end = null) */ public function generateCode() { - $code = $this->generatePartialCode(0, count($this) - 1); + $code = $this->generatePartialCode(0, \count($this) - 1); $this->changeCodeHash(self::calculateCodeHash($code)); return $code; @@ -602,7 +603,7 @@ public function getTokenOfKindSibling($index, $direction, array $tokens = [], $c return $this->isTokenKindFound($this->extractTokenKind($token)); }); - if (!count($tokens)) { + if (!\count($tokens)) { return null; } @@ -729,13 +730,13 @@ public function getPrevMeaningfulToken($index) */ public function findSequence(array $sequence, $start = 0, $end = null, $caseSensitive = true) { - $sequenceCount = count($sequence); + $sequenceCount = \count($sequence); if (0 === $sequenceCount) { throw new \InvalidArgumentException('Invalid sequence.'); } // $end defaults to the end of the collection - $end = null === $end ? count($this) - 1 : min($end, count($this) - 1); + $end = null === $end ? \count($this) - 1 : min($end, \count($this) - 1); if ($start + $sequenceCount - 1 > $end) { return null; @@ -745,7 +746,7 @@ public function findSequence(array $sequence, $start = 0, $end = null, $caseSens foreach ($sequence as $key => $token) { // if not a Token instance already, we convert it to verify the meaningfulness if (!$token instanceof Token) { - if (is_array($token) && !isset($token[1])) { + if (\is_array($token) && !isset($token[1])) { // fake some content as it is required by the Token constructor, // although optional for search purposes $token[1] = 'DUMMY'; @@ -807,7 +808,7 @@ public function findSequence(array $sequence, $start = 0, $end = null, $caseSens // do we have a complete match? // hint: $result is bigger than $sequence since the first token has been removed from the latter - if (count($sequence) < count($result)) { + if (\count($sequence) < \count($result)) { return $result; } } @@ -821,19 +822,23 @@ public function findSequence(array $sequence, $start = 0, $end = null, $caseSens */ public function insertAt($index, $items) { - $items = is_array($items) || $items instanceof self ? $items : [$items]; - $itemsCnt = count($items); + $items = \is_array($items) || $items instanceof self ? $items : [$items]; + $itemsCnt = \count($items); if (0 === $itemsCnt) { return; } - $oldSize = count($this); + $oldSize = \count($this); $this->changed = true; + $this->blockEndCache = []; $this->setSize($oldSize + $itemsCnt); + // since we only move already existing items around, we directly call into SplFixedArray::offset* methods. + // that way we get around additional overhead this class adds with overridden offset* methods. for ($i = $oldSize + $itemsCnt - 1; $i >= $index; --$i) { - $this[$i] = $this->offsetExists($i - $itemsCnt) ? $this[$i - $itemsCnt] : new Token(''); + $oldItem = parent::offsetExists($i - $itemsCnt) ? parent::offsetGet($i - $itemsCnt) : new Token(''); + parent::offsetSet($i, $oldItem); } for ($i = 0; $i < $itemsCnt; ++$i) { @@ -841,7 +846,8 @@ public function insertAt($index, $items) throw new \InvalidArgumentException('Must not add empty token to collection.'); } - $this[$i + $index] = $items[$i]; + $this->registerFoundToken($items[$i]); + parent::offsetSet($i + $index, $items[$i]); } } @@ -898,7 +904,7 @@ public function overrideRange($indexStart, $indexEnd, $items) } $indexToChange = $indexEnd - $indexStart + 1; - $itemsCount = count($items); + $itemsCount = \count($items); // If we want to add more items than passed range contains we need to // add placeholders for overhead items. @@ -929,7 +935,7 @@ public function overrideRange($indexStart, $indexEnd, $items) */ public function removeLeadingWhitespace($index, $whitespaces = null) { - $this->removeWhitespaceSafely($index, 1, $whitespaces); + $this->removeWhitespaceSafely($index, -1, $whitespaces); } /** @@ -938,7 +944,7 @@ public function removeLeadingWhitespace($index, $whitespaces = null) */ public function removeTrailingWhitespace($index, $whitespaces = null) { - $this->removeWhitespaceSafely($index, -1, $whitespaces); + $this->removeWhitespaceSafely($index, 1, $whitespaces); } /** @@ -957,11 +963,11 @@ public function setCode($code) // clear memory $this->setSize(0); - $tokens = defined('TOKEN_PARSE') + $tokens = \defined('TOKEN_PARSE') ? token_get_all($code, TOKEN_PARSE) : token_get_all($code); - $this->setSize(count($tokens)); + $this->setSize(\count($tokens)); foreach ($tokens as $index => $token) { $this[$index] = new Token($token); @@ -988,7 +994,7 @@ public function toJson() $options = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']); } - $output = new \SplFixedArray(count($this)); + $output = new \SplFixedArray(\count($this)); foreach ($this as $index => $token) { $output[$index] = $token->toArray(); @@ -1115,7 +1121,7 @@ public function isPartialCodeMultiline($start, $end) */ public function clearTokenAndMergeSurroundingWhitespace($index) { - $count = count($this); + $count = \count($this); $this->clearAt($index); if ($index === $count - 1) { @@ -1139,15 +1145,16 @@ public function clearTokenAndMergeSurroundingWhitespace($index) $this->clearAt($nextIndex); } - private function removeWhitespaceSafely($index, $offset, $whitespaces = null) + private function removeWhitespaceSafely($index, $direction, $whitespaces = null) { - if (isset($this[$index - $offset]) && $this[$index - $offset]->isWhitespace()) { + $whitespaceIndex = $this->getNonEmptySibling($index, $direction); + if (isset($this[$whitespaceIndex]) && $this[$whitespaceIndex]->isWhitespace()) { $newContent = ''; - $tokenToCheck = $this[$index - $offset]; + $tokenToCheck = $this[$whitespaceIndex]; // if the token candidate to remove is preceded by single line comment we do not consider the new line after this comment as part of T_WHITESPACE - if (isset($this[$index - $offset - 1]) && $this[$index - $offset - 1]->isComment() && '/*' !== substr($this[$index - $offset - 1]->getContent(), 0, 2)) { - list($emptyString, $newContent, $whitespacesToCheck) = Preg::split('/^(\R)/', $this[$index - $offset]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); + if (isset($this[$whitespaceIndex - 1]) && $this[$whitespaceIndex - 1]->isComment() && '/*' !== substr($this[$whitespaceIndex - 1]->getContent(), 0, 2)) { + list($emptyString, $newContent, $whitespacesToCheck) = Preg::split('/^(\R)/', $this[$whitespaceIndex]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); if ('' === $whitespacesToCheck) { return; } @@ -1158,9 +1165,10 @@ private function removeWhitespaceSafely($index, $offset, $whitespaces = null) return; } - $this->clearAt($index - $offset); - if ('' !== $newContent) { - $this->insertAt($index - $offset, new Token([T_WHITESPACE, $newContent])); + if ('' === $newContent) { + $this->clearAt($whitespaceIndex); + } else { + $this[$whitespaceIndex] = new Token([T_WHITESPACE, $newContent]); } } } @@ -1305,7 +1313,11 @@ private function changeCodeHash($codeHash) */ private function registerFoundToken($token) { - $tokenKind = $this->extractTokenKind($token); + // inlined extractTokenKind() call on the hot path + $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; @@ -1321,7 +1333,11 @@ private function registerFoundToken($token) */ private function unregisterFoundToken($token) { - $tokenKind = $this->extractTokenKind($token); + // inlined extractTokenKind() call on the hot path + $tokenKind = $token instanceof Token + ? ($token->isArray() ? $token->getId() : $token->getContent()) + : (\is_array($token) ? $token[0] : $token) + ; if (!isset($this->foundTokenKinds[$tokenKind])) { return; @@ -1339,7 +1355,7 @@ 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) ; } } diff --git a/src/Tokenizer/TokensAnalyzer.php b/src/Tokenizer/TokensAnalyzer.php index 122ea5387bc..bcd4aec8e76 100644 --- a/src/Tokenizer/TokensAnalyzer.php +++ b/src/Tokenizer/TokensAnalyzer.php @@ -47,7 +47,7 @@ public function getClassyElements() $this->tokens->rewind(); $elements = []; - for ($index = 1, $count = count($this->tokens) - 2; $index < $count; ++$index) { + for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) { if ($this->tokens[$index]->isClassy()) { list($index, $newElements) = $this->findClassyElements($index); $elements += $newElements; @@ -64,7 +64,7 @@ public function getClassyElements() * * @param bool $perNamespace Return namespace uses per namespace * - * @return array|array[] + * @return int[]|int[][] */ public function getImportUseIndexes($perNamespace = false) { @@ -202,19 +202,19 @@ public function getMethodAttributes($index) $i = $tokenIndex; $token = $tokens[$tokenIndex]; - if ($token->isGivenKind([T_STATIC])) { + if ($token->isGivenKind(T_STATIC)) { $attributes['static'] = true; continue; } - if ($token->isGivenKind([T_FINAL])) { + if ($token->isGivenKind(T_FINAL)) { $attributes['final'] = true; continue; } - if ($token->isGivenKind([T_ABSTRACT])) { + if ($token->isGivenKind(T_ABSTRACT)) { $attributes['abstract'] = true; continue; @@ -222,19 +222,19 @@ public function getMethodAttributes($index) // visibility - if ($token->isGivenKind([T_PRIVATE])) { + if ($token->isGivenKind(T_PRIVATE)) { $attributes['visibility'] = T_PRIVATE; continue; } - if ($token->isGivenKind([T_PROTECTED])) { + if ($token->isGivenKind(T_PROTECTED)) { $attributes['visibility'] = T_PROTECTED; continue; } - if ($token->isGivenKind([T_PUBLIC])) { + if ($token->isGivenKind(T_PUBLIC)) { $attributes['visibility'] = T_PUBLIC; continue; @@ -355,6 +355,15 @@ public function isConstantInvocation($index) } } + // check for array in double quoted string: `"..$foo[bar].."` + if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) { + $checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]; + + if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) { + return false; + } + } + // check for goto label if ($this->tokens[$nextIndex]->equals(':') && $this->tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]])) { return false; @@ -536,11 +545,11 @@ public function isBinaryOperator($index) CT::T_TYPE_ALTERNATION => true, // | ]; - if (defined('T_SPACESHIP')) { + if (\defined('T_SPACESHIP')) { $arrayOperators[T_SPACESHIP] = true; // <=> } - if (defined('T_COALESCE')) { + if (\defined('T_COALESCE')) { $arrayOperators[T_COALESCE] = true; // ?? } } @@ -609,7 +618,7 @@ private function findClassyElements($index) $classIndex = $index; ++$index; // skip the classy index itself - for ($count = count($this->tokens); $index < $count; ++$index) { + for ($count = \count($this->tokens); $index < $count; ++$index) { $token = $this->tokens[$index]; if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) { diff --git a/src/Tokenizer/Transformer/SquareBraceTransformer.php b/src/Tokenizer/Transformer/SquareBraceTransformer.php index ea28aa8efa8..58dae46a97d 100644 --- a/src/Tokenizer/Transformer/SquareBraceTransformer.php +++ b/src/Tokenizer/Transformer/SquareBraceTransformer.php @@ -22,7 +22,7 @@ * * Performed transformations: * - in `[1, 2, 3]` into CT::T_ARRAY_SQUARE_BRACE_OPEN and CT::T_ARRAY_SQUARE_BRACE_CLOSE, - * - in `[$a, $b, $c] = array(1, 2, 3)` into CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN and CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE. + * - in `[$a, &$b, [$c]] = array(1, 2, array(3))` into CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN and CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE. * * @author Dariusz Rumiński * @author SpacePossum @@ -93,6 +93,19 @@ private function transformIntoDestructuringSquareBrace(Tokens $tokens, $index) $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); $tokens[$endIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); + + $previousMeaningfulIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + + while ($index < $endIndex) { + if ($tokens[$index]->equals('[') && $tokens[$previousMeaningfulIndex]->equalsAny([[CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], ','])) { + $tokens[$tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index)] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']); + $tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']); + } + + $previousMeaningfulIndex = $index; + $index = $tokens->getNextMeaningfulToken($index); + } } /** @@ -105,6 +118,10 @@ private function transformIntoDestructuringSquareBrace(Tokens $tokens, $index) */ private function isShortArray(Tokens $tokens, $index) { + if (!$tokens[$index]->equals('[')) { + return false; + } + static $disallowedPrevTokens = [ ')', ']', @@ -120,12 +137,6 @@ private function isShortArray(Tokens $tokens, $index) [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], ]; - $token = $tokens[$index]; - - if (!$token->equals('[')) { - return false; - } - $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; if ($prevToken->equalsAny($disallowedPrevTokens)) { return false; diff --git a/src/Tokenizer/Transformer/TypeAlternationTransformer.php b/src/Tokenizer/Transformer/TypeAlternationTransformer.php index 1c0e956ab3c..fefebc5fc0f 100644 --- a/src/Tokenizer/Transformer/TypeAlternationTransformer.php +++ b/src/Tokenizer/Transformer/TypeAlternationTransformer.php @@ -58,13 +58,29 @@ public function process(Tokens $tokens, Token $token, $index) return; } - $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); - $prevToken = $tokens[$prevIndex]; + do { + $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex); + if (null === $prevIndex) { + break; + } - if (!$prevToken->equalsAny(['(', [CT::T_TYPE_ALTERNATION]])) { - return; - } + $prevToken = $tokens[$prevIndex]; + + if ($prevToken->isGivenKind([T_NS_SEPARATOR, T_STRING])) { + continue; + } + + if ( + $prevToken->isGivenKind(CT::T_TYPE_ALTERNATION) + || ( + $prevToken->equals('(') + && $tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_CATCH) + ) + ) { + $tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']); + } - $tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']); + break; + } while (true); } } diff --git a/src/Tokenizer/Transformer/UseTransformer.php b/src/Tokenizer/Transformer/UseTransformer.php index 2c9f3e03349..b063bfd476d 100644 --- a/src/Tokenizer/Transformer/UseTransformer.php +++ b/src/Tokenizer/Transformer/UseTransformer.php @@ -36,6 +36,15 @@ public function getCustomTokens() return [CT::T_USE_TRAIT, CT::T_USE_LAMBDA]; } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // Should run after CurlyBraceTransformer + return -5; + } + /** * {@inheritdoc} */ @@ -51,21 +60,20 @@ public function process(Tokens $tokens, Token $token, $index) { if ($token->isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) { $tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]); - } - if (!$token->isClassy()) { return; } - $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); - $prevToken = null === $prevTokenIndex ? null : $tokens[$prevTokenIndex]; + // Only search inside class/trait body for `T_USE` for traits. + // Cannot import traits inside interfaces or anywhere else - if ($prevToken->isGivenKind(T_DOUBLE_COLON)) { + if (!$token->isGivenKind([T_CLASS, T_TRAIT])) { return; } - // Skip whole class braces content. - // That way we can skip whole tokens in class declaration, therefore skip `T_USE` for traits. + if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) { + return; + } $index = $tokens->getNextTokenOfKind($index, ['{']); $innerLimit = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); diff --git a/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php b/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php index 45b01b80299..9878ff333ad 100644 --- a/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php +++ b/src/Tokenizer/Transformer/WhitespacyCommentTransformer.php @@ -58,7 +58,7 @@ public function process(Tokens $tokens, Token $token, $index) return; } - $whitespaces = substr($content, strlen($trimmedContent)); + $whitespaces = substr($content, \strlen($trimmedContent)); $tokens[$index] = new Token([$token->getId(), $trimmedContent]); diff --git a/src/Tokenizer/Transformers.php b/src/Tokenizer/Transformers.php index 468f270ba05..2fda2d76e7c 100644 --- a/src/Tokenizer/Transformers.php +++ b/src/Tokenizer/Transformers.php @@ -64,8 +64,8 @@ public static function create() */ public function transform(Tokens $tokens) { - foreach ($tokens as $index => $token) { - foreach ($this->items as $transformer) { + foreach ($this->items as $transformer) { + foreach ($tokens as $index => $token) { $transformer->process($tokens, $token, $index); } } @@ -91,10 +91,21 @@ private function registerBuiltInTransformers() $registered = true; + foreach ($this->findBuiltInTransformers() as $transformer) { + $this->registerTransformer($transformer); + } + } + + /** + * @return \Generator|TransformerInterface[] + */ + private function findBuiltInTransformers() + { foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) { $relativeNamespace = $file->getRelativePath(); $class = __NAMESPACE__.'\\Transformer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php'); - $this->registerTransformer(new $class()); + + yield new $class(); } } } diff --git a/src/ToolInfo.php b/src/ToolInfo.php index 7a67c587ca5..5e4ff462bc4 100644 --- a/src/ToolInfo.php +++ b/src/ToolInfo.php @@ -47,7 +47,7 @@ public function getComposerInstallationDetails() $composerInstalled = json_decode(file_get_contents($this->getComposerInstalledFile()), true); foreach ($composerInstalled as $package) { - if (in_array($package['name'], [self::COMPOSER_PACKAGE_NAME, self::COMPOSER_LEGACY_PACKAGE_NAME], true)) { + if (\in_array($package['name'], [self::COMPOSER_PACKAGE_NAME, self::COMPOSER_LEGACY_PACKAGE_NAME], true)) { $this->composerInstallationDetails = $package; break; diff --git a/src/Utils.php b/src/Utils.php index d8e192e92e3..d846bdb4165 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -36,8 +36,8 @@ public static function calculateBitmask(array $options) $bitmask = 0; foreach ($options as $optionName) { - if (defined($optionName)) { - $bitmask |= constant($optionName); + if (\defined($optionName)) { + $bitmask |= \constant($optionName); } } @@ -82,25 +82,6 @@ public static function cmpInt($a, $b) return $a < $b ? -1 : 1; } - /** - * Split a multi-line string up into an array of strings. - * - * We're retaining a newline character at the end of non-blank lines, and - * discarding other lines, so this function is unsuitable for anyone for - * wishing to retain the exact number of line endings. If a single-line - * string is passed, we'll just return an array with a element. - * - * @param string $content - * - * @return string[] - */ - public static function splitLines($content) - { - Preg::matchAll("/[^\n\r]+[\r\n]*/", $content, $matches); - - return $matches[0]; - } - /** * Calculate the trailing whitespace. * diff --git a/src/WhitespacesFixerConfig.php b/src/WhitespacesFixerConfig.php index 68912bf5a4c..93ad5f433b6 100644 --- a/src/WhitespacesFixerConfig.php +++ b/src/WhitespacesFixerConfig.php @@ -26,11 +26,11 @@ final class WhitespacesFixerConfig */ public function __construct($indent = ' ', $lineEnding = "\n") { - if (!in_array($indent, [' ', ' ', "\t"], true)) { + if (!\in_array($indent, [' ', ' ', "\t"], true)) { throw new \InvalidArgumentException('Invalid "indent" param, expected tab or two or four spaces.'); } - if (!in_array($lineEnding, ["\n", "\r\n"], true)) { + if (!\in_array($lineEnding, ["\n", "\r\n"], true)) { throw new \InvalidArgumentException('Invalid "lineEnding" param, expected "\n" or "\r\n".'); } diff --git a/src/WordMatcher.php b/src/WordMatcher.php index 02b98450c9b..d0f529d6b08 100644 --- a/src/WordMatcher.php +++ b/src/WordMatcher.php @@ -41,7 +41,7 @@ public function __construct(array $candidates) public function match($needle) { $word = null; - $distance = ceil(strlen($needle) * 0.35); + $distance = ceil(\strlen($needle) * 0.35); foreach ($this->candidates as $candidate) { $candidateDistance = levenshtein($needle, $candidate); diff --git a/tests/AbstractFunctionReferenceFixerTest.php b/tests/AbstractFunctionReferenceFixerTest.php new file mode 100644 index 00000000000..655ef817a20 --- /dev/null +++ b/tests/AbstractFunctionReferenceFixerTest.php @@ -0,0 +1,132 @@ + + * 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\Tests; + +use PhpCsFixer\Tests\Fixtures\FunctionReferenceTestFixer; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @internal + * + * @covers \PhpCsFixer\AbstractFunctionReferenceFixer + */ +final class AbstractFunctionReferenceFixerTest extends TestCase +{ + private $fixer; + + protected function setUp() + { + $this->fixer = new FunctionReferenceTestFixer(); + + parent::setUp(); + } + + protected function tearDown() + { + $this->fixer = null; + + parent::tearDown(); + } + + /** + * @param null|int[] $expected + * @param string $source + * @param string $functionNameToSearch + * @param int $start + * @param null|int $end + * + * @dataProvider provideAbstractFunctionReferenceFixerCases + */ + public function testAbstractFunctionReferenceFixer( + $expected, + $source, + $functionNameToSearch, + $start = 0, + $end = null + ) { + $tokens = Tokens::fromCode($source); + + $this->assertSame( + $expected, + $this->fixer->findTest( + $functionNameToSearch, + $tokens, + $start, + $end + ) + ); + + $this->assertFalse($tokens->isChanged()); + } + + public function provideAbstractFunctionReferenceFixerCases() + { + return [ + 'simple case I' => [ + [1, 2, 3], + ' [ + [2, 3, 4], + ' [ + null, + ' [ + [2, 3, 4], + ' [ + null, + ' [ + null, + ' [ + null, + ' [ + null, + ' [ + null, + 'assertSame($command->getName(), constant(get_class($command).'::COMMAND_NAME')); + $this->assertSame($command->getName(), \constant(\get_class($command).'::COMMAND_NAME')); } public function provideCommandHasNameConstCases() @@ -45,9 +45,9 @@ public function provideCommandHasNameConstCases() $names = array_filter(array_keys($commands), static function ($name) use ($commands) { return // is not an alias - !in_array($name, $commands[$name]->getAliases(), true) + !\in_array($name, $commands[$name]->getAliases(), true) // and is our command - && 0 === strpos(get_class($commands[$name]), 'PhpCsFixer\\') + && 0 === strpos(\get_class($commands[$name]), 'PhpCsFixer\\') ; }); diff --git a/tests/AutoReview/ComposerTest.php b/tests/AutoReview/ComposerTest.php index aaaf09b7a47..6364e0ca128 100644 --- a/tests/AutoReview/ComposerTest.php +++ b/tests/AutoReview/ComposerTest.php @@ -10,7 +10,7 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\tests\AutoReview; +namespace PhpCsFixer\Tests\AutoReview; use PhpCsFixer\Console\Application; use PhpCsFixer\Tests\TestCase; diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php index ba8a4c9556b..10ed634df99 100644 --- a/tests/AutoReview/FixerFactoryTest.php +++ b/tests/AutoReview/FixerFactoryTest.php @@ -35,7 +35,7 @@ public function testFixersPriorityEdgeFixers() $this->assertSame('encoding', $fixers[0]->getName(), 'Expected "encoding" fixer to have the highest priority.'); $this->assertSame('full_opening_tag', $fixers[1]->getName(), 'Expected "full_opening_tag" fixer has second highest priority.'); - $this->assertSame('single_blank_line_at_eof', $fixers[count($fixers) - 1]->getName(), 'Expected "single_blank_line_at_eof" to have the lowest priority.'); + $this->assertSame('single_blank_line_at_eof', $fixers[\count($fixers) - 1]->getName(), 'Expected "single_blank_line_at_eof" to have the lowest priority.'); } /** @@ -44,7 +44,7 @@ public function testFixersPriorityEdgeFixers() */ public function testFixersPriority(FixerInterface $first, FixerInterface $second) { - $this->assertLessThan($first->getPriority(), $second->getPriority(), sprintf('"%s" should have less priority than "%s"', get_class($second), get_class($first))); + $this->assertLessThan($first->getPriority(), $second->getPriority(), sprintf('"%s" should have less priority than "%s"', \get_class($second), \get_class($first))); } public function provideFixersPriorityCases() @@ -64,8 +64,10 @@ public function provideFixersPriorityCases() [$fixers['array_syntax'], $fixers['binary_operator_spaces']], [$fixers['array_syntax'], $fixers['ternary_operator_spaces']], [$fixers['backtick_to_shell_exec'], $fixers['escape_implicit_backslashes']], + [$fixers['backtick_to_shell_exec'], $fixers['explicit_string_variable']], [$fixers['blank_line_after_opening_tag'], $fixers['no_blank_lines_before_namespace']], [$fixers['braces'], $fixers['array_indentation']], + [$fixers['braces'], $fixers['method_chaining_indentation']], [$fixers['class_attributes_separation'], $fixers['braces']], [$fixers['class_attributes_separation'], $fixers['indentation_type']], [$fixers['class_keyword_remove'], $fixers['no_unused_imports']], @@ -77,12 +79,16 @@ public function provideFixersPriorityCases() [$fixers['combine_consecutive_unsets'], $fixers['no_trailing_whitespace']], [$fixers['combine_consecutive_unsets'], $fixers['no_whitespace_in_blank_line']], [$fixers['combine_consecutive_unsets'], $fixers['space_after_semicolon']], + [$fixers['combine_nested_dirname'], $fixers['method_argument_space']], + [$fixers['combine_nested_dirname'], $fixers['no_spaces_inside_parenthesis']], [$fixers['declare_strict_types'], $fixers['blank_line_after_opening_tag']], [$fixers['declare_strict_types'], $fixers['declare_equal_normalize']], + [$fixers['dir_constant'], $fixers['combine_nested_dirname']], [$fixers['doctrine_annotation_array_assignment'], $fixers['doctrine_annotation_spaces']], [$fixers['elseif'], $fixers['braces']], [$fixers['escape_implicit_backslashes'], $fixers['heredoc_to_nowdoc']], [$fixers['escape_implicit_backslashes'], $fixers['single_quote']], + [$fixers['fully_qualified_strict_types'], $fixers['no_superfluous_phpdoc_tags']], [$fixers['function_to_constant'], $fixers['native_function_casing']], [$fixers['function_to_constant'], $fixers['no_extra_blank_lines']], [$fixers['function_to_constant'], $fixers['no_singleline_whitespace_before_semicolons']], @@ -92,10 +98,14 @@ public function provideFixersPriorityCases() [$fixers['general_phpdoc_annotation_remove'], $fixers['phpdoc_trim']], [$fixers['general_phpdoc_annotation_remove'], $fixers['no_empty_phpdoc']], [$fixers['indentation_type'], $fixers['phpdoc_indent']], + [$fixers['implode_call'], $fixers['method_argument_space']], [$fixers['is_null'], $fixers['yoda_style']], [$fixers['list_syntax'], $fixers['binary_operator_spaces']], [$fixers['list_syntax'], $fixers['ternary_operator_spaces']], + [$fixers['method_chaining_indentation'], $fixers['array_indentation']], + [$fixers['no_alias_functions'], $fixers['implode_call']], [$fixers['no_alias_functions'], $fixers['php_unit_dedicate_assert']], + [$fixers['no_blank_lines_after_phpdoc'], $fixers['header_comment']], [$fixers['no_blank_lines_after_phpdoc'], $fixers['single_blank_line_before_namespace']], [$fixers['no_empty_comment'], $fixers['no_extra_blank_lines']], [$fixers['no_empty_comment'], $fixers['no_trailing_whitespace']], @@ -142,7 +152,6 @@ public function provideFixersPriorityCases() [$fixers['no_useless_return'], $fixers['blank_line_before_statement']], [$fixers['no_useless_return'], $fixers['no_extra_blank_lines']], [$fixers['no_useless_return'], $fixers['no_whitespace_in_blank_line']], - [$fixers['no_whitespace_in_blank_line'], $fixers['no_blank_lines_after_phpdoc']], [$fixers['ordered_class_elements'], $fixers['class_attributes_separation']], [$fixers['ordered_class_elements'], $fixers['no_blank_lines_after_class_opening']], [$fixers['ordered_class_elements'], $fixers['space_after_semicolon']], @@ -203,6 +212,7 @@ public function provideFixersPriorityCases() [$fixers['void_return'], $fixers['phpdoc_no_empty_return']], [$fixers['void_return'], $fixers['return_type_declaration']], [$fixers['php_unit_test_annotation'], $fixers['no_empty_phpdoc']], + [$fixers['php_unit_test_annotation'], $fixers['php_unit_method_casing']], [$fixers['php_unit_test_annotation'], $fixers['phpdoc_trim']], [$fixers['no_alternative_syntax'], $fixers['braces']], [$fixers['no_alternative_syntax'], $fixers['elseif']], @@ -242,7 +252,7 @@ static function ($name) { ); foreach ($docFixerNames as $docFixerName) { - if (!in_array($docFixerName, ['comment_to_phpdoc', 'phpdoc_to_comment', 'phpdoc_indent', 'phpdoc_types', 'phpdoc_scalar'], true)) { + if (!\in_array($docFixerName, ['comment_to_phpdoc', 'phpdoc_to_comment', 'phpdoc_indent', 'phpdoc_types', 'phpdoc_scalar'], true)) { $cases[] = [$fixers['comment_to_phpdoc'], $fixers[$docFixerName]]; $cases[] = [$fixers['phpdoc_indent'], $fixers[$docFixerName]]; $cases[] = [$fixers['phpdoc_scalar'], $fixers[$docFixerName]]; @@ -266,12 +276,10 @@ public function testFixersPriorityPairsHaveIntegrationTest(FixerInterface $first // This structure contains older cases that are not yet covered by tests. // It may only shrink, never add anything to it. $casesWithoutTests = [ - 'class_attributes_separation,braces.test', 'class_attributes_separation,indentation_type.test', 'indentation_type,phpdoc_indent.test', 'method_separation,braces.test', 'method_separation,indentation_type.test', - 'no_empty_statement,multiline_whitespace_before_semicolons.test', 'no_empty_statement,no_multiline_whitespace_before_semicolons.test', 'phpdoc_no_access,phpdoc_order.test', 'phpdoc_no_access,phpdoc_separation.test', @@ -284,7 +292,7 @@ public function testFixersPriorityPairsHaveIntegrationTest(FixerInterface $first $integrationTestExists = $this->doesIntegrationTestExist($first, $second); - if (in_array($this->generateIntegrationTestName($first, $second), $casesWithoutTests, true)) { + if (\in_array($this->generateIntegrationTestName($first, $second), $casesWithoutTests, true)) { $this->assertFalse($integrationTestExists, sprintf('Case "%s" already has an integration test, so it should be removed from "$casesWithoutTests".', $this->generateIntegrationTestName($first, $second))); $this->markTestIncomplete(sprintf('Case "%s" has no integration test yet, please help and add it.', $this->generateIntegrationTestName($first, $second))); } @@ -298,7 +306,7 @@ public function provideFixersPriorityPairsHaveIntegrationTestCases() $this->provideFixersPriorityCases(), // ignore speed-up only priorities set up function (array $case) { - return !in_array( + return !\in_array( $this->generateIntegrationTestName($case[0], $case[1]), [ 'function_to_constant,native_function_casing.test', @@ -351,13 +359,13 @@ public function testPriorityIntegrationTestFilesAreListedPriorityCases($fileName ksort($priorityCases); } - if (in_array($fileName, [ + if (\in_array($fileName, [ 'braces,indentation_type,no_break_comment.test', ], true)) { $this->markTestIncomplete(sprintf('Case "%s" has unexpected name, please help fixing it.', $fileName)); } - if (in_array($fileName, [ + if (\in_array($fileName, [ 'combine_consecutive_issets,no_singleline_whitespace_before_semicolons.test', ], true)) { $this->markTestIncomplete(sprintf('Case "%s" is not fully handled, please help fixing it.', $fileName)); diff --git a/tests/AutoReview/FixerTest.php b/tests/AutoReview/FixerTest.php index 1739e89c2d9..4f4a1e55b8f 100644 --- a/tests/AutoReview/FixerTest.php +++ b/tests/AutoReview/FixerTest.php @@ -111,7 +111,7 @@ public function testFixerDefinitions(FixerInterface $fixer) $duplicatedCodeSample = array_search( $sample, - array_slice($samples, 0, $sampleCounter), + \array_slice($samples, 0, $sampleCounter), false ); @@ -133,7 +133,7 @@ public function testFixerDefinitions(FixerInterface $fixer) foreach ($options as $option) { // @TODO 2.12 adjust fixers to use new casing and deprecate old one - if (in_array($fixerName, [ + if (\in_array($fixerName, [ 'final_internal_class', 'ordered_class_elements', ], true)) { @@ -183,7 +183,7 @@ public function testDeprecatedFixersHaveCorrectSummary(FixerInterface $fixer) if ($fixer instanceof DeprecatedFixerInterface) { $this->assertContains('@deprecated', $comment); - } elseif (is_string($comment)) { + } elseif (\is_string($comment)) { $this->assertNotContains('@deprecated', $comment); } } diff --git a/tests/AutoReview/ProjectCodeTest.php b/tests/AutoReview/ProjectCodeTest.php index 2bd8d853ca5..57d6d7be367 100644 --- a/tests/AutoReview/ProjectCodeTest.php +++ b/tests/AutoReview/ProjectCodeTest.php @@ -46,7 +46,6 @@ final class ProjectCodeTest extends TestCase \PhpCsFixer\Doctrine\Annotation\Tokens::class, \PhpCsFixer\Runner\FileCachingLintingIterator::class, \PhpCsFixer\Runner\FileLintingIterator::class, - \PhpCsFixer\Tokenizer\Transformers::class, ]; public function testThatClassesWithoutTestsVarIsProper() @@ -68,7 +67,7 @@ public function testThatSrcClassHaveTestClass($className) { $testClassName = str_replace('PhpCsFixer', 'PhpCsFixer\\Tests', $className).'Test'; - if (in_array($className, self::$classesWithoutTests, true)) { + if (\in_array($className, self::$classesWithoutTests, true)) { $this->assertFalse(class_exists($testClassName), sprintf('Class "%s" already has tests, so it should be removed from "%s::$classesWithoutTests".', $className, __CLASS__)); $this->markTestIncomplete(sprintf('Class "%s" has no tests yet, please help and add it.', $className)); } @@ -93,7 +92,7 @@ function (\ReflectionClass $interface) { $rc->getInterfaces() ); - if (count($allowedMethods)) { + if (\count($allowedMethods)) { $allowedMethods = array_unique(array_merge(...array_values($allowedMethods))); } @@ -313,9 +312,9 @@ static function ($item) { if ( $rc->isInterface() - || ($doc && count($doc->getAnnotationsOfType('internal'))) - || 0 === count($rc->getInterfaces()) - || in_array($className, [ + || ($doc && \count($doc->getAnnotationsOfType('internal'))) + || 0 === \count($rc->getInterfaces()) + || \in_array($className, [ \PhpCsFixer\Finder::class, \PhpCsFixer\Test\AbstractFixerTestCase::class, \PhpCsFixer\Test\AbstractIntegrationTestCase::class, diff --git a/tests/AutoReview/ProjectFixerConfigurationTest.php b/tests/AutoReview/ProjectFixerConfigurationTest.php index 6e43944de90..0eac98fd69b 100644 --- a/tests/AutoReview/ProjectFixerConfigurationTest.php +++ b/tests/AutoReview/ProjectFixerConfigurationTest.php @@ -32,7 +32,7 @@ public function testCreate() { $config = $this->loadConfig(); - $this->assertInstanceOf('PhpCsFixer\Config', $config); + $this->assertInstanceOf(\PhpCsFixer\Config::class, $config); $this->assertEmpty($config->getCustomFixers()); $this->assertNotEmpty($config->getRules()); diff --git a/tests/AutoReview/TransformerTest.php b/tests/AutoReview/TransformerTest.php index 7add0b46baa..aa7197d8731 100644 --- a/tests/AutoReview/TransformerTest.php +++ b/tests/AutoReview/TransformerTest.php @@ -10,7 +10,7 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\tests\AutoReview; +namespace PhpCsFixer\Tests\AutoReview; use PhpCsFixer\Tests\TestCase; use PhpCsFixer\Tokenizer\TransformerInterface; @@ -18,6 +18,7 @@ /** * @author SpacePossum + * @author Dave van der Brugge * * @internal * @@ -52,15 +53,44 @@ public function provideTransformerCases() if (null === $transformersArray) { $transformers = Transformers::create(); $reflection = new \ReflectionObject($transformers); - $transformersItems = $reflection->getProperty('items'); - $transformersItems->setAccessible(true); - $transformersItems = $transformersItems->getValue($transformers); + $builtInTransformers = $reflection->getMethod('findBuiltInTransformers'); + $builtInTransformers->setAccessible(true); $transformersArray = []; - foreach ($transformersItems as $transformer) { + foreach ($builtInTransformers->invoke($transformers) as $transformer) { $transformersArray[] = [$transformer]; } } return $transformersArray; } + + /** + * @dataProvider provideTransformerPriorityCases + */ + public function testTransformerPriority(TransformerInterface $first, TransformerInterface $second) + { + $this->assertLessThan( + $first->getPriority(), + $second->getPriority(), + sprintf('"%s" should have less priority than "%s"', \get_class($second), \get_class($first)) + ); + } + + public function provideTransformerPriorityCases() + { + $transformers = []; + + foreach ($this->provideTransformerCases() as list($transformer)) { + $transformers[$transformer->getName()] = $transformer; + } + + return [ + [$transformers['curly_brace'], $transformers['brace_class_instantiation']], + [$transformers['curly_brace'], $transformers['use']], + [$transformers['return_ref'], $transformers['type_colon']], + [$transformers['square_brace'], $transformers['brace_class_instantiation']], + [$transformers['type_colon'], $transformers['nullable_type']], + [$transformers['use'], $transformers['type_colon']], + ]; + } } diff --git a/tests/Cache/CacheTest.php b/tests/Cache/CacheTest.php index 43c49752755..524c75fd292 100644 --- a/tests/Cache/CacheTest.php +++ b/tests/Cache/CacheTest.php @@ -198,7 +198,7 @@ public function testToJsonThrowsExceptionOnInvalid() { $invalidUtf8Sequence = "\xB1\x31"; - $signature = $this->prophesize('PhpCsFixer\Cache\SignatureInterface'); + $signature = $this->prophesize(\PhpCsFixer\Cache\SignatureInterface::class); $signature->getPhpVersion()->willReturn('7.1.0'); $signature->getFixerVersion()->willReturn('2.2.0'); $signature->getRules()->willReturn([ @@ -208,7 +208,7 @@ public function testToJsonThrowsExceptionOnInvalid() $cache = new Cache($signature->reveal()); $this->expectException( - 'UnexpectedValueException' + \UnexpectedValueException::class ); $this->expectExceptionMessage( 'Can not encode cache signature to JSON, error: "Malformed UTF-8 characters, possibly incorrectly encoded". If you have non-UTF8 chars in your signature, like in license for `header_comment`, consider enabling `ext-mbstring` or install `symfony/polyfill-mbstring`.' diff --git a/tests/Console/Command/DescribeCommandTest.php b/tests/Console/Command/DescribeCommandTest.php index eada76b1cc7..b86401b8dc3 100644 --- a/tests/Console/Command/DescribeCommandTest.php +++ b/tests/Console/Command/DescribeCommandTest.php @@ -206,7 +206,7 @@ public function testFixerClassNameIsExposedWhenVerbose() ] ); - $this->assertContains(get_class($mock), $commandTester->getDisplay(true)); + $this->assertContains(\get_class($mock), $commandTester->getDisplay(true)); } /** diff --git a/tests/Console/ConfigurationResolverTest.php b/tests/Console/ConfigurationResolverTest.php index 807fc9c87fc..f1927aa25b5 100644 --- a/tests/Console/ConfigurationResolverTest.php +++ b/tests/Console/ConfigurationResolverTest.php @@ -301,7 +301,7 @@ public function testResolvePathRelativeB() $resolver = $this->createConfigurationResolver( ['path' => [basename(__DIR__)]], null, - dirname(__DIR__) + \dirname(__DIR__) ); $this->assertSame([__DIR__], $resolver->getPath()); @@ -312,7 +312,8 @@ public function testResolvePathWithFileThatIsExcludedDirectlyOverridePathMode() $config = new Config(); $config->getFinder() ->in(__DIR__) - ->notPath(basename(__FILE__)); + ->notPath(basename(__FILE__)) + ; $resolver = $this->createConfigurationResolver( ['path' => [__FILE__]], @@ -327,7 +328,8 @@ public function testResolvePathWithFileThatIsExcludedDirectlyIntersectionPathMod $config = new Config(); $config->getFinder() ->in(__DIR__) - ->notPath(basename(__FILE__)); + ->notPath(basename(__FILE__)) + ; $resolver = $this->createConfigurationResolver([ 'path' => [__FILE__], @@ -339,11 +341,12 @@ public function testResolvePathWithFileThatIsExcludedDirectlyIntersectionPathMod public function testResolvePathWithFileThatIsExcludedByDirOverridePathMode() { - $dir = dirname(__DIR__); + $dir = \dirname(__DIR__); $config = new Config(); $config->getFinder() ->in($dir) - ->exclude(basename(__DIR__)); + ->exclude(basename(__DIR__)) + ; $resolver = $this->createConfigurationResolver( ['path' => [__FILE__]], @@ -355,11 +358,12 @@ public function testResolvePathWithFileThatIsExcludedByDirOverridePathMode() public function testResolvePathWithFileThatIsExcludedByDirIntersectionPathMode() { - $dir = dirname(__DIR__); + $dir = \dirname(__DIR__); $config = new Config(); $config->getFinder() ->in($dir) - ->exclude(basename(__DIR__)); + ->exclude(basename(__DIR__)) + ; $resolver = $this->createConfigurationResolver([ 'path-mode' => 'intersection', @@ -375,7 +379,8 @@ public function testResolvePathWithFileThatIsNotExcluded() $config = new Config(); $config->getFinder() ->in($dir) - ->notPath('foo-'.basename(__FILE__)); + ->notPath('foo-'.basename(__FILE__)) + ; $resolver = $this->createConfigurationResolver( ['path' => [__FILE__]], @@ -396,7 +401,7 @@ public function testResolvePathWithFileThatIsNotExcluded() public function testResolveIntersectionOfPaths($expected, $configFinder, array $path, $pathMode, $configOption = null) { if ($expected instanceof \Exception) { - $this->expectException(get_class($expected)); + $this->expectException(\get_class($expected)); } if (null !== $configFinder) { diff --git a/tests/Console/Output/ErrorOutputTest.php b/tests/Console/Output/ErrorOutputTest.php index a3e79886c45..775a1a7cb55 100644 --- a/tests/Console/Output/ErrorOutputTest.php +++ b/tests/Console/Output/ErrorOutputTest.php @@ -65,7 +65,7 @@ public function testErrorOutput(Error $error, $verbosityLevel, $lineNumber, $exc %s (%d) '.' '.' ', - get_class($source), + \get_class($source), $source->getMessage(), $source->getCode() ); @@ -151,7 +151,7 @@ public function testLintingExceptionOutputsAppliedFixersAndDiff() */ private function createStreamOutput($verbosityLevel) { - $output = new StreamOutput(fopen('php://memory', 'wb', false)); + $output = new StreamOutput(fopen('php://memory', 'w', false)); $output->setDecorated(false); $output->setVerbosity($verbosityLevel); diff --git a/tests/Console/WarningsDetectorTest.php b/tests/Console/WarningsDetectorTest.php index 4a9d40d9312..1fef80cdcc2 100644 --- a/tests/Console/WarningsDetectorTest.php +++ b/tests/Console/WarningsDetectorTest.php @@ -26,7 +26,7 @@ final class WarningsDetectorTest extends TestCase { public function testDetectOldVendorNotInstalledByComposer() { - $toolInfo = $this->prophesize('PhpCsFixer\ToolInfoInterface'); + $toolInfo = $this->prophesize(\PhpCsFixer\ToolInfoInterface::class); $toolInfo->isInstalledByComposer()->willReturn(false); $warningsDetector = new WarningsDetector($toolInfo->reveal()); @@ -37,7 +37,7 @@ public function testDetectOldVendorNotInstalledByComposer() public function testDetectOldVendorNotLegacyPackage() { - $toolInfo = $this->prophesize('PhpCsFixer\ToolInfoInterface'); + $toolInfo = $this->prophesize(\PhpCsFixer\ToolInfoInterface::class); $toolInfo->isInstalledByComposer()->willReturn(false); $toolInfo->getComposerInstallationDetails()->willReturn([ 'name' => 'friendsofphp/php-cs-fixer', @@ -51,7 +51,7 @@ public function testDetectOldVendorNotLegacyPackage() public function testDetectOldVendorLegacyPackage() { - $toolInfo = $this->prophesize('PhpCsFixer\ToolInfoInterface'); + $toolInfo = $this->prophesize(\PhpCsFixer\ToolInfoInterface::class); $toolInfo->isInstalledByComposer()->willReturn(true); $toolInfo->getComposerInstallationDetails()->willReturn([ 'name' => 'fabpot/php-cs-fixer', diff --git a/tests/Differ/DiffConsoleFormatterTest.php b/tests/Differ/DiffConsoleFormatterTest.php index 8a6c34f11c2..a07eaa0dcc3 100644 --- a/tests/Differ/DiffConsoleFormatterTest.php +++ b/tests/Differ/DiffConsoleFormatterTest.php @@ -101,6 +101,27 @@ public function provideTestCases() ', '| %s', ], + [ + mb_convert_encoding("--- Original\n+ausgefüllt", 'ISO-8859-1'), + true, + '%s', + mb_convert_encoding("--- Original\n+ausgefüllt", 'ISO-8859-1'), + '%s', + ], + [ + mb_convert_encoding("--- Original\n+++ New\n@@ @@\n-ausgefüllt", 'ISO-8859-1'), + true, + '%s', + mb_convert_encoding("--- Original\n+++ New\n@@ @@\n-ausgefüllt", 'ISO-8859-1'), + '%s', + ], + [ + mb_convert_encoding("--- Original\n+++ New\n@@ @@\n-ausgefüllt", 'ISO-8859-1'), + false, + '%s', + mb_convert_encoding("--- Original\n+++ New\n@@ @@\n-ausgefüllt", 'ISO-8859-1'), + '%s', + ], ]; } } diff --git a/tests/DocBlock/AnnotationTest.php b/tests/DocBlock/AnnotationTest.php index 26343f1cc8b..9dfce595e13 100644 --- a/tests/DocBlock/AnnotationTest.php +++ b/tests/DocBlock/AnnotationTest.php @@ -342,6 +342,29 @@ public function provideTypesCases() ]; } + /** + * @param string[] $expected + * @param string $input + * + * @dataProvider provideNormalizedTypesCases + */ + public function testNormalizedTypes($expected, $input) + { + $line = new Line($input); + $tag = new Annotation([$line]); + + $this->assertSame($expected, $tag->getNormalizedTypes()); + } + + public function provideNormalizedTypesCases() + { + return [ + [['null', 'string'], '* @param StRiNg|NuLl $foo'], + [['void'], '* @return Void'], + [['bar', 'baz', 'foo', 'null', 'qux'], '* @return Foo|Bar|Baz|Qux|null'], + ]; + } + public function testGetTypesOnBadTag() { $this->expectException(\RuntimeException::class); diff --git a/tests/FileReaderTest.php b/tests/FileReaderTest.php index eabf2ac35ab..a0c9cc9e03b 100644 --- a/tests/FileReaderTest.php +++ b/tests/FileReaderTest.php @@ -38,7 +38,7 @@ public function testCreateSingleton() { $instance = FileReader::createSingleton(); - $this->assertInstanceOf('PhpCsFixer\FileReader', $instance); + $this->assertInstanceOf(\PhpCsFixer\FileReader::class, $instance); $this->assertSame($instance, FileReader::createSingleton()); } @@ -63,4 +63,17 @@ public function testReadStdinCaches() $this->assertSame('read('php://stdin')); $this->assertSame('read('php://stdin')); } + + public function testThrowsExceptionOnFail() + { + $fs = vfsStream::setup(); + $nonExistentFilePath = $fs->url().'/non-existent.php'; + + $reader = new FileReader(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Failed to read content from "'.$nonExistentFilePath.'". file_get_contents('.$nonExistentFilePath.'): failed to open stream: "org\bovigo\vfs\vfsStreamWrapper::stream_open" call failed'); + + $reader->read($nonExistentFilePath); + } } diff --git a/tests/Fixer/Alias/EregToPregFixerTest.php b/tests/Fixer/Alias/EregToPregFixerTest.php index 1e3d2f295e2..97e591892e2 100644 --- a/tests/Fixer/Alias/EregToPregFixerTest.php +++ b/tests/Fixer/Alias/EregToPregFixerTest.php @@ -46,6 +46,7 @@ public function provideFixCases() ['split("[A-Z]", $m);'], + ['getStaticAttribute(\PhpCsFixer\Fixer\Alias\NoAliasFunctionsFixer::class, $setStaticAttributeName); foreach ($aliases as $alias => $master) { diff --git a/tests/Fixer/Alias/PowToExponentiationFixerTest.php b/tests/Fixer/Alias/PowToExponentiationFixerTest.php index 920a344c6ac..2b14b33b34f 100644 --- a/tests/Fixer/Alias/PowToExponentiationFixerTest.php +++ b/tests/Fixer/Alias/PowToExponentiationFixerTest.php @@ -252,4 +252,30 @@ public function provideNotFixCases() ], ]; } + + /** + * @param string $expected + * @param string $input + * + * @requires PHP 7.3 + * @dataProvider provideFix73Cases + */ + public function testFix73($expected, $input) + { + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + 'fixer->configure($config); + + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + ' ['rand' => 'random_int', 'mt_rand' => 'random_int']], + ], + [ + 'doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + 'null cast' => [ + ' [ + ' [ + 'fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -814,15 +812,13 @@ public function bar() { /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixMissingBracesAndIndentCases */ - public function testFixMissingBracesAndIndent($expected, $input = null, array $configuration = null) + public function testFixMissingBracesAndIndent($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -2746,15 +2742,13 @@ function foo() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixClassyBracesCases */ - public function testFixClassyBraces($expected, $input = null, array $configuration = null) + public function testFixClassyBraces($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -2933,15 +2927,13 @@ trait TFoo {public $a;}', /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixAnonFunctionInShortArraySyntaxCases */ - public function testFixAnonFunctionInShortArraySyntax($expected, $input = null, array $configuration = null) + public function testFixAnonFunctionInShortArraySyntax($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -3053,15 +3045,13 @@ function myFunction() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixCommentBeforeBraceCases */ - public function testFixCommentBeforeBrace($expected, $input = null, array $configuration = null) + public function testFixCommentBeforeBrace($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -3150,16 +3140,14 @@ public function provideFixCommentBeforeBraceCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixCommentBeforeBrace70Cases * @requires PHP 7.0 */ - public function testFixCommentBeforeBrace70($expected, $input = null, array $configuration = null) + public function testFixCommentBeforeBrace70($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -3196,15 +3184,13 @@ public function provideFixCommentBeforeBrace70Cases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixWhitespaceBeforeBraceCases */ - public function testFixWhitespaceBeforeBrace($expected, $input = null, array $configuration = null) + public function testFixWhitespaceBeforeBrace($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -3417,15 +3403,13 @@ public function provideFixWhitespaceBeforeBraceCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixFunctionsCases */ - public function testFixFunctions($expected, $input = null, array $configuration = null) + public function testFixFunctions($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -3781,15 +3765,13 @@ function &($a, $b) use ($selfName) { /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixMultiLineStructuresCases */ - public function testFixMultiLineStructures($expected, $input = null, array $configuration = null) + public function testFixMultiLineStructures($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -3905,15 +3887,13 @@ public static function bar( /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixSpaceAroundTokenCases */ - public function testFixSpaceAroundToken($expected, $input = null, array $configuration = null) + public function testFixSpaceAroundToken($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -4106,15 +4086,13 @@ public function provideFixSpaceAroundTokenCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFinallyCases */ - public function testFinally($expected, $input = null, array $configuration = null) + public function testFinally($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -4209,15 +4187,13 @@ public function provideFinallyCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFunctionImportCases */ - public function testFunctionImport($expected, $input = null, array $configuration = null) + public function testFunctionImport($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -4263,16 +4239,14 @@ public function provideFunctionImportCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFix70Cases * @requires PHP 7.0 */ - public function testFix70($expected, $input = null, array $configuration = null) + public function testFix70($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -4920,15 +4894,13 @@ public function sth(): string /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider providePreserveLineAfterControlBraceCases */ - public function testPreserveLineAfterControlBrace($expected, $input = null, array $configuration = null) + public function testPreserveLineAfterControlBrace($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -5042,16 +5014,12 @@ public function providePreserveLineAfterControlBraceCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixWithAllowOnelineLambdaCases */ - public function testFixWithAllowSingleLineClosure($expected, $input = null, array $configuration = null) + public function testFixWithAllowSingleLineClosure($expected, $input = null) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } - $this->fixer->configure([ 'allow_single_line_closure' => true, ]); @@ -5126,15 +5094,13 @@ public function provideDoWhileLoopInsideAnIfWithoutBracketsCases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideMessyWhitespacesCases */ - public function testMessyWhitespaces($expected, $input = null, array $configuration = null) + public function testMessyWhitespaces($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t", "\r\n")); diff --git a/tests/Fixer/Basic/Psr0FixerTest.php b/tests/Fixer/Basic/Psr0FixerTest.php index 045b903b5df..29a1a8200c5 100644 --- a/tests/Fixer/Basic/Psr0FixerTest.php +++ b/tests/Fixer/Basic/Psr0FixerTest.php @@ -57,6 +57,21 @@ class Psr0_fOo_bAr {} $this->doTest($expected, $input, $file); } + public function testIgnoreMultipleClassyInFile() + { + $file = $this->getTestFile(__FILE__); + + $expected = <<<'EOF' +doTest($expected, null, $file); + } + public function testFixClassName() { $file = $this->getTestFile(__FILE__); @@ -176,6 +191,35 @@ class Psr0Fixer {} $this->doTest($expected, null, $file); } + /** + * @requires PHP 7.0 + */ + public function testClassWithAnonymousClass() + { + $file = $this->getTestFile(__FILE__); + + $expected = <<<'EOF' +doTest($expected, $input, $file); + } + /** * @requires PHP 7.0 */ @@ -200,6 +244,24 @@ public function testIgnoreAnonymousClass() $this->doTest($expected, null, $file); } + /** + * @requires PHP 7.0 + */ + public function testIgnoreMultipleClassWithAnonymousClass() + { + $file = $this->getTestFile(__FILE__); + + $expected = <<<'EOF' +doTest($expected, null, $file); + } + /** * @param string $filename * @@ -243,7 +305,7 @@ public function provideIgnoredCases() 'T_TRAIT' => 'trait', 'T_TRAIT_C' => '__TRAIT__', ] as $tokenType => $tokenValue) { - if (defined($tokenType)) { + if (\defined($tokenType)) { $ignoreCases[] = [$tokenValue.'.php']; $ignoreCases[] = [strtolower($tokenValue).'.php']; } diff --git a/tests/Fixer/Basic/Psr4FixerTest.php b/tests/Fixer/Basic/Psr4FixerTest.php index a4cadcba0a9..b9815341252 100644 --- a/tests/Fixer/Basic/Psr4FixerTest.php +++ b/tests/Fixer/Basic/Psr4FixerTest.php @@ -57,6 +57,21 @@ class Psr4_fOo_bAr {} $this->doTest($expected, $input, $file); } + public function testIgnoreMultipleClassyInFile() + { + $file = $this->getTestFile(__FILE__); + + $expected = <<<'EOF' +doTest($expected, null, $file); + } + public function testFixClassName() { $file = $this->getTestFile(__FILE__); @@ -163,6 +178,35 @@ class Psr4Fixer {} $this->doTest($expected, null, $file); } + /** + * @requires PHP 7.0 + */ + public function testClassWithAnonymousClass() + { + $file = $this->getTestFile(__FILE__); + + $expected = <<<'EOF' +doTest($expected, $input, $file); + } + /** * @requires PHP 7.0 */ @@ -187,6 +231,24 @@ public function testIgnoreAnonymousClass() $this->doTest($expected, null, $file); } + /** + * @requires PHP 7.0 + */ + public function testIgnoreMultipleClassWithAnonymousClass() + { + $file = $this->getTestFile(__FILE__); + + $expected = <<<'EOF' +doTest($expected, null, $file); + } + /** * @param string $filename * @@ -230,7 +292,7 @@ public function provideIgnoredCases() 'T_TRAIT' => 'trait', 'T_TRAIT_C' => '__TRAIT__', ] as $tokenType => $tokenValue) { - if (defined($tokenType)) { + if (\defined($tokenType)) { $ignoreCases[] = [$tokenValue.'.php']; $ignoreCases[] = [strtolower($tokenValue).'.php']; } diff --git a/tests/Fixer/Casing/LowercaseConstantsFixerTest.php b/tests/Fixer/Casing/LowercaseConstantsFixerTest.php index 8bb242ca9b8..d300180e0b6 100644 --- a/tests/Fixer/Casing/LowercaseConstantsFixerTest.php +++ b/tests/Fixer/Casing/LowercaseConstantsFixerTest.php @@ -139,6 +139,7 @@ class Null { } }', ], + ['False = 1; $this->True = 2; $this->Null = 3; } }'], ]; } } diff --git a/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php b/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php index 56bdab27a3d..192a694e8d6 100644 --- a/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php +++ b/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php @@ -137,6 +137,12 @@ public function provideFixCases() [ ' + * 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\Tests\Fixer\Casing; + +use PhpCsFixer\Fixer\Casing\MagicMethodCasingFixer; +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author SpacePossum + * + * @internal + * + * @covers \PhpCsFixer\Fixer\Casing\MagicMethodCasingFixer + */ +final class MagicMethodCasingFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + $fixerReflection = new \ReflectionClass(MagicMethodCasingFixer::class); + $property = $fixerReflection->getProperty('magicNames'); + $property->setAccessible(true); + $allMethodNames = $property->getValue(); + + // '__callStatic' + yield 'method declaration for "__callstatic".' => [ + ' [ + '', + '', + ]; + + unset($allMethodNames['__callstatic']); + + // static version of '__set_state' + yield 'method declaration for "__set_state".' => [ + ' [ + '', + '', + ]; + + // '__clone' + yield 'method declaration for "__clone".' => [ + ' $name) { + unset($allMethodNames[$name]); + + yield sprintf('method declaration for "%s".', $name) => [ + sprintf(' $name) { + yield sprintf('method call "%s".', $name) => [ + sprintf('%s($a, $b);', $name), + sprintf('%s($a, $b);', strtoupper($name)), + ]; + } + + // single argument + $methodNames = ['__get', '__isset', '__unset']; + + foreach ($methodNames as $i => $name) { + unset($allMethodNames[$name]); + + yield sprintf('method declaration for "%s".', $name) => [ + sprintf(' $name) { + yield sprintf('method call "%s".', $name) => [ + sprintf('%s($a);', $name), + sprintf('%s($a);', strtoupper($name)), + ]; + } + + // no argument + + foreach ($allMethodNames as $i => $name) { + yield sprintf('method declaration for "%s".', $name) => [ + sprintf(' $name) { + yield sprintf('method call "%s".', $name) => [ + sprintf('%s();', $name), + sprintf('%s();', strtoupper($name)), + ]; + } + + yield 'method declaration in interface' => [ + ' [ + 'doTest($expected, $input); + } + + public function provideDoNotFixCases() + { + return [ + [ + '__sleep() + /** ->__sleep() */ + echo $a->__sleep; + ', + ], + [ + '__not_magic(); + ', + ], + [ + 'a(); + ', + ], + ]; + } + + /** + * @requires PHP 7.0 + */ + public function testFixPHP7() + { + $this->doTest( + '__isset($b); // fix + } + + private function bar() + { + new class { + public function __unset($a) // fix + { + $b = null === $a + ? $b->__unset($a) // fix + : $a->__unset($a) // fix + ; + + return $b; + } + }; + } + } + + function __ISSET($bar){} // do not fix + + $a->__unset($foo); // fix + ', + '__IsseT($b); // fix + } + + private function bar() + { + new class { + public function __UnSet($a) // fix + { + $b = null === $a + ? $b->__UnSet($a) // fix + : $a->__UnSet($a) // fix + ; + + return $b; + } + }; + } + } + + function __ISSET($bar){} // do not fix + + $a->__UnSet($foo); // fix + ' + ); + } + + /** + * @requires PHP 7.3 + */ + public function testFix73() + { + $this->doTest( + '__invoke(1, );', + '__INVOKE(1, );' + ); + } +} diff --git a/tests/Fixer/Casing/NativeFunctionCasingFixerTest.php b/tests/Fixer/Casing/NativeFunctionCasingFixerTest.php index 60cd8cd8ce5..e873705441f 100644 --- a/tests/Fixer/Casing/NativeFunctionCasingFixerTest.php +++ b/tests/Fixer/Casing/NativeFunctionCasingFixerTest.php @@ -102,6 +102,10 @@ public function gettypE() function sqrT($a) { } + + function &END($a) + { + } } ', ], @@ -130,6 +134,55 @@ function sqrT($a) $a->STRTOLOWER(); ', ], + [ + 'doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + 'STRTOLOWER(1,); + ', + ], ]; } } diff --git a/tests/Fixer/CastNotation/ModernizeTypesCastingFixerTest.php b/tests/Fixer/CastNotation/ModernizeTypesCastingFixerTest.php index 530bf137802..45ef542983b 100644 --- a/tests/Fixer/CastNotation/ModernizeTypesCastingFixerTest.php +++ b/tests/Fixer/CastNotation/ModernizeTypesCastingFixerTest.php @@ -135,6 +135,10 @@ public function usesInval() [$multiLinePatternFixed, $multiLinePatternToFix], [$overriddenFunctionFixed, $overriddenFunction], + [ + 'doTest($expected, $input); + } + + public function provideFix70Cases() + { + return [ + [ + 'doTest($expected, $input); + } + + public function provideFix73Cases() + { + 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\Tests\Fixer\CastNotation; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @internal + * + * @covers \PhpCsFixer\Fixer\CastNotation\NoUnsetCastFixer + */ +final class NoUnsetCastFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + 'simple form I' => [ + " [ + " [ + " [ + 'doTest( + 'fixer->configure(['elements' => ['property', 'method', 'const']]); + + $this->doTest( + 'doTest( + 'expectException(InvalidFixerConfigurationException::class); + $this->expectExceptionMessageRegExp('#^\[header_comment\] Cannot use \'\*/\' in header\.$#'); + + $this->fixer->configure([ + 'header' => '/** test */', + 'comment_type' => 'PHPDoc', + ]); + } } diff --git a/tests/Fixer/Comment/MultilineCommentOpeningClosingFixerTest.php b/tests/Fixer/Comment/MultilineCommentOpeningClosingFixerTest.php index c6256b41869..7f7be96054a 100644 --- a/tests/Fixer/Comment/MultilineCommentOpeningClosingFixerTest.php +++ b/tests/Fixer/Comment/MultilineCommentOpeningClosingFixerTest.php @@ -27,20 +27,20 @@ final class MultilineCommentOpeningClosingFixerTest extends AbstractFixerTestCas * @param string $expected * @param null|string $input * - * @dataProvider provideDocblocksCases + * @dataProvider provideFixCases */ public function testFix($expected, $input = null) { $this->doTest($expected, $input); } - public function provideDocblocksCases() + public function provideFixCases() { return [ ['doTest($expected, $input); + } + + /** + * @return array + */ + public function provideFix71WithDefaultConfigurationCases() + { + return [ + [ + 'doTest($expected, $input); } + + public function testFixScopedOnly() + { + $this->fixer->configure(['scope' => 'namespaced']); + + $expected = <<<'EOT' +doTest($expected, $input); + } + + public function testFixScopedOnlyNoNamespace() + { + $this->fixer->configure(['scope' => 'namespaced']); + + $expected = <<<'EOT' +doTest($expected); + } } diff --git a/tests/Fixer/FunctionNotation/CombineNestedDirnameFixerTest.php b/tests/Fixer/FunctionNotation/CombineNestedDirnameFixerTest.php new file mode 100644 index 00000000000..05e95bbd02d --- /dev/null +++ b/tests/Fixer/FunctionNotation/CombineNestedDirnameFixerTest.php @@ -0,0 +1,134 @@ + + * 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\Tests\Fixer\FunctionNotation; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Gregor Harlan + * + * @internal + * + * @covers \PhpCsFixer\Fixer\FunctionNotation\CombineNestedDirnameFixer + */ +final class CombineNestedDirnameFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + * @requires PHP 7.0 + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + [ + 'doTest('doTest($expected, $input); + } + + public function provideFix73Cases() + { + 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\Tests\Fixer\FunctionNotation; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author SpacePossum + * + * @internal + * + * @covers \PhpCsFixer\Fixer\FunctionNotation\FOpenFlagOrderFixer + */ +final class FopenFlagOrderFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + 'most simple fix case' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + 'fopen($foo, "br+"); + ', + ], + 'comments, PHPDoc and literal' => [ + ' [ + ' + * 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\Tests\Fixer\FunctionNotation; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author SpacePossum + * + * @internal + * + * @covers \PhpCsFixer\Fixer\FunctionNotation\FopenFlagsFixer + */ +final class FopenFlagsFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param string $input + * @param array $config + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input, array $config = []) + { + $this->fixer->configure($config); + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + 'missing "b"' => [ + ' [ + ' [ + ' [ + ' false], + ], + '"t" and superfluous "b"' => [ + ' false], + ], + 'superfluous "b"' => [ + ' false], + ], + ]; + } + + /** + * @param string $expected + * + * @dataProvider provideDoNotFixCases + */ + public function testDoNotFix($expected) + { + $this->doTest($expected); + $this->fixer->configure(['b_mode' => false]); + $this->doTest($expected); + } + + public function provideDoNotFixCases() + { + return [ + 'not simple flags' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + 'fopen($foo, "r+");', + ], + 'comments, PHPDoc and literal' => [ + ' [ + 'fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -338,15 +336,13 @@ function foo() /* bar */ /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFix54Cases */ - public function test54($expected, $input = null, array $configuration = null) + public function test54($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } @@ -385,16 +381,14 @@ public function provideFix54Cases() /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFix70Cases * @requires PHP 7.0 */ - public function test70($expected, $input = null, array $configuration = null) + public function test70($expected, $input = null, array $configuration = []) { - if (null !== $configuration) { - $this->fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } diff --git a/tests/Fixer/FunctionNotation/ImplodeCallFixerTest.php b/tests/Fixer/FunctionNotation/ImplodeCallFixerTest.php new file mode 100644 index 00000000000..e8474cc9a5c --- /dev/null +++ b/tests/Fixer/FunctionNotation/ImplodeCallFixerTest.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\Tests\Fixer\FunctionNotation; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Kuba Werłos + * + * @internal + * + * @covers \PhpCsFixer\Fixer\FunctionNotation\ImplodeCallFixer + */ +final class ImplodeCallFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + yield ["implode($foo);']; + yield ['doTest($expected, $input); + } + + public function provideFix73Cases() + { + yield [ + 'fixer->configure($configuration); - } $indent = ' '; $lineEnding = "\n"; - if (null !== $input) { - if (false !== strpos($input, "\t")) { + + if (null !== $expected) { + if (false !== strpos($expected, "\t")) { $indent = "\t"; - } elseif (preg_match('/\n \S/', $input)) { + } elseif (preg_match('/\n \S/', $expected)) { $indent = ' '; } - if (false !== strpos($input, "\r")) { + + if (false !== strpos($expected, "\r")) { $lineEnding = "\r\n"; } } + + $this->fixer->configure($configuration); $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig( $indent, $lineEnding @@ -59,15 +60,12 @@ public function testFix($expected, $input = null, array $configuration = null) /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideFixCases */ - public function testFixWithDifferentLineEndings( - $expected, - $input = null, - array $configuration = null - ) { + public function testFixWithDifferentLineEndings($expected, $input = null, array $configuration = []) + { if (null !== $input) { $input = str_replace("\n", "\r\n", $input); } @@ -203,16 +201,20 @@ public function provideFixCases() ['keep_multiple_spaces_after_comma' => true], ], 'multi line testing method call' => [ - ' [ ' [ - "foo( << [ ' ['@compiler_optimized'], ], ], + [ + ' [ + ' ['@compiler_optimized'], + 'strict' => true, + ], + ], + 'scope namespaced and strict enabled' => [ + ' 'namespaced', + 'strict' => true, + ], + ], ]; } + + /** + * @requires PHP 7.3 + */ + public function testFix73() + { + $this->doTest( + 'doTest($expected, $input); + } + + /** + * @return array + */ + public function provideFix71cases() + { + return [ + [ + 'fixer->configure($config); - } + + $this->fixer->configure($config); $this->doTest($expected, $input); } diff --git a/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php b/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php index 8959b11e3c1..f2ac3bf6f7e 100644 --- a/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php +++ b/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php @@ -371,6 +371,13 @@ public function doSomething( ){} }', ], + // Test reference + [ + ' [ + <<<'EOF' +fixer->configure($config); + $this->doTest($expected, $input); } @@ -695,6 +698,10 @@ public function provideFix70Cases() ClassF }; ', + [ + 'sort_algorithm' => OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['class', 'const', 'function'], + ], ], [ ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['class', 'const', 'function'], + ], + ], + [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['class', 'function', 'const'], + ], + ], + [ + ' [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['class', 'function', 'const'], + ], + ], + 'alpha - [\'class\', \'const\', \'function\']' => [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['class', 'const', 'function'], + ], + ], + 'alpha - [\'function\', \'class\', \'const\']' => [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['function', 'class', 'const'], + ], + ], + 'alpha - [\'function\', \'const\', \'class\']' => [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['function', 'const', 'class'], + ], + ], + 'alpha - [\'const\', \'function\', \'class\']' => [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['const', 'function', 'class'], + ], + ], + 'alpha - [\'const\', \'class\', \'function\']' => [ + ' OrderedImportsFixer::SORT_ALPHA, + 'imports_order' => ['const', 'class', 'function'], + ], + ], + '"strcasecmp" vs. "strnatcasecmp"' => [ + ' OrderedImportsFixer::SORT_ALPHA, + ], + ], ]; } @@ -810,7 +1014,7 @@ public function testInvalidOrderType() * @param array $configuration * @param string $expectedValue */ - public function testInvalidSortAlgorithm($configuration, $expectedValue) + public function testInvalidSortAlgorithm(array $configuration, $expectedValue) { $this->expectException(\PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class); $this->expectExceptionMessage(sprintf( @@ -1565,6 +1769,18 @@ public function provideFix70ByLengthCases() use const some\b\{ConstX, ConstY, ConstZ, ConstG}; use some\b\{ClassA, ClassB, ClassC as C}; use some\a\{ ClassB,ClassC, /*z*/ ClassA as A}; +', + ], + [ + 'configureFixerWithAliasedOptions($config); - } + $this->configureFixerWithAliasedOptions($config); $this->doTest($expected, $input); } diff --git a/tests/Fixer/LanguageConstruct/DirConstantFixerTest.php b/tests/Fixer/LanguageConstruct/DirConstantFixerTest.php index 3409623cde6..e9149bbf8c5 100644 --- a/tests/Fixer/LanguageConstruct/DirConstantFixerTest.php +++ b/tests/Fixer/LanguageConstruct/DirConstantFixerTest.php @@ -85,6 +85,10 @@ public function provideFixCases() '", "", ], + [ + "", + "", + ], [ 'doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + 'fixer->configure($config); - } + $this->fixer->configure($config); $this->doTest($expected, $input); } @@ -122,4 +120,15 @@ public function provideFixCases() ], ]; } + + /** + * @requires PHP 7.3 + */ + public function testFix73() + { + $this->doTest( + 'fixer->configure($config); - } + $this->fixer->configure($config); $this->doTest($expected, $input); } @@ -245,4 +243,25 @@ public function testInvalidConfigurationValue() $this->fixer->configure(['pi123']); } + + /** + * @param string $expected + * @param null|string $input + * + * @requires PHP 7.0 + * @dataProvider provideFix70Cases + */ + public function testFix70($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFix70Cases() + { + return [ + [ + '', + ], + ]; + } } diff --git a/tests/Fixer/LanguageConstruct/IsNullFixerTest.php b/tests/Fixer/LanguageConstruct/IsNullFixerTest.php index c11c112f7d7..56c86e0b895 100644 --- a/tests/Fixer/LanguageConstruct/IsNullFixerTest.php +++ b/tests/Fixer/LanguageConstruct/IsNullFixerTest.php @@ -74,6 +74,7 @@ public function provideFixCases() ['', '', ], + + // edge cases: $isContainingDangerousConstructs, $wrapIntoParentheses + [ + 'doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + '', + '', + ], + + // edge cases: $isContainingDangerousConstructs, $wrapIntoParentheses + [ + 'bar = null;', 'bar);', ], + 'It replaces an unset on a property with = null II' => [ + 'bar = null ;', + 'bar );', + ], 'It replaces an unset on a static property with = null' => [ ' [ 'foo[0]);', ], - 'It works in a more complex unset ' => [ + 'It works in a more complex unset' => [ 'foo[0]); self::$foo = null; \Test\Baz::$fooBar = null; unset($bar->foo[0]); $this->foo = null; unset($a); unset($b);', 'foo[0], self::$foo, \Test\Baz::$fooBar, $bar->foo[0], $this->foo, $a, $b);', ], @@ -89,7 +93,7 @@ public function provideFixCases() ', ], 'It works with weirdly placed comments' => [ - 'foo[0]); self::$foo/*baz*/ = null; /*ello*/\Test\Baz::$fooBar/*comment*/ = null; unset($bar->foo[0]); $this->foo = null; unset($a); unset($b); + 'foo[0]); self::$foo = null/*baz*/; /*ello*/\Test\Baz::$fooBar = null/*comment*/; unset($bar->foo[0]); $this->foo = null; unset($a); unset($b); unset/*foo*/(/*bar*/$bar);', 'foo[0], self::$foo/*baz*/, /*ello*/\Test\Baz::$fooBar/*comment*/, $bar->foo[0], $this->foo, $a, $b); unset/*foo*/(/*bar*/$bar);', @@ -100,6 +104,138 @@ public function provideFixCases() 'a);', ], + 'It does not replace function call with class constant inside' => [ + ' [ + 'property[array_search(\Types::TYPE_RANDOM, $this->property)]);', + ], + ]; + } + + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFix70Cases + * @requires PHP 7.0 + */ + public function testFix70($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFix70Cases() + { + return [ + 'It does not break complex expressions' => [ + 'a); + ', + ], + ]; + } + + /** + * @param string $expected + * @param null|string $input + * + * @requires PHP 7.3 + * @dataProvider provideFix73Cases + */ + public function testFix73($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + 'It replaces an unset on a property with = null' => [ + 'bar = null;', + 'bar,);', + ], + 'It replaces multiple unsets, but not those that arent properties' => [ + 'bar = null; $bar->foo = null; unset($bar,);', + 'bar, $bar->foo, $bar,);', + ], + 'It replaces an unset on a static property with = null' => [ + ' [ + 'a; unset($foo,);', + ], + 'It replaces multiple unsets on variables with = null' => [ + 'bar = null; $bar->foo = null; $bar->baz = null; $a->ba = null;', + 'bar, $bar->foo, $bar->baz, $a->ba,);', + ], + 'It replaces multiple unsets, but not those that arent properties in multiple places' => [ + 'foo = null; unset($bar,);', + 'foo, $bar,);', + ], + 'It replaces $this -> and self:: replacements' => [ + 'bar = null; self::$foo = null; unset($bar,);', + 'bar, self::$foo, $bar,);', + ], + 'It does not replace unsets on arrays' => [ + 'foo[0],);', + ], + 'It works in a more complex unset' => [ + 'foo[0]); self::$foo = null; \Test\Baz::$fooBar = null; unset($bar->foo[0]); $this->foo = null; unset($a); unset($b,);', + 'foo[0], self::$foo, \Test\Baz::$fooBar, $bar->foo[0], $this->foo, $a, $b,);', + ], + 'It works with consecutive unsets' => [ + 'bar = null; unset($foo); unset($bar); unset($baz); $this->ab = null;', + 'bar, $foo, $bar, $baz, $this->ab,);', + ], + 'It does not replace unsets on arrays with special notation' => [ + 'foo{0},);', + ], + 'It works when around messy whitespace' => [ + 'b = null; + $this->a = null; unset($b,); +', + 'b,); + unset($this->a, $b,); +', + ], + 'It works with weirdly placed comments' => [ + 'foo[0]); self::$foo = null/*baz*/; /*ello*/\Test\Baz::$fooBar = null/*comment*/; unset($bar->foo[0]); $this->foo = null; unset($a); unset($b,); + unset/*foo*/(/*bar*/$bar,);', + 'foo[0], self::$foo/*baz*/, /*ello*/\Test\Baz::$fooBar/*comment*/, $bar->foo[0], $this->foo, $a, $b,); + unset/*foo*/(/*bar*/$bar,);', + ], + 'It does not mess with consecutive unsets' => [ + 'a = null;', + 'a,);', + ], + 'It does not replace function call with class constant inside' => [ + ' [ + 'property[array_search(\Types::TYPE_RANDOM, $this->property)],);', + ], + [ + 'bar = null ;', + 'bar, );', + ], + [ + 'bar = null ;', + 'bar ,);', + ], + [ + 'bar = null ;', + 'bar , );', + ], ]; } } diff --git a/tests/Fixer/LanguageConstruct/SilencedDeprecationErrorFixerTest.php b/tests/Fixer/LanguageConstruct/SilencedDeprecationErrorFixerTest.php index d00300f27b2..e3404fdab19 100644 --- a/tests/Fixer/LanguageConstruct/SilencedDeprecationErrorFixerTest.php +++ b/tests/Fixer/LanguageConstruct/SilencedDeprecationErrorFixerTest.php @@ -81,4 +81,15 @@ public function provideFixCases() ], ]; } + + /** + * @requires PHP 7.3 + */ + public function testFix73() + { + $this->doTest( + 'fixer->configure(['syntax' => 'short']); + $this->doTest($expected, $input); + } + + /** + * @param string $input + * @param string $expected + * + * @requires PHP 7.2 + * @dataProvider providePhp72Cases + */ + public function testFixToLongSyntaxPhp72($input, $expected) + { + $this->fixer->configure(['syntax' => 'long']); + $this->doTest($expected, $input); + } + + public function providePhp72Cases() + { + return [ + [ + 'fixer->configure($configuration); $this->doTest($expected, $input); @@ -79,11 +79,11 @@ public function myFunction() { /** * @param string $expected * @param null|string $input - * @param null|array $configuration + * @param array $configuration * * @dataProvider provideTestCases */ - public function testConfigured($expected, $input = null, array $configuration = null) + public function testConfigured($expected, $input = null, array $configuration = []) { $this->fixer->configure($configuration); $this->doTest($expected, $input); diff --git a/tests/Fixer/Operator/IncrementStyleFixerTest.php b/tests/Fixer/Operator/IncrementStyleFixerTest.php index 3fffc302aaf..4aad2a87cdd 100644 --- a/tests/Fixer/Operator/IncrementStyleFixerTest.php +++ b/tests/Fixer/Operator/IncrementStyleFixerTest.php @@ -138,6 +138,36 @@ public function provideFixPreIncrementCases() ['fixer->configure(['assertions' => ['__TEST__']]); } + /** + * @param string $expected + * @param string $input + * + * @requires PHP 7.3 + * @dataProvider provideFix73Cases + */ + public function testFix73($expected, $input) + { + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + 'assertTrue($a, );', + 'assertSame(true, $a, );', + ], + [ + 'assertTrue($a, $message , );', + 'assertSame(true, $a, $message , );', + ], + ]; + } + private function generateCases($expectedTemplate, $inputTemplate) { $cases = []; diff --git a/tests/Fixer/PhpUnit/PhpUnitDedicateAssertFixerTest.php b/tests/Fixer/PhpUnit/PhpUnitDedicateAssertFixerTest.php index 6b3f1249cb3..21b2590810b 100644 --- a/tests/Fixer/PhpUnit/PhpUnitDedicateAssertFixerTest.php +++ b/tests/Fixer/PhpUnit/PhpUnitDedicateAssertFixerTest.php @@ -423,6 +423,11 @@ public function provideTestAssertCountCases() $this->test(); // $this->assertSame($b, %s($a)); ', ], + 'do not fix 7' => [ + 'assertSame(2, count($array) - 1); + ', + ], ]; } @@ -467,4 +472,58 @@ public function provideTestAssertCountCasingCases() ], ]; } + + /** + * @param string $expected + * @param string $input + * + * @requires PHP 7.3 + * @dataProvider provideFix73Cases + */ + public function testFix73($expected, $input) + { + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + 'assertNan($a, );', + 'assertTrue(is_nan($a), );', + ], + [ + 'assertNan($a);', + 'assertTrue(is_nan($a, ));', + ], + [ + 'assertNan($a, );', + 'assertTrue(is_nan($a, ), );', + ], + [ + 'assertInternalType(\'array\', $a,);', + 'assertTrue(is_array($a,),);', + ], + [ + 'assertNan($b); + ', + 'assertTrue(\is_nan($b,)); + ', + ], + [ + 'assertFileExists($f, \'message\',); + ', + 'assertTrue(file_exists($f,), \'message\',); + ', + ], + [ + 'assertNan($y , );', + 'assertTrue(is_nan($y) , );', + ], + ]; + } } diff --git a/tests/Fixer/PhpUnit/PhpUnitExpectationFixerTest.php b/tests/Fixer/PhpUnit/PhpUnitExpectationFixerTest.php index e0f2a4d9cd4..079333f10ed 100644 --- a/tests/Fixer/PhpUnit/PhpUnitExpectationFixerTest.php +++ b/tests/Fixer/PhpUnit/PhpUnitExpectationFixerTest.php @@ -322,4 +322,70 @@ final class MyTest extends \PHPUnit_Framework_TestCase return [[$expected, $input]]; } + + /** + * @param string $expected + * @param string $input + * + * @requires PHP 7.3 + * @dataProvider provideFix73Cases + */ + public function testFix73($expected, $input) + { + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + 'expectException("RuntimeException"); + $this->expectExceptionMessage("msg"/*B*/); + $this->expectExceptionCode(/*C*/123); + zzz(); + } + }', + 'setExpectedException("RuntimeException", "msg"/*B*/, /*C*/123, ); + zzz(); + } + }', + ], + [ + 'expectException("RuntimeException"); + $this->expectExceptionMessage("msg"/*B*/); + $this->expectExceptionCode(/*C*/123/*D*/); + zzz(); + } + }', + 'setExpectedException("RuntimeException", "msg"/*B*/, /*C*/123, /*D*/); + zzz(); + } + }', + ], + ]; + } } diff --git a/tests/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixerTest.php b/tests/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixerTest.php index f2e813d9344..222ec57f120 100644 --- a/tests/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixerTest.php +++ b/tests/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixerTest.php @@ -27,6 +27,12 @@ public function testFix() { $expected = <<<'EOF' doTest($expected, $input); } + + public function testIgnoringNonPhpUnitClass() + { + $this->doTest(' + + * 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\Tests\Fixer\PhpUnit; + +use PhpCsFixer\Fixer\PhpUnit\PhpUnitMethodCasingFixer; +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @author Filippo Tessarotto + * + * @internal + * + * @covers \PhpCsFixer\Fixer\PhpUnit\PhpUnitMethodCasingFixer + */ +final class PhpUnitMethodCasingFixerTest extends AbstractFixerTestCase +{ + /** + * @dataProvider provideFixCases + * + * @param string $expected + * @param null|string $input + */ + public function testFixToCamelCase($expected, $input = null) + { + $this->doTest($expected, $input); + } + + /** + * @dataProvider provideFixCases + * + * @param mixed $camelExpected + * @param null|mixed $camelInput + */ + public function testFixToSnakeCase($camelExpected, $camelInput = null) + { + if (null === $camelInput) { + $expected = $camelExpected; + $input = $camelInput; + } else { + $expected = $camelInput; + $input = $camelExpected; + } + + $this->fixer->configure(['case' => PhpUnitMethodCasingFixer::SNAKE_CASE]); + $this->doTest($expected, $input); + } + + /** + * @return array + */ + public function provideFixCases() + { + return [ + 'skip non phpunit methods' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + 'doTest( + 'createMock("Foo",); + $this->createMock("Bar" ,); + $this->createMock("Baz" , ); + $this->createMock($foo(1, 2), ); + $this->createMock($foo(3, 4, )); + $this->createMock($foo(5, 6, ), ); + $this->createPartialMock("Foo", ["aaa"], ); + $this->createPartialMock("Foo", ["bbb", ], ); + $this->getMock("Foo", ["aaa"], ["argument"], ); + $this->getMock("Foo", ["bbb", ], ["argument", ], ); + } + }', + 'getMock("Foo",); + $this->getMock("Bar" ,); + $this->getMock("Baz" , ); + $this->getMock($foo(1, 2), ); + $this->getMock($foo(3, 4, )); + $this->getMock($foo(5, 6, ), ); + $this->getMock("Foo", ["aaa"], ); + $this->getMock("Foo", ["bbb", ], ); + $this->getMock("Foo", ["aaa"], ["argument"], ); + $this->getMock("Foo", ["bbb", ], ["argument", ], ); + } + }' + ); + } } diff --git a/tests/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixerTest.php b/tests/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixerTest.php index dbd5bd59ce8..7ac46a4305f 100644 --- a/tests/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixerTest.php +++ b/tests/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixerTest.php @@ -546,6 +546,46 @@ public function testMessageOnMultilinesWithAnotherSpaceAndTag() EOT , ], + 'annotation with double @' => [ + 'setExpectedException(\FooException::class); + + aaa(); + } + }', + ' [ + 'getMethodsMap() as $methodBefore => $methodAfter) { $cases[] = ["${methodBefore}(1, 1);"]; $cases[] = ["${methodAfter}(1, 1);"]; + $cases[] = ["${methodBefore}(1, 2, 'message', \$toMuch);"]; $cases[] = [ "${methodAfter}(1, 2);", "${methodBefore}(1, 2);", @@ -122,6 +123,33 @@ public function testInvalidConfig() $this->fixer->configure(['assertions' => ['__TEST__']]); } + /** + * @param string $expected + * @param string $input + * + * @requires PHP 7.3 + * @dataProvider provideFix73Cases + */ + public function testFix73($expected, $input) + { + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + foreach ($this->getMethodsMap() as $methodBefore => $methodAfter) { + yield [ + " */ diff --git a/tests/Fixer/PhpUnit/PhpUnitTestAnnotationFixerTest.php b/tests/Fixer/PhpUnit/PhpUnitTestAnnotationFixerTest.php index 58858e5249d..7adb7a09f6e 100644 --- a/tests/Fixer/PhpUnit/PhpUnitTestAnnotationFixerTest.php +++ b/tests/Fixer/PhpUnit/PhpUnitTestAnnotationFixerTest.php @@ -29,7 +29,7 @@ final class PhpUnitTestAnnotationFixerTest extends AbstractFixerTestCase * * @param string $expected * @param null|string $input - * @param null|array $config + * @param array $config */ public function testFix($expected, $input = null, array $config = []) { @@ -202,25 +202,6 @@ class Test extends \PhpUnit\FrameWork\TestCase public function works() {} }', ], - 'Annotation is removed, the function is one word and we want it to use snake case' => [ - ' 'snake'], - ], 'Annotation is added, and it is snake case' => [ ' 'annotation'], ], - 'Annotation is removed, and it is snake case' => [ - ' 'snake'], - ], 'Annotation gets added, it has an @depends, and we use snake case' => [ ' 'annotation'], ], - 'Annotation gets removed, it has an @depends and we use camel case' => [ - ' 'snake'], - ], 'Class has both camel and snake case, annotated functions and not, and wants to add annotations' => [ ' 'annotation'], ], - 'Annotation has to be removed from multiple functions and we use snake case' => [ - ' 'snake'], - ], 'Class with big doc blocks and multiple functions has to remove annotations' => [ ' [ - ' 'snake'], - ], 'Test Annotation has to be removed, but its just one line' => [ 'doTest($expected); } + public function testLineWithSpacesIsRemovedWhenNextTokenIsIndented() + { + $this->doTest( + 'doTest( + ' [ + 'no typehint' => [ ' [ + 'same typehint' => [ ' [ + 'same optional typehint' => [ ' [ + 'same typehint with description' => [ ' [ + 'no typehint mixed' => [ ' [ + 'multiple different types' => [ ' [ + 'same typehint with different casing' => [ ' [ + 'multiple arguments' => [ ' [ + 'with import' => [ ' [ + 'with root symbols' => [ ' [ + 'with mix of imported and fully qualified symbols' => [ ' [ + 'with aliased imported' => [ ' [ + 'with unmapped param' => [ ' [ + 'with param superfluous but not return' => [ ' [ + 'with not all params superfluous' => [ ' [ + 'with special type hints' => [ ' [ + ' [ + ' [ + ' [ + 'same type hint' => [ ' [ + 'same type hint with description' => [ ' [ + 'multiple different types' => [ ' [ + 'with import' => [ ' [ + 'with root symbols' => [ ' [ + 'with mix of imported and fully qualified symbols' => [ ' [ + 'with aliased imported' => [ ' [ + 'with scalar type hints' => [ ' [ + 'same nullable type hint' => [ ' [ + 'same nullable type hint reversed' => [ ' [ + 'same nullable type hint with description' => [ ' [ + 'same optional nullable type hint' => [ ' [ + 'multiple different types' => [ ' [ + 'with import' => [ ' [ + 'with root symbols' => [ ' [ + 'with mix of imported and fully qualified symbols' => [ ' [ + 'with aliased imported' => [ ' [ + 'with special type hints' => [ 'fixer->configure(['allow_mixed' => true]); + $this->doTest($expected, $input); + } + + public function provideFixWithAllowedMixedCases() + { + $cases = $this->provideFixCases(); + $cases['no typehint mixed'] = [ + 'fixer->configure(['allow_mixed' => true]); + $this->doTest($expected, $input); + } + + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixPhp71Cases + * @requires PHP 7.1 + */ + public function testFixPhp71WithAllowedMixed($expected, $input = null) + { + $this->fixer->configure(['allow_mixed' => true]); + $this->doTest($expected, $input); + } } diff --git a/tests/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixerTest.php b/tests/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixerTest.php index 140547dc0eb..72c05931156 100644 --- a/tests/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixerTest.php +++ b/tests/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixerTest.php @@ -20,7 +20,6 @@ * * @internal * - * @covers \PhpCsFixer\AbstractFunctionReferenceFixer * @covers \PhpCsFixer\Fixer\Phpdoc\PhpdocAddMissingParamAnnotationFixer */ final class PhpdocAddMissingParamAnnotationFixerTest extends AbstractFixerTestCase @@ -50,7 +49,7 @@ public function testConfigureRejectsInvalidConfigurationValue($value) $this->expectException(\PhpCsFixer\ConfigurationException\InvalidConfigurationException::class); $this->expectExceptionMessage(sprintf( 'expected to be of type "bool", but is of type "%s".', - is_object($value) ? get_class($value) : gettype($value) + \is_object($value) ? \get_class($value) : \gettype($value) )); $this->fixer->configure([ @@ -75,11 +74,11 @@ public function provideConfigureRejectsInvalidConfigurationValueCases() /** * @param string $expected * @param null|string $input - * @param null|array $config + * @param array $config * * @dataProvider provideFixCases */ - public function testFix($expected, $input = null, array $config = null) + public function testFix($expected, $input = null, array $config = []) { $this->fixer->configure($config ?: ['only_untyped' => false]); @@ -294,12 +293,12 @@ function hello($string) /** * @param string $expected * @param null|string $input - * @param null|array $config + * @param array $config * * @dataProvider provideFix70Cases * @requires PHP 7.0 */ - public function testFix70($expected, $input = null, array $config = null) + public function testFix70($expected, $input = null, array $config = []) { $this->fixer->configure($config ?: ['only_untyped' => false]); @@ -335,12 +334,12 @@ function f10(string $foo = "null", $bar) {}', /** * @param string $expected * @param null|string $input - * @param null|array $config + * @param array $config * * @dataProvider provideFix71Cases * @requires PHP 7.1 */ - public function testFix71($expected, $input = null, array $config = null) + public function testFix71($expected, $input = null, array $config = []) { $this->fixer->configure($config ?: ['only_untyped' => false]); @@ -369,11 +368,11 @@ function p1(?array $foo = null, $bar) {}', /** * @param string $expected * @param null|string $input - * @param null|array $config + * @param array $config * * @dataProvider provideMessyWhitespacesCases */ - public function testMessyWhitespaces($expected, $input = null, array $config = null) + public function testMessyWhitespaces($expected, $input = null, array $config = []) { $this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t", "\r\n")); $this->fixer->configure($config ?: ['only_untyped' => false]); @@ -390,4 +389,113 @@ public function provideMessyWhitespacesCases() ], ]; } + + /** + * @param string $expected + * @param string $input + * + * @dataProvider provideByReferenceCases + */ + public function testByReference($expected, $input) + { + $this->fixer->configure(['only_untyped' => false]); + $this->doTest($expected, $input); + } + + public function provideByReferenceCases() + { + return [ + [ + 'fixer->configure(['only_untyped' => false]); + $this->doTest($expected, $input); + } + + public function provideVariableNumberOfArgumentsCases() + { + return [ + [ + 'doTest($expected, $input); + } + + public function testFixVoidCaseInsensitive() + { + $expected = <<<'EOF' +doTest($expected, $input); + } + + public function testFixNullCaseInsensitive() + { + $expected = <<<'EOF' +doTest($expected, $input); @@ -127,4 +167,36 @@ public function testOtherDoNothing() $this->doTest($expected); } + + public function testYetAnotherDoNothing() + { + $expected = <<<'EOF' +doTest($expected); + } + + public function testHandleSingleLinePhpdoc() + { + $expected = <<<'EOF' +doTest($expected, $input); + } } diff --git a/tests/Fixer/Phpdoc/PhpdocTypesFixerTest.php b/tests/Fixer/Phpdoc/PhpdocTypesFixerTest.php index 49e7ea4c94b..eeb6f4a6dba 100644 --- a/tests/Fixer/Phpdoc/PhpdocTypesFixerTest.php +++ b/tests/Fixer/Phpdoc/PhpdocTypesFixerTest.php @@ -16,6 +16,7 @@ /** * @author Graham Campbell + * @author Dariusz Rumiński * * @internal * @@ -198,4 +199,38 @@ public function testInlineDoc() $this->doTest($expected, $input); } + + public function testWithConfig() + { + $expected = <<<'EOF' +fixer->configure(['groups' => ['simple', 'meta']]); + $this->doTest($expected, $input); + } + + public function testWrongConfig() + { + $this->expectException(\PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class); + $this->expectExceptionMessageRegExp('/^\[phpdoc_types\] Invalid configuration: The option "groups" .*\.$/'); + + $this->fixer->configure(['groups' => ['__TEST__']]); + } } diff --git a/tests/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixerTest.php b/tests/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixerTest.php new file mode 100644 index 00000000000..3059ec60a7f --- /dev/null +++ b/tests/Fixer/Phpdoc/PhpdocVarAnnotationCorrectOrderFixerTest.php @@ -0,0 +1,165 @@ + + * 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\Tests\Fixer\Phpdoc; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; + +/** + * @internal + * + * @author Kuba Werłos + * + * @covers \PhpCsFixer\Fixer\Phpdoc\PhpdocVarAnnotationCorrectOrderFixer + */ +final class PhpdocVarAnnotationCorrectOrderFixerTest extends AbstractFixerTestCase +{ + /** + * @param string $expected + * @param null|string $input + * + * @dataProvider provideFixCases + */ + public function testFix($expected, $input = null) + { + $this->doTest($expected, $input); + } + + public function provideFixCases() + { + yield [ // It's @param, we care only about @var + ' $foo */ +', + ' */ +', + ]; + + yield [ + ' $foo Array of something */ +', + ' Array of something */ +', + ]; + + yield [ + '|null $foo */ +', + '|null */ +', + ]; + } +} diff --git a/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php b/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php index a33b95cddba..504d7100db8 100644 --- a/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php +++ b/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php @@ -423,6 +423,13 @@ public function testDoNotFix($expected) public function provideDoNotFixCases() { return [ + 'invalid reference stays invalid' => [ + ' [ 'fixer->configure(['strategy' => MultilineWhitespaceBeforeSemicolonsFixer::STRATEGY_NEW_LINE_FOR_CHAINED_CALLS]); + $this->doTest($expected, $input); + } + + public function provideFix73Cases() + { + return [ + [ + "one()\n ->two(2, )\n;", + "one()\n ->two(2, );", + ], + [ + "one(1, )\n ->two()\n;", + "one(1, )\n ->two();", + ], + ]; + } } diff --git a/tests/Fixer/Strict/StrictParamFixerTest.php b/tests/Fixer/Strict/StrictParamFixerTest.php index c244f8bb6cc..f3941d129b1 100644 --- a/tests/Fixer/Strict/StrictParamFixerTest.php +++ b/tests/Fixer/Strict/StrictParamFixerTest.php @@ -135,8 +135,37 @@ public function provideFixCases() class Foo { public function __construct($foo, $bar) {} + }', + ], + [ + 'doTest( + 'fixer->configure($configuration); - } + $this->fixer->configure($configuration); $this->doTest($expected, $input); } diff --git a/tests/Fixer/StringNotation/ExplicitStringVariableFixerTest.php b/tests/Fixer/StringNotation/ExplicitStringVariableFixerTest.php index bb35ca39959..363086a5e73 100644 --- a/tests/Fixer/StringNotation/ExplicitStringVariableFixerTest.php +++ b/tests/Fixer/StringNotation/ExplicitStringVariableFixerTest.php @@ -51,6 +51,10 @@ public function provideTestFixCases() 'property}{$array[1]}!";', + 'property$array[1]!";', + ], [ 'b} start";', 'b start";', @@ -177,6 +185,21 @@ public function provideTestFixCases() 'b} start";', + 'b start";', + ], + [ + ' + * 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\Tests\Fixer\Whitespace; + +use PhpCsFixer\Tests\Test\AbstractFixerTestCase; +use PhpCsFixer\WhitespacesFixerConfig; + +/** + * @author Gregor Harlan + * + * @internal + * + * @covers \PhpCsFixer\Fixer\Whitespace\HeredocIndentationFixer + */ +final class HeredocIndentationFixerTest extends AbstractFixerTestCase +{ + /** + * @requires PHP <7.3 + */ + public function testDoNotFix() + { + $this->doTest( + <<<'TEST' +doTest($expected, $input); + } + + public function provideFixCases() + { + return [ + [ + <<<'EXPECTED' +fixer->setWhitespacesConfig(new WhitespacesFixerConfig("\t")); + + $expected = <<doTest($expected, $input); + } +} diff --git a/tests/Fixer/Whitespace/IndentationTypeFixerTest.php b/tests/Fixer/Whitespace/IndentationTypeFixerTest.php index 1d56630f348..e45d04c2679 100644 --- a/tests/Fixer/Whitespace/IndentationTypeFixerTest.php +++ b/tests/Fixer/Whitespace/IndentationTypeFixerTest.php @@ -319,7 +319,7 @@ public function provideMessyWhitespacesReversedCases() return array_filter( $this->provideMessyWhitespacesCases(), static function ($key) { - return !is_string($key) || false === strpos($key, 'mix indentation'); + return !\is_string($key) || false === strpos($key, 'mix indentation'); }, ARRAY_FILTER_USE_KEY ); diff --git a/tests/Fixer/Whitespace/MethodChainingIndentationFixerTest.php b/tests/Fixer/Whitespace/MethodChainingIndentationFixerTest.php index e1c7de91105..cf61bd8fdac 100644 --- a/tests/Fixer/Whitespace/MethodChainingIndentationFixerTest.php +++ b/tests/Fixer/Whitespace/MethodChainingIndentationFixerTest.php @@ -260,4 +260,28 @@ public function provideWindowsWhitespacesCases() ], ]; } + + /** + * @requires PHP 7.3 + */ + public function testFix73() + { + $this->doTest( + 'setEmail("voff.web@gmail.com", ) + ->setPassword("233434" ,) + ->setEmailConfirmed(false , ) + ->setEmailConfirmationCode("123456", ); +', + 'setEmail("voff.web@gmail.com", ) + + ->setPassword("233434" ,) + ->setEmailConfirmed(false , ) +->setEmailConfirmationCode("123456", ); +' + ); + } } diff --git a/tests/Fixer/Whitespace/NoExtraBlankLinesFixerTest.php b/tests/Fixer/Whitespace/NoExtraBlankLinesFixerTest.php index ae238ecfcba..9fb7334c724 100644 --- a/tests/Fixer/Whitespace/NoExtraBlankLinesFixerTest.php +++ b/tests/Fixer/Whitespace/NoExtraBlankLinesFixerTest.php @@ -763,9 +763,9 @@ public function provideOneAndInLine70Cases() * * @dataProvider provideBraceCases */ - public function testBraces(array $config = null, $expected, $input = null) + public function testBraces(array $config, $expected, $input = null) { - $this->fixer->configure(['tokens' => $config]); + $this->fixer->configure($config); $this->doTest($expected, $input); } @@ -773,16 +773,16 @@ public function provideBraceCases() { return [ [ - ['curly_brace_block'], + ['tokens' => ['curly_brace_block']], " ['curly_brace_block']], " ['parenthesis_brace_block']], ' ['parenthesis_brace_block']], " ['parenthesis_brace_block']], ' ['return']], ' ['square_brace_block']], "assertSame('baz', $option->getDefault()); $this->assertSame(['bool'], $option->getAllowedTypes()); $this->assertSame([true, false], $option->getAllowedValues()); - $this->assertInstanceOf('Closure', $option->getNormalizer()); + $this->assertInstanceOf(\Closure::class, $option->getNormalizer()); $this->assertSame('baz', $option->getAlias()); } } diff --git a/tests/FixerConfiguration/AliasedFixerOptionTest.php b/tests/FixerConfiguration/AliasedFixerOptionTest.php index 30d21b18d52..9e79a473258 100644 --- a/tests/FixerConfiguration/AliasedFixerOptionTest.php +++ b/tests/FixerConfiguration/AliasedFixerOptionTest.php @@ -114,7 +114,7 @@ public function testGetUndefinedDefault() { $option = new AliasedFixerOption(new FixerOption('foo', 'Bar.'), 'baz'); - $this->expectException('LogicException'); + $this->expectException(\LogicException::class); $this->expectExceptionMessage('No default value defined.'); $option->getDefault(); } @@ -168,7 +168,7 @@ public function testGetAllowedValuesClosure() $this->assertInternalType('array', $allowedTypes); $this->assertCount(1, $allowedTypes); $this->assertArrayHasKey(0, $allowedTypes); - $this->assertInstanceOf('Closure', $allowedTypes[0]); + $this->assertInstanceOf(\Closure::class, $allowedTypes[0]); } public function testGetNormalizers() @@ -177,7 +177,7 @@ public function testGetNormalizers() $this->assertNull($option->getNormalizer()); $option = new AliasedFixerOption(new FixerOption('foo', 'Bar.', true, null, null, null, function () {}), 'baz'); - $this->assertInstanceOf('Closure', $option->getNormalizer()); + $this->assertInstanceOf(\Closure::class, $option->getNormalizer()); } /** @@ -202,7 +202,7 @@ public function provideGetAliasCases() public function testRequiredWithDefaultValue() { - $this->expectException('LogicException'); + $this->expectException(\LogicException::class); $this->expectExceptionMessage('Required options cannot have a default value.'); new AliasedFixerOption(new FixerOption('foo', 'Bar.', true, false), 'baz'); diff --git a/tests/FixerConfiguration/FixerConfigurationResolverTest.php b/tests/FixerConfiguration/FixerConfigurationResolverTest.php index cc6a89cde17..a64198d1a33 100644 --- a/tests/FixerConfiguration/FixerConfigurationResolverTest.php +++ b/tests/FixerConfiguration/FixerConfigurationResolverTest.php @@ -48,7 +48,7 @@ public function testWithDuplicatesOptions() public function testWithDuplicateAliasOptions() { - $this->expectException('LogicException'); + $this->expectException(\LogicException::class); $this->expectExceptionMessage('The "foo" option is defined multiple times.'); new FixerConfigurationResolver([ @@ -192,7 +192,7 @@ public function testResolveWithAliasedDuplicateConfig() new AliasedFixerOption(new FixerOption('bar', 'Bar.'), 'baz'), ]); - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectException(\Symfony\Component\OptionsResolver\Exception\InvalidOptionsException::class); $this->expectExceptionMessage('Aliased option bar/baz is passed multiple times'); $configuration->resolve([ diff --git a/tests/FixerDefinition/VersionSpecificCodeSampleTest.php b/tests/FixerDefinition/VersionSpecificCodeSampleTest.php index 8477cf0a0d2..254cc7d9448 100644 --- a/tests/FixerDefinition/VersionSpecificCodeSampleTest.php +++ b/tests/FixerDefinition/VersionSpecificCodeSampleTest.php @@ -65,7 +65,8 @@ public function testIsSuitableForUsesVersionSpecification($version, $isSatisfied $versionSpecification ->isSatisfiedBy($version) - ->willReturn($isSatisfied); + ->willReturn($isSatisfied) + ; $codeSample = new VersionSpecificCodeSample( 'registerBuiltInFixers(); - $this->assertGreaterThan(0, count($factory->getFixers())); + $this->assertGreaterThan(0, \count($factory->getFixers())); } /** @@ -92,7 +92,7 @@ public function testThatFixersAreSorted() } // There are no rules that forces $fxs[1] to be prioritized before $fxs[3]. We should not test against that - $this->assertSame([$fxs[2], $fxs[0]], array_slice($factory->getFixers(), 0, 2)); + $this->assertSame([$fxs[2], $fxs[0]], \array_slice($factory->getFixers(), 0, 2)); } /** @@ -111,9 +111,9 @@ public function testThatCanRegisterAndGetFixers() $factory->registerFixer($f1, false); $factory->registerCustomFixers([$f2, $f3]); - $this->assertTrue(in_array($f1, $factory->getFixers(), true)); - $this->assertTrue(in_array($f2, $factory->getFixers(), true)); - $this->assertTrue(in_array($f3, $factory->getFixers(), true)); + $this->assertTrue(\in_array($f1, $factory->getFixers(), true)); + $this->assertTrue(\in_array($f2, $factory->getFixers(), true)); + $this->assertTrue(\in_array($f3, $factory->getFixers(), true)); } /** @@ -250,7 +250,7 @@ public function testSetWhitespacesConfig() $factory = new FixerFactory(); $config = new WhitespacesFixerConfig(); - $fixer = $this->prophesize('PhpCsFixer\Fixer\WhitespacesAwareFixerInterface'); + $fixer = $this->prophesize(\PhpCsFixer\Fixer\WhitespacesAwareFixerInterface::class); $fixer->getName()->willReturn('foo'); $fixer->setWhitespacesConfig($config)->shouldBeCalled(); @@ -265,7 +265,7 @@ public function testRegisterFixerInvalidName() $fixer = $this->createFixerDouble('0'); - $this->expectException('UnexpectedValueException'); + $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessage('Fixer named "0" has invalid name.'); $factory->registerFixer($fixer, false); @@ -279,7 +279,7 @@ public function testConfigureNonConfigurableFixer() $factory->registerFixer($fixer, false); $this->expectException( - 'PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException' + \PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class ); $this->expectExceptionMessage( '[non_configurable] Is not configurable.' @@ -299,13 +299,13 @@ public function testConfigureFixerWithNonArray($value) { $factory = new FixerFactory(); - $fixer = $this->prophesize('PhpCsFixer\Fixer\ConfigurableFixerInterface'); + $fixer = $this->prophesize(\PhpCsFixer\Fixer\ConfigurableFixerInterface::class); $fixer->getName()->willReturn('foo'); $factory->registerFixer($fixer->reveal(), false); $this->expectException( - 'PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException' + \PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class ); $this->expectExceptionMessage( '[foo] Configuration must be an array and may not be empty.' @@ -328,7 +328,7 @@ public function provideConfigureFixerWithNonArrayCases() public function testConfigurableFixerIsConfigured() { - $fixer = $this->prophesize('PhpCsFixer\Fixer\ConfigurableFixerInterface'); + $fixer = $this->prophesize(\PhpCsFixer\Fixer\ConfigurableFixerInterface::class); $fixer->getName()->willReturn('foo'); $fixer->configure(['bar' => 'baz'])->shouldBeCalled(); diff --git a/tests/Fixtures/FunctionReferenceTestFixer.php b/tests/Fixtures/FunctionReferenceTestFixer.php new file mode 100644 index 00000000000..e81fd076377 --- /dev/null +++ b/tests/Fixtures/FunctionReferenceTestFixer.php @@ -0,0 +1,39 @@ + + * 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\Tests\Fixtures; + +use PhpCsFixer\AbstractFunctionReferenceFixer; +use PhpCsFixer\Tokenizer\Tokens; + +final class FunctionReferenceTestFixer extends AbstractFunctionReferenceFixer +{ + public function getDefinition() + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function isCandidate(Tokens $tokens) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function findTest($functionNameToSearch, Tokens $tokens, $start = 0, $end = null) + { + return parent::find($functionNameToSearch, $tokens, $start, $end); + } + + protected function applyFix(\SplFileInfo $file, Tokens $tokens) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/tests/Fixtures/Integration/misc/PHP7.test b/tests/Fixtures/Integration/misc/PHP7.test index 45e09ab50c3..e027d1fdbf8 100644 --- a/tests/Fixtures/Integration/misc/PHP7.test +++ b/tests/Fixtures/Integration/misc/PHP7.test @@ -17,16 +17,16 @@ declare(strict_types=1); use some\a\ClassA; use some\a\ClassB; use some\a\ClassC as C; -use some\x\ClassB; use const some\a\ConstA; use const some\a\ConstB; use const some\a\ConstC; -use const some\x\E; use function some\a\fn_a; use function some\a\fn_b; use function some\a\fn_c; use function some\x\CC as C; +use some\x\ClassB; use function some\x\D; +use const some\x\E; function dummyUsage() { diff --git a/tests/Fixtures/Integration/misc/PHP7_3.test b/tests/Fixtures/Integration/misc/PHP7_3.test new file mode 100644 index 00000000000..6b122feb60d --- /dev/null +++ b/tests/Fixtures/Integration/misc/PHP7_3.test @@ -0,0 +1,161 @@ +--TEST-- +PHP 7.3 test. +--RULESET-- +{ + "@PHP71Migration": true, + "@PHP71Migration:risky": true, + "@Symfony": true, + "@Symfony:risky": true, + "heredoc_indentation": true, + "list_syntax": {"syntax": "short"}, + "mb_str_functions": true, + "method_chaining_indentation": true, + "multiline_whitespace_before_semicolons": true, + "native_function_invocation": {"include": ["get_class"]}, + "no_unset_cast": true, + "no_unset_on_property": true, + "php_unit_dedicate_assert": true, + "php_unit_expectation": true, + "php_unit_mock": true, + "php_unit_strict": true, + "php_unit_test_case_static_method_calls": {"call_type": "this"}, + "strict_param": true +} +--REQUIREMENTS-- +{"php": 70300} +--EXPECT-- +__invoke(1, ); // `magic_method_casing` rule +implode('', $pieces, ); // `implode_call` rule +implode('', $pieces, ); // `implode_call` rule +null === $var; // `is_null` rule +mb_strpos($a, $b, ); // `mb_str_functions` rule +sample('foo', 'foobarbaz', 'baz', ); // `method_argument_space` rule +$user->setEmail('voff.web@gmail.com', ) // `method_chaining_indentation` rule + ->setPassword('233434', ); +$a = (int) $b; // `modernize_types_casting` rule +$this->method1() // `multiline_whitespace_before_semicolons` rule + ->method2(3, ); +mb_strlen($str, ); // `native_function_casing` rule +$c = \get_class($d, ); // `native_function_invocation` rule +$a = rtrim($b, ); // `no_alias_functions` rule +$foo->bar($arg1, $arg2, ); // `no_spaces_inside_parenthesis` rule +$this->assertTrue($a, ); // `php_unit_construct` rule +$this->assertNan($a, ); // `php_unit_dedicate_assert` rule +final class MyTest extends \PHPUnit_Framework_TestCase +{ + public function testFoo(): void + { + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('Msg'); + $this->expectExceptionCode(123); // `php_unit_expectation` rule + $this->createMock('Foo', ); // `php_unit_mock` rule + $this->assertSame(1, 2, ); // `php_unit_test_case_static_method_calls` rule + $this->assertSame(1, $a, '', ); // `php_unit_strict` rule + } +} +$a ** 1; // `pow_to_exponentiation` rule +random_int($a, $b, ); // `random_api_migration` rule +$foo = (int) $foo; // `set_type_to_cast` rule +in_array($b, $c, true, ); // `strict_param` rule +@trigger_error('Warning.', E_USER_DEPRECATED, ); // `error_suppression` rule +foo(null === $a, ); // `yoda_style` rule +$a = null; // `no_unset_cast` rule +$foo->bar = null; // `no_unset_on_property` rule +// `heredoc_indentation` rule + $a = <<<'EOD' + abc + def + ghi + EOD; + +--INPUT-- +__INVOKE(1, ); // `magic_method_casing` rule +implode($pieces, '', ); // `implode_call` rule +implode($pieces, ); // `implode_call` rule +is_null($var, ); // `is_null` rule +strpos($a, $b, ); // `mb_str_functions` rule +sample('foo', 'foobarbaz', 'baz' , ); // `method_argument_space` rule +$user->setEmail('voff.web@gmail.com', ) // `method_chaining_indentation` rule + ->setPassword('233434', ); +$a = intval($b, ); // `modernize_types_casting` rule +$this->method1() // `multiline_whitespace_before_semicolons` rule + ->method2(3, ) +; +STRLEN($str, ); // `native_function_casing` rule +$c = get_class($d, ); // `native_function_invocation` rule +$a = chop($b, ); // `no_alias_functions` rule +$foo->bar( $arg1, $arg2, );// `no_spaces_inside_parenthesis` rule +$this->assertSame(true, $a, ); // `php_unit_construct` rule +$this->assertTrue(is_nan($a, ), ); // `php_unit_dedicate_assert` rule +final class MyTest extends \PHPUnit_Framework_TestCase +{ + public function testFoo(): void + { + $this->setExpectedException('RuntimeException', 'Msg', 123, ); // `php_unit_expectation` rule + $this->getMock('Foo', ); // `php_unit_mock` rule + static::assertSame(1, 2, ); // `php_unit_test_case_static_method_calls` rule + self::assertEquals(1, $a, '', ); // `php_unit_strict` rule + } +} +pow($a, 1, ); // `pow_to_exponentiation` rule +rand($a, $b, ); // `random_api_migration` rule +settype($foo, "integer", ); // `set_type_to_cast` rule +in_array($b, $c, ); // `strict_param` rule +trigger_error('Warning.', E_USER_DEPRECATED, ); // `error_suppression` rule +foo($a === null, ); // `yoda_style` rule +$a =(unset)$z; // `no_unset_cast` rule +unset($foo->bar,); // `no_unset_on_property` rule +// `heredoc_indentation` rule + $a = <<<'EOD' +abc + def + ghi +EOD; diff --git a/tests/Fixtures/Integration/misc/combine_nested_dirname,no_trailing_whitespace.test b/tests/Fixtures/Integration/misc/combine_nested_dirname,no_trailing_whitespace.test new file mode 100644 index 00000000000..4de41fd5ea0 --- /dev/null +++ b/tests/Fixtures/Integration/misc/combine_nested_dirname,no_trailing_whitespace.test @@ -0,0 +1,16 @@ +--TEST-- +Integration of fixers: combine_nested_dirname,no_trailing_whitespace. +--RULESET-- +{"combine_nested_dirname": true, "no_trailing_whitespace": true} +--REQUIREMENTS-- +{"php": 70000} +--EXPECT-- +entity + ->expects($this->once()) + ->method('toXml') + ->willReturnCallback( + function (\DOMDocument $dom) { + return $dom->createElement('elem'); + }); + } +} + +--INPUT-- +entity + ->expects($this->once()) + ->method('toXml') + ->willReturnCallback( + function (\DOMDocument $dom) { + return $dom->createElement('elem'); + }); + } +} diff --git a/tests/Fixtures/Integration/priority/class_attributes_separation,braces.test b/tests/Fixtures/Integration/priority/class_attributes_separation,braces.test new file mode 100644 index 00000000000..35aa1e3ab53 --- /dev/null +++ b/tests/Fixtures/Integration/priority/class_attributes_separation,braces.test @@ -0,0 +1,27 @@ +--TEST-- +Integration of fixers: class_attributes_separation, braces +--RULESET-- +{"class_attributes_separation": true, "braces": true} +--EXPECT-- +bar() + ->baz([ + 'foo' => 'bar', + ]) + ; +} + +--INPUT-- +bar() + ->baz([ + 'foo' => 'bar', + ]) + ; +} diff --git a/tests/Fixtures/Integration/priority/no_alias_functions,implode_call.test b/tests/Fixtures/Integration/priority/no_alias_functions,implode_call.test new file mode 100644 index 00000000000..3305a318822 --- /dev/null +++ b/tests/Fixtures/Integration/priority/no_alias_functions,implode_call.test @@ -0,0 +1,13 @@ +--TEST-- +Integration of fixers: no_alias_functions,implode_call. +--RULESET-- +{"no_alias_functions": true, "implode_call": true} +--EXPECT-- +hello() + ->chain() + ->more(); + +} + +--INPUT-- +hello() + ->chain() + ->more() + ; +; +} diff --git a/tests/Fixtures/Integration/priority/php_unit_test_annotation,php_unit_method_casing.test b/tests/Fixtures/Integration/priority/php_unit_test_annotation,php_unit_method_casing.test new file mode 100644 index 00000000000..c1a304fc6e2 --- /dev/null +++ b/tests/Fixtures/Integration/priority/php_unit_test_annotation,php_unit_method_casing.test @@ -0,0 +1,25 @@ +--TEST-- +Integration of fixers: php_unit_test_annotation,php_unit_method_casing +--RULESET-- +{"php_unit_test_annotation": true, "php_unit_method_casing" : {"case": "snake_case"}} +--EXPECT-- + [ + [], + 'getFileName(), [ + if (\in_array($case->getFileName(), [ 'priority'.\DIRECTORY_SEPARATOR.'backtick_to_shell_exec,escape_implicit_backslashes.test', 'priority'.\DIRECTORY_SEPARATOR.'braces,indentation_type,no_break_comment.test', 'priority'.\DIRECTORY_SEPARATOR.'standardize_not_equals,binary_operator_spaces.test', diff --git a/tests/Linter/CachingLinterTest.php b/tests/Linter/CachingLinterTest.php index fa317c1e979..5265914816f 100644 --- a/tests/Linter/CachingLinterTest.php +++ b/tests/Linter/CachingLinterTest.php @@ -32,7 +32,7 @@ final class CachingLinterTest extends TestCase */ public function testIsAsync($isAsync) { - $sublinter = $this->prophesize('PhpCsFixer\Linter\LinterInterface'); + $sublinter = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class); $sublinter->isAsync()->willReturn($isAsync); $linter = new CachingLinter($sublinter->reveal()); @@ -56,10 +56,10 @@ public function testLintFileIsCalledOnceOnSameContent() 'baz.php' => 'prophesize('PhpCsFixer\Linter\LintingResultInterface'); - $result2 = $this->prophesize('PhpCsFixer\Linter\LintingResultInterface'); + $result1 = $this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class); + $result2 = $this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class); - $sublinter = $this->prophesize('PhpCsFixer\Linter\LinterInterface'); + $sublinter = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class); $sublinter->lintFile($fs->url().'/foo.php')->shouldBeCalledTimes(1)->willReturn($result1->reveal()); $sublinter->lintFile($fs->url().'/bar.php')->shouldNotBeCalled(); $sublinter->lintFile($fs->url().'/baz.php')->shouldBeCalledTimes(1)->willReturn($result2->reveal()); @@ -74,10 +74,10 @@ public function testLintFileIsCalledOnceOnSameContent() public function testLintSourceIsCalledOnceOnSameContent() { - $result1 = $this->prophesize('PhpCsFixer\Linter\LintingResultInterface'); - $result2 = $this->prophesize('PhpCsFixer\Linter\LintingResultInterface'); + $result1 = $this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class); + $result2 = $this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class); - $sublinter = $this->prophesize('PhpCsFixer\Linter\LinterInterface'); + $sublinter = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class); $sublinter->lintSource('shouldBeCalledTimes(1)->willReturn($result1->reveal()); $sublinter->lintSource('shouldBeCalledTimes(1)->willReturn($result2->reveal()); diff --git a/tests/Linter/LinterTest.php b/tests/Linter/LinterTest.php index 68867136ab8..0663a04eb97 100644 --- a/tests/Linter/LinterTest.php +++ b/tests/Linter/LinterTest.php @@ -25,7 +25,7 @@ final class LinterTest extends AbstractLinterTestCase { public function testIsAsync() { - $this->assertSame(!defined('TOKEN_PARSE'), $this->createLinter()->isAsync()); + $this->assertSame(!\defined('TOKEN_PARSE'), $this->createLinter()->isAsync()); } /** diff --git a/tests/Linter/ProcessLinterProcessBuilderTest.php b/tests/Linter/ProcessLinterProcessBuilderTest.php index 7ce67ebd256..16c7f4074f9 100644 --- a/tests/Linter/ProcessLinterProcessBuilderTest.php +++ b/tests/Linter/ProcessLinterProcessBuilderTest.php @@ -29,8 +29,8 @@ final class ProcessLinterProcessBuilderTest extends TestCase * @param string $file * @param string $expected * - * @testWith ["php", "foo.php", "\"php\" -l \"foo.php\""] - * ["C:\\Program Files\\php\\php.exe", "foo bar\\baz.php", "\"C:\\Program Files\\php\\php.exe\" -l \"foo bar\\baz.php\""] + * @testWith ["php", "foo.php", "'php' '-l' 'foo.php'"] + * ["C:\\Program Files\\php\\php.exe", "foo bar\\baz.php", "'C:\\Program Files\\php\\php.exe' '-l' 'foo bar\\baz.php'"] * @requires OS Linux|Darwin */ public function testPrepareCommandOnPhpOnLinuxOrMac($executable, $file, $expected) @@ -48,7 +48,7 @@ public function testPrepareCommandOnPhpOnLinuxOrMac($executable, $file, $expecte * @param string $file * @param string $expected * - * @testWith ["php", "foo.php", "\"php\" -l \"foo.php\""] + * @testWith ["php", "foo.php", "php -l foo.php"] * ["C:\\Program Files\\php\\php.exe", "foo bar\\baz.php", "\"C:\\Program Files\\php\\php.exe\" -l \"foo bar\\baz.php\""] * @requires OS ^Win */ diff --git a/tests/PregExceptionTest.php b/tests/PregExceptionTest.php index bee7044247e..c0adbe34477 100644 --- a/tests/PregExceptionTest.php +++ b/tests/PregExceptionTest.php @@ -27,6 +27,6 @@ public function testIsRuntimeException() { $exception = new PregException(); - $this->assertInstanceOf('\\RuntimeException', $exception); + $this->assertInstanceOf(\RuntimeException::class, $exception); } } diff --git a/tests/PregTest.php b/tests/PregTest.php index 70eb1ef1697..d471c42bb68 100644 --- a/tests/PregTest.php +++ b/tests/PregTest.php @@ -138,9 +138,9 @@ public function provideCommonCases() return [ ['/u/u', 'u'], ['/u/u', 'u/u'], - ['/./', chr(224).'bc'], + ['/./', \chr(224).'bc'], ['/à/', 'àbc'], - ['/'.chr(224).'|í/', 'àbc'], + ['/'.\chr(224).'|í/', 'àbc'], ]; } @@ -148,7 +148,7 @@ public function provideArrayOfPatternsCases() { return [ [['/à/', '/í/'], 'Tàíl'], - [['/'.chr(174).'/', '/'.chr(224).'/'], 'foo'], + [['/'.\chr(174).'/', '/'.\chr(224).'/'], 'foo'], ]; } @@ -166,13 +166,14 @@ public function testSplitFailing() /** * @param string $pattern + * @param string $subject * * @dataProvider provideCommonCases */ - public function testSplit($pattern) + public function testSplit($pattern, $subject) { - $expectedResult = preg_split($pattern, 'foo'); - $actualResult = Preg::split($pattern, 'foo'); + $expectedResult = preg_split($pattern, $subject); + $actualResult = Preg::split($pattern, $subject); $this->assertSame($expectedResult, $actualResult); } @@ -192,12 +193,12 @@ public function testCorrectnessForUtf8String() public function testCorrectnessForNonUtf8String() { $pattern = '/./u'; - $subject = chr(224).'bc'; + $subject = \chr(224).'bc'; Preg::match($pattern, $subject, $methodMatches); preg_match($pattern, $subject, $functionMatches); - $this->assertSame([chr(224)], $methodMatches); - $this->assertNotSame([chr(224)], $functionMatches); + $this->assertSame([\chr(224)], $methodMatches); + $this->assertNotSame([\chr(224)], $functionMatches); } } diff --git a/tests/RuleSetTest.php b/tests/RuleSetTest.php index 61c8676f0a6..75c7551b19b 100644 --- a/tests/RuleSetTest.php +++ b/tests/RuleSetTest.php @@ -182,7 +182,9 @@ public function testResolveRulesWithNestedSet() 'line_ending' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, - 'method_argument_space' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + ], 'no_break_comment' => true, 'no_closing_tag' => true, 'no_spaces_after_function_name' => true, @@ -222,7 +224,9 @@ public function testResolveRulesWithDisabledSet() 'line_ending' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, - 'method_argument_space' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + ], 'no_break_comment' => true, 'no_closing_tag' => true, 'no_spaces_after_function_name' => true, @@ -542,7 +546,7 @@ private function sort(array &$data) } foreach ($data as $key => $value) { - if (is_array($value)) { + if (\is_array($value)) { $this->sort($data[$key]); } } @@ -556,7 +560,7 @@ private function sort(array &$data) private function allInteger(array $values) { foreach ($values as $value) { - if (!is_int($value)) { + if (!\is_int($value)) { return false; } } diff --git a/tests/Runner/FileFilterIteratorTest.php b/tests/Runner/FileFilterIteratorTest.php index 9e0a93e3e60..4e1ec47e994 100644 --- a/tests/Runner/FileFilterIteratorTest.php +++ b/tests/Runner/FileFilterIteratorTest.php @@ -188,4 +188,34 @@ public function testInvalidIterator() iterator_to_array($filter); } + + /** + * @requires OS Linux|Darwin + */ + public function testFileIsAcceptedAfterFilteredAsSymlink() + { + $link = __DIR__.'/../Fixtures/Test/FileFilterIteratorTest/FileFilterIteratorTest.php.link'; + + $this->assertTrue(is_link($link), 'Fixture data is no longer correct for this test.'); + $this->assertSame(__FILE__, realpath($link), 'Fixture data is no longer correct for this test.'); + + $file = new \SplFileInfo(__FILE__); + $link = new \SplFileInfo($link); + + $cache = $this->prophesize(\PhpCsFixer\Cache\CacheManagerInterface::class); + $cache->needFixing( + __FILE__, + file_get_contents($file->getPathname()) + )->willReturn(true); + + $filter = new FileFilterIterator( + new \ArrayIterator([$link, $file]), + null, + $cache->reveal() + ); + + $files = iterator_to_array($filter); + $this->assertCount(1, $files); + $this->assertSame($file, reset($files)); + } } diff --git a/tests/Runner/RunnerTest.php b/tests/Runner/RunnerTest.php index 37fb9915193..d034e052c8e 100644 --- a/tests/Runner/RunnerTest.php +++ b/tests/Runner/RunnerTest.php @@ -40,13 +40,16 @@ public function testThatFixSuccessfully() $linterProphecy = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class); $linterProphecy ->isAsync() - ->willReturn(false); + ->willReturn(false) + ; $linterProphecy ->lintFile(Argument::type('string')) - ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()); + ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()) + ; $linterProphecy ->lintSource(Argument::type('string')) - ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()); + ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal()) + ; $fixers = [ new Fixer\ClassNotation\VisibilityRequiredFixer(), diff --git a/tests/Smoke/AbstractSmokeTest.php b/tests/Smoke/AbstractSmokeTest.php new file mode 100644 index 00000000000..2565348608c --- /dev/null +++ b/tests/Smoke/AbstractSmokeTest.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\Tests\Smoke; + +use PhpCsFixer\Tests\TestCase; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @requires OS Linux|Darwin + * @coversNothing + * @group covers-nothing + * @large + */ +abstract class AbstractSmokeTest extends TestCase +{ + protected static function markTestSkippedOrFail($message) + { + if (getenv('PHP_CS_FIXER_TEST_ALLOW_SKIPPING_SMOKE_TESTS')) { + self::markTestSkipped($message); + } + + self::fail($message.' Failing as test is obligatory because of `PHP_CS_FIXER_TEST_ALLOW_SKIPPING_SMOKE_TESTS=0`.'); + } +} diff --git a/tests/Smoke/CiIntegrationTest.php b/tests/Smoke/CiIntegrationTest.php index b19d378545a..ae8311a2295 100644 --- a/tests/Smoke/CiIntegrationTest.php +++ b/tests/Smoke/CiIntegrationTest.php @@ -14,7 +14,6 @@ use Keradus\CliExecutor\CommandExecutor; use Keradus\CliExecutor\ScriptExecutor; -use PhpCsFixer\Tests\TestCase; /** * @author Dariusz Rumiński @@ -26,7 +25,7 @@ * @group covers-nothing * @large */ -final class CiIntegrationTest extends TestCase +final class CiIntegrationTest extends AbstractSmokeTest { public static $fixtureDir; @@ -36,6 +35,18 @@ public static function setUpBeforeClass() self::$fixtureDir = __DIR__.'/../Fixtures/ci-integration'; + try { + CommandExecutor::create('composer --version', __DIR__)->getResult(); + } catch (\RuntimeException $e) { + self::markTestSkippedOrFail('Missing `composer` env script. Details:'."\n".$e->getMessage()); + } + + try { + CommandExecutor::create('composer check', __DIR__.'/../..')->getResult(); + } catch (\RuntimeException $e) { + self::markTestSkippedOrFail('Composer check failed. Details:'."\n".$e->getMessage()); + } + try { self::executeScript([ 'rm -rf .git', @@ -46,7 +57,7 @@ public static function setUpBeforeClass() 'git commit -m "init" -q', ]); } catch (\RuntimeException $e) { - self::markTestSkipped($e->getMessage()); + self::markTestSkippedOrFail($e->getMessage()); } } @@ -128,7 +139,7 @@ public function testIntegration( $steps[4], ]); - $optionalIncompatibilityWarning = 'PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.2.*. + $optionalIncompatibilityWarning = 'PHP needs to be a minimum version of PHP 5.6.0 and maximum version of PHP 7.3.*. Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable. '; @@ -143,7 +154,7 @@ public function testIntegration( preg_quote($optionalIncompatibilityWarning, '/'), preg_quote($optionalXdebugWarning, '/'), preg_quote('Loaded config default from ".php_cs.dist".', '/'), - strlen($expectedResult3Files), + \strlen($expectedResult3Files), preg_quote('Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error', '/') ); diff --git a/tests/Smoke/InstallViaComposerTest.php b/tests/Smoke/InstallViaComposerTest.php index ca158064fe9..d13be7f006e 100644 --- a/tests/Smoke/InstallViaComposerTest.php +++ b/tests/Smoke/InstallViaComposerTest.php @@ -13,8 +13,8 @@ namespace PhpCsFixer\Tests\Smoke; use Keradus\CliExecutor\CommandExecutor; +use PhpCsFixer\Console\Application; use PhpCsFixer\Utils; -use PHPUnit\Framework\TestCase; use Symfony\Component\Filesystem\Filesystem; /** @@ -26,24 +26,43 @@ * @group covers-nothing * @large */ -final class InstallViaComposerTest extends TestCase +final class InstallViaComposerTest extends AbstractSmokeTest { + private $stepsToVerifyInstallation = [ + // Confirm we can install. + 'composer install -q', + // Ensure that autoloader works. + 'composer dump-autoload --optimize', + 'php vendor/autoload.php', + // Ensure basic commands work. + 'vendor/bin/php-cs-fixer --version', + 'vendor/bin/php-cs-fixer fix --help', + ]; + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + try { CommandExecutor::create('php --version', __DIR__)->getResult(); } catch (\RuntimeException $e) { - self::markTestSkipped('Missing `php` env script. Details:'."\n".$e->getMessage()); + self::markTestSkippedOrFail('Missing `php` env script. Details:'."\n".$e->getMessage()); } try { CommandExecutor::create('composer --version', __DIR__)->getResult(); } catch (\RuntimeException $e) { - self::markTestSkipped('Missing `composer` env script. Details:'."\n".$e->getMessage()); + self::markTestSkippedOrFail('Missing `composer` env script. Details:'."\n".$e->getMessage()); + } + + try { + CommandExecutor::create('composer check', __DIR__.'/../..')->getResult(); + } catch (\RuntimeException $e) { + self::markTestSkippedOrFail('Composer check failed. Details:'."\n".$e->getMessage()); } } - public function testInstallationIsPossible() + public function testInstallationViaPathIsPossible() { $fs = new Filesystem(); @@ -68,11 +87,80 @@ public function testInstallationIsPossible() json_encode($initialComposerFileState, Utils::calculateBitmask(['JSON_PRETTY_PRINT'])) ); - $this->assertSame(0, CommandExecutor::create('composer install -q', $tmpPath)->getResult()->getCode()); - $this->assertSame(0, CommandExecutor::create('composer dump-autoload --optimize', $tmpPath)->getResult()->getCode()); - $this->assertSame(0, CommandExecutor::create('php vendor/autoload.php', $tmpPath)->getResult()->getCode()); - $this->assertSame(0, CommandExecutor::create('vendor/bin/php-cs-fixer --version', $tmpPath)->getResult()->getCode()); + self::assertCommandsWork($this->stepsToVerifyInstallation, $tmpPath); $fs->remove($tmpPath); } + + // test that respects `export-ignore` from `.gitattributes` file + public function testInstallationViaArtifactIsPossible() + { + // Composer Artifact Repository requires `zip` extension + if (!\extension_loaded('zip')) { + $this->markTestSkippedOrFail('No zip extension available.'); + } + + $fs = new Filesystem(); + + $tmpPath = tempnam(sys_get_temp_dir(), 'cs_fixer_tmp_'); + unlink($tmpPath); + $fs->mkdir($tmpPath); + + $tmpArtifactPath = tempnam(sys_get_temp_dir(), 'cs_fixer_tmp_'); + unlink($tmpArtifactPath); + $fs->mkdir($tmpArtifactPath); + + $fakeVersion = preg_replace('/\\-.+/', '', Application::VERSION, 1).'-alpha987654321'; + + $initialComposerFileState = [ + 'repositories' => [ + [ + 'type' => 'artifact', + 'url' => $tmpArtifactPath, + ], + ], + 'require' => [ + 'friendsofphp/php-cs-fixer' => $fakeVersion, + ], + ]; + + file_put_contents( + $tmpPath.'/composer.json', + json_encode($initialComposerFileState, Utils::calculateBitmask(['JSON_PRETTY_PRINT'])) + ); + + $cwd = __DIR__.'/../..'; + + $stepsToInitializeArtifact = [ + // Clone current version of project to new location, as we gonna modify it. + // Warning! Only already committed changes will be cloned! + "git clone . ${tmpArtifactPath}", + ]; + $stepsToPrepareArtifact = [ + // Configure git user for new repo to not use global git user. + // We need this, as global git user may not be set! + 'git config user.name test && git config user.email test', + // Adjust cloned project to expose version in `composer.json`. + // Without that, it would not be possible to use it as Composer Artifact. + "composer config version ${fakeVersion} && git add . && git commit -m 'provide version'", + // Create repo archive that will serve as Composer Artifact. + 'git archive HEAD --format=zip -o archive.zip', + // Drop the repo, keep the archive + 'git rm -r . && rm -rf .git', + ]; + + self::assertCommandsWork($stepsToInitializeArtifact, $cwd); + self::assertCommandsWork($stepsToPrepareArtifact, $tmpArtifactPath); + self::assertCommandsWork($this->stepsToVerifyInstallation, $tmpPath); + + $fs->remove($tmpPath); + $fs->remove($tmpArtifactPath); + } + + private static function assertCommandsWork(array $commands, $cwd) + { + foreach ($commands as $command) { + self::assertSame(0, CommandExecutor::create($command, $cwd)->getResult()->getCode()); + } + } } diff --git a/tests/Smoke/PharTest.php b/tests/Smoke/PharTest.php index da3f3464acf..5f696d2ab3e 100644 --- a/tests/Smoke/PharTest.php +++ b/tests/Smoke/PharTest.php @@ -16,7 +16,6 @@ use PhpCsFixer\Console\Application; use PhpCsFixer\Console\Command\DescribeCommand; use PhpCsFixer\Console\Command\HelpCommand; -use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandTester; /** @@ -28,7 +27,7 @@ * @group covers-nothing * @large */ -final class PharTest extends TestCase +final class PharTest extends AbstractSmokeTest { private static $pharCwd; private static $pharName; @@ -41,11 +40,7 @@ public static function setUpBeforeClass() self::$pharName = 'php-cs-fixer.phar'; if (!file_exists(self::$pharCwd.'/'.self::$pharName)) { - if (getenv('PHP_CS_FIXER_TEST_ALLOW_SKIPPING_PHAR_TESTS')) { - self::markTestSkipped('No phar file available.'); - } - - self::fail('No phar file available. Failing as test is obligatory because of `PHP_CS_FIXER_TEST_ALLOW_SKIPPING_PHAR_TESTS=0`.'); + self::markTestSkippedOrFail('No phar file available.'); } } @@ -96,6 +91,14 @@ public function testFix() ); } + public function testFixHelp() + { + $this->assertSame( + 0, + self::executePharCommand('fix --help')->getCode() + ); + } + /** * @param string $params * diff --git a/tests/Smoke/StdinTest.php b/tests/Smoke/StdinTest.php index a2c391131ef..fd0fa718339 100644 --- a/tests/Smoke/StdinTest.php +++ b/tests/Smoke/StdinTest.php @@ -13,7 +13,6 @@ namespace PhpCsFixer\Tests\Smoke; use Keradus\CliExecutor\CommandExecutor; -use PHPUnit\Framework\TestCase; /** * @author Dariusz Rumiński @@ -24,7 +23,7 @@ * @coversNothing * @group covers-nothing */ -final class StdinTest extends TestCase +final class StdinTest extends AbstractSmokeTest { public function testFixingStdin() { diff --git a/tests/StdinFileInfoTest.php b/tests/StdinFileInfoTest.php index 956c64f234d..0c5ce2f62cb 100644 --- a/tests/StdinFileInfoTest.php +++ b/tests/StdinFileInfoTest.php @@ -69,7 +69,7 @@ public function testGetFileInfo() { $fileInfo = new StdinFileInfo(); - $this->expectException('BadMethodCallException'); + $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Method "PhpCsFixer\StdinFileInfo::getFileInfo" is not implemented.'); $fileInfo->getFileInfo(); @@ -128,7 +128,7 @@ public function testGetPathInfo() { $fileInfo = new StdinFileInfo(); - $this->expectException('BadMethodCallException'); + $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Method "PhpCsFixer\StdinFileInfo::getPathInfo" is not implemented.'); $fileInfo->getPathInfo(); @@ -208,7 +208,7 @@ public function testOpenFile() { $fileInfo = new StdinFileInfo(); - $this->expectException('BadMethodCallException'); + $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('Method "PhpCsFixer\StdinFileInfo::openFile" is not implemented.'); $fileInfo->openFile(); diff --git a/tests/Test/AbstractFixerTestCase.php b/tests/Test/AbstractFixerTestCase.php index f85c96de044..22c1f425d6a 100644 --- a/tests/Test/AbstractFixerTestCase.php +++ b/tests/Test/AbstractFixerTestCase.php @@ -129,8 +129,8 @@ protected function doTest($expected, $input = null, \SplFileInfo $file = null) $tokens->clearEmptyTokens(); $this->assertSame( - count($tokens), - count(array_unique(array_map(static function (Token $token) { + \count($tokens), + \count(array_unique(array_map(static function (Token $token) { return spl_object_hash($token); }, $tokens->toArray()))), 'Token items inside Tokens collection must be unique.' @@ -173,27 +173,6 @@ protected function lintSource($source) } } - private function assertTokens(Tokens $expectedTokens, Tokens $inputTokens) - { - foreach ($expectedTokens as $index => $expectedToken) { - $option = ['JSON_PRETTY_PRINT']; - $inputToken = $inputTokens[$index]; - - $this->assertTrue( - $expectedToken->equals($inputToken), - sprintf("The token at index %d must be:\n%s,\ngot:\n%s.", $index, $expectedToken->toJson($option), $inputToken->toJson($option)) - ); - - $expectedTokenKind = $expectedToken->isArray() ? $expectedToken->getId() : $expectedToken->getContent(); - $this->assertTrue( - $inputTokens->isTokenKindFound($expectedTokenKind), - sprintf('The token kind %s must be found in fixed tokens collection.', $expectedTokenKind) - ); - } - - $this->assertSame($expectedTokens->count(), $inputTokens->count(), 'Both collections must have the same length.'); - } - /** * @return LinterInterface */ diff --git a/tests/Test/AbstractFixerWithAliasedOptionsTestCase.php b/tests/Test/AbstractFixerWithAliasedOptionsTestCase.php index 92c3242b067..b6ad6011ef5 100644 --- a/tests/Test/AbstractFixerWithAliasedOptionsTestCase.php +++ b/tests/Test/AbstractFixerWithAliasedOptionsTestCase.php @@ -73,7 +73,7 @@ protected function configureFixerWithAliasedOptions(array $configuration) $alias = $option->getAlias(); - if (array_key_exists($alias, $configuration)) { + if (\array_key_exists($alias, $configuration)) { $configuration[$option->getName()] = $configuration[$alias]; unset($configuration[$alias]); } diff --git a/tests/Test/AbstractIntegrationCaseFactory.php b/tests/Test/AbstractIntegrationCaseFactory.php index fc402450214..d94e4063e28 100644 --- a/tests/Test/AbstractIntegrationCaseFactory.php +++ b/tests/Test/AbstractIntegrationCaseFactory.php @@ -91,17 +91,17 @@ protected function determineConfig(SplFileInfo $file, $config) 'lineEnding' => "\n", ]); - if (!is_string($parsed['indent'])) { + if (!\is_string($parsed['indent'])) { throw new \InvalidArgumentException(sprintf( 'Expected string value for "indent", got "%s".', - is_object($parsed['indent']) ? get_class($parsed['indent']) : gettype($parsed['indent']).'#'.$parsed['indent'] + \is_object($parsed['indent']) ? \get_class($parsed['indent']) : \gettype($parsed['indent']).'#'.$parsed['indent'] )); } - if (!is_string($parsed['lineEnding'])) { + if (!\is_string($parsed['lineEnding'])) { throw new \InvalidArgumentException(sprintf( 'Expected string value for "lineEnding", got "%s".', - is_object($parsed['lineEnding']) ? get_class($parsed['lineEnding']) : gettype($parsed['lineEnding']).'#'.$parsed['lineEnding'] + \is_object($parsed['lineEnding']) ? \get_class($parsed['lineEnding']) : \gettype($parsed['lineEnding']).'#'.$parsed['lineEnding'] )); } @@ -122,10 +122,10 @@ protected function determineRequirements(SplFileInfo $file, $config) 'php' => \PHP_VERSION_ID, ]); - if (!is_int($parsed['php'])) { + if (!\is_int($parsed['php'])) { throw new \InvalidArgumentException(sprintf( 'Expected int value like 50509 for "php", got "%s".', - is_object($parsed['php']) ? get_class($parsed['php']) : gettype($parsed['php']).'#'.$parsed['php'] + \is_object($parsed['php']) ? \get_class($parsed['php']) : \gettype($parsed['php']).'#'.$parsed['php'] )); } @@ -172,10 +172,10 @@ protected function determineSettings(SplFileInfo $file, $config) 'checkPriority' => true, ]); - if (!is_bool($parsed['checkPriority'])) { + if (!\is_bool($parsed['checkPriority'])) { throw new \InvalidArgumentException(sprintf( 'Expected bool value for "checkPriority", got "%s".', - is_object($parsed['checkPriority']) ? get_class($parsed['checkPriority']) : gettype($parsed['checkPriority']).'#'.$parsed['checkPriority'] + \is_object($parsed['checkPriority']) ? \get_class($parsed['checkPriority']) : \gettype($parsed['checkPriority']).'#'.$parsed['checkPriority'] )); } diff --git a/tests/Test/AbstractIntegrationTestCase.php b/tests/Test/AbstractIntegrationTestCase.php index ccfc5c01d8d..282ed919400 100644 --- a/tests/Test/AbstractIntegrationTestCase.php +++ b/tests/Test/AbstractIntegrationTestCase.php @@ -80,7 +80,7 @@ public static function setUpBeforeClass() self::$fileRemoval->observe($tmpFile); if (!is_file($tmpFile)) { - $dir = dirname($tmpFile); + $dir = \dirname($tmpFile); if (!is_dir($dir)) { $fs = new Filesystem(); @@ -264,7 +264,7 @@ protected function doTest(IntegrationCase $case) ) ); - if (1 < count($fixers)) { + if (1 < \count($fixers)) { $tmpFile = static::getTempFile(); if (false === @file_put_contents($tmpFile, $input)) { throw new IOException(sprintf('Failed to write to tmp. file "%s".', $tmpFile)); @@ -315,7 +315,7 @@ protected static function assertRevertedOrderFixing(IntegrationCase $case, $fixe if ($fixedInputCode !== $fixedInputCodeWithReversedFixers) { static::assertGreaterThan( 1, - count(array_unique(array_map( + \count(array_unique(array_map( static function (FixerInterface $fixer) { return $fixer->getPriority(); }, @@ -344,7 +344,8 @@ private static function createFixers(IntegrationCase $case) ->setWhitespacesConfig( new WhitespacesFixerConfig($config['indent'], $config['lineEnding']) ) - ->getFixers(); + ->getFixers() + ; } /** diff --git a/tests/Test/AbstractTransformerTestCase.php b/tests/Test/AbstractTransformerTestCase.php index 3868617e865..c1df20c43ab 100644 --- a/tests/Test/AbstractTransformerTestCase.php +++ b/tests/Test/AbstractTransformerTestCase.php @@ -28,12 +28,12 @@ protected function doTest($source, array $expectedTokens = [], array $observedKi $tokens = Tokens::fromCode($source); $this->assertSame( - count($expectedTokens), + \count($expectedTokens), $this->countTokenPrototypes( $tokens, array_map( static function ($kindOrPrototype) { - return is_int($kindOrPrototype) ? [$kindOrPrototype] : $kindOrPrototype; + return \is_int($kindOrPrototype) ? [$kindOrPrototype] : $kindOrPrototype; }, array_unique(array_merge($observedKindsOrPrototypes, $expectedTokens)) ) @@ -42,7 +42,7 @@ static function ($kindOrPrototype) { ); foreach ($expectedTokens as $index => $tokenIdOrContent) { - if (is_string($tokenIdOrContent)) { + if (\is_string($tokenIdOrContent)) { $this->assertTrue($tokens[$index]->equals($tokenIdOrContent)); continue; diff --git a/tests/Test/Assert/AssertTokensTrait.php b/tests/Test/Assert/AssertTokensTrait.php index d5769d531b8..1a6232ae059 100644 --- a/tests/Test/Assert/AssertTokensTrait.php +++ b/tests/Test/Assert/AssertTokensTrait.php @@ -40,7 +40,7 @@ private function assertTokens(Tokens $expectedTokens, Tokens $inputTokens) sprintf( 'The token kind %s (%s) must be found in tokens collection.', $expectedTokenKind, - is_string($expectedTokenKind) ? $expectedTokenKind : Token::getNameForId($expectedTokenKind) + \is_string($expectedTokenKind) ? $expectedTokenKind : Token::getNameForId($expectedTokenKind) ) ); } diff --git a/tests/Test/IntegrationCase.php b/tests/Test/IntegrationCase.php index 58abb33c8fc..212e56a34a0 100644 --- a/tests/Test/IntegrationCase.php +++ b/tests/Test/IntegrationCase.php @@ -124,14 +124,14 @@ public function getInputCode() */ public function getRequirement($name) { - if (!is_string($name)) { + if (!\is_string($name)) { throw new \InvalidArgumentException(sprintf( 'Requirement key must be a string, got "%s".', - is_object($name) ? get_class($name) : gettype($name).'#'.$name + \is_object($name) ? \get_class($name) : \gettype($name).'#'.$name )); } - if (!array_key_exists($name, $this->requirements)) { + if (!\array_key_exists($name, $this->requirements)) { throw new \InvalidArgumentException(sprintf( 'Unknown requirement key "%s", expected any of "%s".', $name, diff --git a/tests/Test/InternalIntegrationCaseFactory.php b/tests/Test/InternalIntegrationCaseFactory.php index 9bea2c8a50f..0228b2cc537 100644 --- a/tests/Test/InternalIntegrationCaseFactory.php +++ b/tests/Test/InternalIntegrationCaseFactory.php @@ -28,7 +28,7 @@ protected function determineSettings(SplFileInfo $file, $config) { $parsed = parent::determineSettings($file, $config); - $parsed['isExplicitPriorityCheck'] = in_array('priority', explode(\DIRECTORY_SEPARATOR, $file->getRelativePathname()), true); + $parsed['isExplicitPriorityCheck'] = \in_array('priority', explode(\DIRECTORY_SEPARATOR, $file->getRelativePathname()), true); return $parsed; } diff --git a/tests/Tokenizer/Analyzer/Analysis/TypeAnalysisTest.php b/tests/Tokenizer/Analyzer/Analysis/TypeAnalysisTest.php index dc82010d48d..e8083a430b2 100644 --- a/tests/Tokenizer/Analyzer/Analysis/TypeAnalysisTest.php +++ b/tests/Tokenizer/Analyzer/Analysis/TypeAnalysisTest.php @@ -68,7 +68,7 @@ public function provideReservedCases() ['bool', true], ['callable', true], ['int', true], - ['iteratable', true], + ['iterable', true], ['float', true], ['mixed', true], ['numeric', true], diff --git a/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php b/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php index f167bbde681..f79caa09e39 100644 --- a/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php +++ b/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php @@ -40,7 +40,7 @@ public function testArguments($code, $openIndex, $closeIndex, array $arguments) $tokens = Tokens::fromCode($code); $analyzer = new ArgumentsAnalyzer(); - $this->assertSame(count($arguments), $analyzer->countArguments($tokens, $openIndex, $closeIndex)); + $this->assertSame(\count($arguments), $analyzer->countArguments($tokens, $openIndex, $closeIndex)); $this->assertSame($arguments, $analyzer->getArguments($tokens, $openIndex, $closeIndex)); } @@ -67,7 +67,9 @@ public function provideArgumentsCases() { return [ [' 3]], + [' 4]], [' 3, 5 => 6]], [' 3, 5 => 15, 17 => 22]], ]; @@ -82,12 +84,24 @@ public function provideArgumentsInfoCases() null, null )], + ['', 5, 6, new ArgumentAnalysis( + '$b', + 6, + null, + null + )], ['assertSame(\count($arguments), $analyzer->countArguments($tokens, $openIndex, $closeIndex)); + $this->assertSame($arguments, $analyzer->getArguments($tokens, $openIndex, $closeIndex)); + } + + public function provideArguments73Cases() + { + return [ + [' 3]], + [' 3]], + [' 14]], + [' 15]], + [' 5, 7 => 7, 9 => 9, 11 => 11, 13 => 13]], + [' 3, 5 => 7]], + ]; + } } diff --git a/tests/Tokenizer/Analyzer/CommentsAnalyzerTest.php b/tests/Tokenizer/Analyzer/CommentsAnalyzerTest.php index c19f5ead486..6b1077376e7 100644 --- a/tests/Tokenizer/Analyzer/CommentsAnalyzerTest.php +++ b/tests/Tokenizer/Analyzer/CommentsAnalyzerTest.php @@ -184,7 +184,6 @@ public function testPhpdocCandidateAcceptsOnlyComments() } /** - * @param bool $isPhpdocCandidate * @param string $code * * @dataProvider providePhpdocCandidateCases @@ -236,7 +235,6 @@ public function providePhpdocCandidateCases() } /** - * @param bool $isPhpdocCandidate * @param string $code * * @dataProvider provideNotPhpdocCandidateCases diff --git a/tests/Tokenizer/Analyzer/FunctionsAnalyzerTest.php b/tests/Tokenizer/Analyzer/FunctionsAnalyzerTest.php index 063c4c2994e..54724eb427b 100644 --- a/tests/Tokenizer/Analyzer/FunctionsAnalyzerTest.php +++ b/tests/Tokenizer/Analyzer/FunctionsAnalyzerTest.php @@ -97,7 +97,17 @@ public function provideIsGlobalFunctionCallCases() ], [ false, - 'expectException('InvalidArgumentException'); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('No custom token was found for "123".'); CT::getName(123); @@ -74,7 +74,7 @@ public function testGetNameNotExists() public function testConstants($name, $value) { $this->assertGreaterThan(10000, $value); - $this->assertNull(@constant($name), 'The CT name must not use native T_* name.'); + $this->assertNull(@\constant($name), 'The CT name must not use native T_* name.'); } public function provideConstantsCases() diff --git a/tests/Tokenizer/Generator/NamespacedStringTokenGeneratorTest.php b/tests/Tokenizer/Generator/NamespacedStringTokenGeneratorTest.php index 6b3f650d0d5..34c11c1d891 100644 --- a/tests/Tokenizer/Generator/NamespacedStringTokenGeneratorTest.php +++ b/tests/Tokenizer/Generator/NamespacedStringTokenGeneratorTest.php @@ -10,7 +10,7 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\tests\Tokenizer\Generator; +namespace PhpCsFixer\Tests\Tokenizer\Generator; use PhpCsFixer\Tests\TestCase; use PhpCsFixer\Tokenizer\Generator\NamespacedStringTokenGenerator; diff --git a/tests/Tokenizer/Resolver/TypeShortNameResolverTest.php b/tests/Tokenizer/Resolver/TypeShortNameResolverTest.php index 3cb5c60fa3c..26b5e82ee3e 100644 --- a/tests/Tokenizer/Resolver/TypeShortNameResolverTest.php +++ b/tests/Tokenizer/Resolver/TypeShortNameResolverTest.php @@ -10,7 +10,7 @@ * with this source code in the file LICENSE. */ -namespace PhpCsFixer\tests\Tokenizer\Resolver; +namespace PhpCsFixer\Tests\Tokenizer\Resolver; use PhpCsFixer\Tests\TestCase; use PhpCsFixer\Tokenizer\Resolver\TypeShortNameResolver; diff --git a/tests/Tokenizer/TokensAnalyzerTest.php b/tests/Tokenizer/TokensAnalyzerTest.php index 82ba0fd0985..40f6d8347b9 100644 --- a/tests/Tokenizer/TokensAnalyzerTest.php +++ b/tests/Tokenizer/TokensAnalyzerTest.php @@ -544,6 +544,10 @@ public function provideIsConstantInvocationCases() ' true], ], + [ + ' true], + ], [ ' true, 5 => true], @@ -644,10 +648,22 @@ public function provideIsConstantInvocationCases() ' false], ], + [ + ' false], + ], + [ + ' true], + ], [ ' false, 6 => false], ], + [ + ' true, 7 => true], + ], ]; } @@ -1104,7 +1120,7 @@ public function testIsArray71($source, $tokenIndexes) $tokensAnalyzer = new TokensAnalyzer($tokens); foreach ($tokens as $index => $token) { - $expect = in_array($index, $tokenIndexes, true); + $expect = \in_array($index, $tokenIndexes, true); $this->assertSame( $expect, $tokensAnalyzer->isArray($index), @@ -1122,8 +1138,9 @@ public function provideIsArray71Cases() ["a" => $a, "b" => $b] = $array; $c = [$d, $e] = $array[$a]; [[$a, $b], [$c, $d]] = $d; + $array = []; $d = array(); ', - [51, 59], + [76, 84], ], ]; } diff --git a/tests/Tokenizer/TokensTest.php b/tests/Tokenizer/TokensTest.php index 3456c9183bc..b5fadf4e64b 100644 --- a/tests/Tokenizer/TokensTest.php +++ b/tests/Tokenizer/TokensTest.php @@ -522,7 +522,7 @@ public function bar() public function testClearTokenAndMergeSurroundingWhitespace($source, array $indexes, array $expected) { $this->doTestClearTokens($source, $indexes, $expected); - if (count($indexes) > 1) { + if (\count($indexes) > 1) { $this->doTestClearTokens($source, array_reverse($indexes), $expected); } } @@ -867,7 +867,7 @@ public function testClone() $this->assertTrue($tokens->isTokenKindFound(T_OPEN_TAG)); $this->assertTrue($tokensClone->isTokenKindFound(T_OPEN_TAG)); - $count = count($tokens); + $count = \count($tokens); $this->assertCount($count, $tokensClone); for ($i = 0; $i < $count; ++$i) { @@ -1174,6 +1174,72 @@ public function provideRemoveTrailingWhitespaceCases() return $cases; } + public function testRemovingLeadingWhitespaceWithEmptyTokenInCollection() + { + $code = "clearAt(2); + + $tokens->removeLeadingWhitespace(3); + + $tokens->clearEmptyTokens(); + $this->assertTokens(Tokens::fromCode("clearAt(2); + + $tokens->removeTrailingWhitespace(1); + + $tokens->clearEmptyTokens(); + $this->assertTokens(Tokens::fromCode("count(); + + $tokens->removeLeadingWhitespace(4); + + $this->assertSame($originalCount, $tokens->count()); + $this->assertSame( + 'generateCode() + ); + } + + /** + * Action that begins with the word "remove" should not change the size of collection. + */ + public function testRemovingTrailingWhitespaceWillNotIncreaseTokensCount() + { + $tokens = Tokens::fromCode('count(); + + $tokens->removeTrailingWhitespace(2); + + $this->assertSame($originalCount, $tokens->count()); + $this->assertSame( + 'generateCode() + ); + } + /** * @param null|Token[] $expected * @param null|Token[] $input @@ -1213,11 +1279,11 @@ private function doTestClearTokens($source, array $indexes, array $expected) $tokens->clearTokenAndMergeSurroundingWhitespace($index); } - $this->assertSame(count($expected), $tokens->count()); + $this->assertSame(\count($expected), $tokens->count()); foreach ($expected as $index => $expectedToken) { $token = $tokens[$index]; $expectedPrototype = $expectedToken->getPrototype(); - if (is_array($expectedPrototype)) { + if (\is_array($expectedPrototype)) { unset($expectedPrototype[2]); // don't compare token lines as our token mutations don't deal with line numbers } diff --git a/tests/Tokenizer/Transformer/SquareBraceTransformerTest.php b/tests/Tokenizer/Transformer/SquareBraceTransformerTest.php index c62a801168a..76729e17faa 100644 --- a/tests/Tokenizer/Transformer/SquareBraceTransformerTest.php +++ b/tests/Tokenizer/Transformer/SquareBraceTransformerTest.php @@ -49,7 +49,7 @@ public function testIsShortArray($source, $inspectIndexes, $expected) } foreach ($tokens as $index => $token) { - if (in_array($index, $inspectIndexes, true)) { + if (\in_array($index, $inspectIndexes, true)) { $this->assertSame('[', $tokens[$index]->getContent(), sprintf('Token @ index %d must have content \']\'', $index)); $exp = $expected; } elseif ('[' === $tokens[$index]->getContent()) { @@ -77,6 +77,7 @@ public function provideIsShortArrayCases() [' $a, "b" => $b, "c" => $c] = $array;', [1], false], + [' CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ], ], + [ + ' CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 9 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 14 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 15 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ], + ], + 'nested I' => [ + ' CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 5 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ], + ], + 'nested II (with array offset)' => [ + ' CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 6 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ], + ], + 'nested III' => [ + ' CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 8 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 10 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 17 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ], + ], + [ + ' CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 2 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 3 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 5 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 7 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 16 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 18 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 20 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 21 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 25 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ], + ], ]; } @@ -308,8 +356,6 @@ public function testProcess72($source, array $expectedTokens) $source, $expectedTokens, [ - CT::T_ARRAY_SQUARE_BRACE_OPEN, - CT::T_ARRAY_SQUARE_BRACE_CLOSE, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ] @@ -340,6 +386,21 @@ public function provideProcess72Cases() 8 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ], ], + [ + ' CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 2 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 4 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 11 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 14 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 17 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 19 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 22 => CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, + 26 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + 27 => CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, + ], + ], ]; } } diff --git a/tests/Tokenizer/Transformer/TypeAlternationTransformerTest.php b/tests/Tokenizer/Transformer/TypeAlternationTransformerTest.php index 32a5ba8bf15..27b499b678d 100644 --- a/tests/Tokenizer/Transformer/TypeAlternationTransformerTest.php +++ b/tests/Tokenizer/Transformer/TypeAlternationTransformerTest.php @@ -44,18 +44,40 @@ public function testProcess($source, array $expectedTokens = []) public function provideProcessCases() { return [ - [ + 'no namespace' => [ ' CT::T_TYPE_ALTERNATION, 15 => CT::T_TYPE_ALTERNATION, ], ], - [ + 'comments & spacing' => [ + " CT::T_TYPE_ALTERNATION, + ], + ], + 'native namespace only' => [ + ' CT::T_TYPE_ALTERNATION, + 17 => CT::T_TYPE_ALTERNATION, + ], + ], + 'namespaces' => [ + ' CT::T_TYPE_ALTERNATION, + 20 => CT::T_TYPE_ALTERNATION, + ], + ], + 'do not fix cases' => [ ' CT::T_USE_LAMBDA, ], ], + [ + ' T_USE, + ], + ], + ]; + } + + /** + * @param string $source + * @param array $expectedTokens index => kind + * + * @dataProvider provideFix70Cases + * @requires PHP 7.0 + */ + public function testFix70($source, array $expectedTokens = []) + { + $this->doTest( + $source, + $expectedTokens, + [ + T_USE, + CT::T_USE_LAMBDA, + CT::T_USE_TRAIT, + ] + ); + } + + public function provideFix70Cases() + { + return [ + 'nested anonymous classes' => [ + 'test(); + $a(); + } +}; +', + [ + 38 => CT::T_USE_LAMBDA, + 76 => CT::T_USE_TRAIT, + ], + ], ]; } diff --git a/tests/Tokenizer/TransformersTest.php b/tests/Tokenizer/TransformersTest.php new file mode 100644 index 00000000000..8ff3f0dd263 --- /dev/null +++ b/tests/Tokenizer/TransformersTest.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\Tests\Tokenizer; + +use PhpCsFixer\Tests\TestCase; +use PhpCsFixer\Tokenizer\CT; +use PhpCsFixer\Tokenizer\Tokens; + +/** + * @author Dave van der Brugge + * + * @internal + * + * @covers \PhpCsFixer\Tokenizer\Transformers + */ +final class TransformersTest extends TestCase +{ + /** + * @dataProvider provideTransformCases + * + * @param string $input + * @param array $expectedTokenKinds + */ + public function testTransform($input, $expectedTokenKinds) + { + $tokens = Tokens::fromCode($input); + + foreach ($expectedTokenKinds as $index => $expected) { + $this->assertTrue($tokens->offsetExists($index)); + $this->assertTrue($tokens[$index]->isGivenKind($expected)); + } + } + + public function provideTransformCases() + { + return [ + 'use trait after complex string variable' => [ + <<<'SOURCE' +assertSame('1', "{$a}"); + } + + use TestTrait; + + public function testUsingTrait() + { + $this->testTraitFunction(); + } +} + +SOURCE + , + [46 => CT::T_USE_TRAIT], + ], + ]; + } +} diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index d0d7dda1010..e518c167172 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -90,39 +90,6 @@ public function provideCmpIntCases() ]; } - /** - * @param array $expected - * @param string $input - * - * @dataProvider provideSplitLinesCases - */ - public function testSplitLines(array $expected, $input) - { - $this->assertSame($expected, Utils::splitLines($input)); - } - - public function provideSplitLinesCases() - { - return [ - [ - ["\t aaa\n", " bbb\n", "\t"], - "\t aaa\n bbb\n\t", - ], - [ - ["aaa\r\n", " bbb\r\n"], - "aaa\r\n bbb\r\n", - ], - [ - ["aaa\r\n", " bbb\n"], - "aaa\r\n bbb\n", - ], - [ - ["aaa\r\n\n\n\r\n", " bbb\n"], - "aaa\r\n\n\n\r\n bbb\n", - ], - ]; - } - /** * @param string $spaces * @param array|string $input token prototype