diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index f6eedd5..0000000 --- a/.coveralls.yml +++ /dev/null @@ -1,2 +0,0 @@ -coverage_clover: coverage-clover.xml -json_path: coveralls-upload.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8751c07 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +ecs-internal.php export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..fca3e08 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* jindrich.pech@almamedia.com diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..50112e8 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + time: "04:00" diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml index 726278d..f1744ff 100644 --- a/.github/workflows/pr-check.yaml +++ b/.github/workflows/pr-check.yaml @@ -8,7 +8,7 @@ jobs: name: Block fixup commits steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Block fixup commit merge uses: 13rac1/block-fixup-merge-action@v2.0.0 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 0d2d5eb..8921dda 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -6,16 +6,13 @@ on: schedule: - cron: '0 3 * * *' -env: - COVERALLS_SERVICE_NUMBER: ${{ github.run_id }}-${{ github.run_attempt }} - jobs: tests: runs-on: ubuntu-latest strategy: matrix: - php-version: ['8.0', '8.1', '8.2', '8.3'] + php-version: ['8.0', '8.1', '8.2', '8.3', '8.4'] dependencies: [''] include: - { php-version: '8.0', dependencies: '--prefer-lowest --prefer-stable' } @@ -23,7 +20,7 @@ jobs: name: PHP ${{ matrix.php-version }} ${{ matrix.dependencies }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,7 +28,6 @@ jobs: php-version: ${{ matrix.php-version }} extensions: mbstring, intl, zip coverage: xdebug - tools: composer:v2 - name: Install dependencies run: composer update --no-progress ${{ matrix.dependencies }} @@ -40,24 +36,33 @@ jobs: run: vendor/bin/phpunit --coverage-clover coverage-clover.xml - name: Submit coverage to Coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - composer global require --no-progress --dev php-coveralls/php-coveralls guzzlehttp/guzzle:^6.5 - ~/.composer/vendor/bin/php-coveralls -v + uses: coverallsapp/github-action@v2 + with: + flag-name: ${{ github.job }}-PHP-${{ matrix.php-version }} ${{ matrix.dependencies }} + file: coverage-clover.xml + parallel: true + + tests-finished: + needs: tests + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Notify Coveralls of finished builds + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true codestyle: name: "Code style and static analysis" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.3' extensions: mbstring, intl - tools: composer:v2 - name: Install dependencies run: composer update --no-progress @@ -72,7 +77,7 @@ jobs: name: "Markdown link check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - uses: gaurav-nelson/github-action-markdown-link-check@v1 with: use-verbose-mode: 'yes' diff --git a/CHANGELOG.md b/CHANGELOG.md index 1464251..a3d2c5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,44 @@ # Changelog - + ## Unreleased -- Require PHP ^8.0 -- Update to slevomat/coding-standard ^8.0 -- Update to squizlabs/php_codesniffer ^3.9 -- Update to symplify/easy-coding-standard ^12.1 -- Move coding standard declarations from `ecs-7.4.php` and `ecs-8.0.php` to `ecs.php` and remove the former files -- Change deprecated rules to new ones -- Add new `ecs-8.2.php` coding standard declaration file for PHP 8.2+ -- Add new `ecs-8.3.php` coding standard declaration file for PHP 8.3+ + +## 5.0.0 - TBD +- **BC break:** Change namespace from `Lmc\CodingStandard` to `AlmaOss\CodingStandard`. See [UPGRADE-5.0.md](UPGRADE-5.0.md) for step-by-step upgrade howto. +- **BC break:** Remove skip of `BlankLineAfterOpeningTagFixer` to be PSR-12 compliant. A blank line after the opening PHP tag is now required. + +## 4.2.0 - 2025-02-28 +- Rename the package from "Alma Career Czechia Standard for PHP" to more broad "Alma Career Coding Standard for PHP". 🎉 +- Change composer package name to `almacareer/coding-standard`. Previous `lmc/coding-standard` is now deprecated. +- Move GitHub repository from `lmc-eu/php-coding-standard` to `alma-oss/php-coding-standard`. +- Update PHP CS Fixer to fixed version. +- Add `SetList::ALMACAREER` for easier importing of the coding standard. + +## 4.1.2 - 2024-12-27 +- Restrict php-cs-fixer version 3.65.0 (included in ECS version 12.4+) from being installed because of [bug in `NullableTypeDeclarationFixer`](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/8330). + +## 4.1.1 - 2024-06-10 +- Disable incorrectly behaving PhpdocAlignFixer. + +## 4.1.0 - 2024-05-27 +- Add most of the available checks for [PER-2](https://www.php-fig.org/per/coding-style/) coding style. +- Add many new checks from `php-cs-fixer` up to version 3.56. +- Add available php-cs-fixer rules for PHP 8.1 and 8.2 (which are automatically run only on compatible PHP versions). + +## 4.0.0 - 2024-05-27 +- BC: Update to symplify/easy-coding-standard ^12.1 and change configuration format in `ecs.php`. See [UPGRADE-4.0.md](UPGRADE-4.0.md) for step-by-step upgrade howto. +- BC: Move coding standard declarations from `ecs-7.4.php`, `ecs-8.0.php` and `ecs-8.1.php` to `ecs.php` and remove the former files. +- BC: Change base coding standard from [PSR-2](https://www.php-fig.org/psr/psr-2/) to [PSR-12](https://www.php-fig.org/psr/psr-12/). +- BC: Change deprecated `php-cs-fixer` sniffs to new ones. +- Rebrand to Alma Career in documentation and meta information. +- Require PHP ^8.0. +- Update to slevomat/coding-standard ^8.0. +- Update to squizlabs/php_codesniffer ^3.9. +- Refactor: Remove nette/utils dependency. +- Add tests to ensure the code style defined by this library is being properly checked and fixed. ## 3.3.1 - 2022-05-23 - Lock `symplify/easy-coding-standard` to <10.2.4 because of backward incompatibilities introduced in its bugfix releases. @@ -72,7 +98,7 @@ - Temporarily disable `ArrayDeclarationSniff.ValueNoNewline` because of [bug](https://github.com/squizlabs/PHP_CodeSniffer/issues/2937) in PHP_CodeSniffer 3.5.5. ## 2.0.0 - 2020-03-02 -- BC: change the way the standard is imported to your `easy-coding-standard.yaml` (change `%vendor_dir%` placeholder directly to name of the vendor directory like `vendor`). See example in [README](https://github.com/lmc-eu/php-coding-standard#usage). +- BC: change the way the standard is imported to your `easy-coding-standard.yaml` (change `%vendor_dir%` placeholder directly to name of the vendor directory like `vendor`). See example in [README](https://github.com/alma-oss/php-coding-standard#usage). - Drop PHP 7.1 support. - Require EasyCodingStandard 7+. - `VisibilityRequiredFixer` now check visibility is declared also on class constants. diff --git a/LICENCE.md b/LICENSE.md similarity index 96% rename from LICENCE.md rename to LICENSE.md index 4b0a84d..a4ecd69 100644 --- a/LICENCE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ ### The MIT License (MIT) ### -Copyright (c) 2018 LMC s.r.o. +Copyright (c) Alma Career Czechia s.r.o. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e44e6b7..f7d0c3c 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,75 @@ -# LMC Coding Standard for PHP +# Alma Career Coding Standard for PHP -[![Latest Stable Version](https://img.shields.io/packagist/v/lmc/coding-standard.svg?style=flat-square)](https://packagist.org/packages/lmc/coding-standard) +[![Latest Stable Version](https://img.shields.io/packagist/v/almacareer/coding-standard.svg?style=flat-square)](https://packagist.org/packages/almacareer/coding-standard) -PHP coding standard used in [LMC](https://www.lmc.eu/en/) projects. +PHP coding standard used in [Alma Career][Alma Career] (formerly LMC) products. -Standard is based on [PSR-12](https://www.php-fig.org/psr/psr-12/) and adds -various checks to make sure the code is readable, does follow the same conventions and does not contain common mistakes. +The standard is based on [PSR-12][psr-12] and partially [PER 2.0][per-2] and adds +various checks to make sure the code is readable, follows the same conventions, and does not contain common mistakes. -We use [EasyCodingStandard] to define and execute checks created for both [PHP-CS-Fixer] and [PHP_CodeSniffer]. +We use [EasyCodingStandard][ecs] to define and execute checks created for both [PHP-CS-Fixer] and [PHP_CodeSniffer]. + +## Switching from lmc/coding-standard + +The package `almacareer/coding-standard` is 1:1 replacement for the previous deprecated `lmc/coding-standard` package. + +⚠️ **Note:** Starting from version 5.0.0, the namespace has changed from `Lmc\CodingStandard` to `AlmaOss\CodingStandard`. See [UPGRADE-5.0.md](UPGRADE-5.0.md) for detailed upgrade instructions. + +To change the package, you only need to do the following changes in your project: + +### 1. Update dependency in composer.json +```diff +- "lmc/coding-standard": "^4.1", ++ "almacareer/coding-standard": "^5.0", +``` + +And then run `composer update`. + +### 2. Change path to ecs.php in your ecs.php + +You can also use `SetList::ALMACAREER` instead of explicitly specifying path to the file: + +```diff + ->withSets( + [ +- __DIR__ . '/vendor/lmc-eu/coding-standard/ecs.php', ++ \AlmaOss\CodingStandard\Set\SetList::ALMACAREER, + ] + ); +``` ## Installation ```bash -composer require --dev lmc/coding-standard +composer require --dev almacareer/coding-standard ``` ## Usage -1. Create `ecs.php` file in the root directory of your project and import the LMC code-style rules: +1. Create `ecs.php` file in the root directory of your project and import the code-style rules: ```php -withPaths([__DIR__ . '/src', __DIR__ . '/tests']) // optionally add 'config' or other directories with PHP files + ->withRootFiles() // to also check ecs.php and all other php files in the root directory ->withSets( [ - __DIR__ . '/vendor/lmc/coding-standard/ecs.php', + SetList::ALMACAREER, ] ); - - // Be default only checks compatible with PHP 8.0 are enabled. - // Depending on the lowest PHP version your project need to support, you can enable additional checks for - // PHP 8.1, 8.2 and 8.3. - - - // Import one of ecs-8.1.php, ecs-8.2.php or ecs-8.3.php. Use only one file (for the highest possible PHP version). - //->withSets( - // [ - // __DIR__ . '/vendor/lmc/coding-standard/ecs.php', - // __DIR__ . '/vendor/lmc/coding-standard/ecs-8.3.php', - // ] - //); ``` -2. Run the check command (for `src/` and `tests/` directories): +2. Run the check command ```bash -vendor/bin/ecs check src/ tests/ +vendor/bin/ecs check ``` 3. Optionally we recommend adding this to `scripts` section of your `composer.json`: @@ -56,12 +77,12 @@ vendor/bin/ecs check src/ tests/ ```json "scripts": { "analyze": [ - "vendor/bin/ecs check src/ tests/ --ansi", + "vendor/bin/ecs check --ansi", "[... other scripts, like PHPStan etc.]" ], "fix": [ "...", - "vendor/bin/ecs check ./src/ ./tests/ --ansi --fix" + "vendor/bin/ecs check --ansi --fix" ], } ``` @@ -70,76 +91,97 @@ Now you will be able to run the fix using `composer analyze` and execute automat ### Add custom checks or override default settings -On top of default code-style rules you are free to add any rules from [PHP-CS-Fixer] or [PHP_CodeSniffer]. -If needed, you can also override some default settings. +On top of the default code-style rules, you are free to add any rules from [PHP-CS-Fixer] or [PHP_CodeSniffer]. +If needed, you can also override any default settings. -Be aware you must add these settings **after** import of the base LMC code-style: +Below find examples of some more opinionated checks you may want to add depending on your needs: ```php -withSets( [ - __DIR__ . '/vendor/lmc/coding-standard/ecs.php', + SetList::ALMACAREER, + ] + ) + ->withRules( + [ + // PHPUnit attributes must be used over their respective PHPDoc-based annotations. (Use with PHPUnit 10+.) + PhpUnitAttributesFixer::class, + // Single-line comments must have proper spacing (one space after `//`). + SingleLineCommentSpacingFixer::class, + // Convert multiline string to heredoc or nowdoc. + MultilineStringToHeredocFixer::class, ] ) - // Enforce line-length to 120 characters + // Enforce line-length to 120 characters. ->withConfiguredRule(LineLengthSniff::class, ['absoluteLineLimit' => 120]) - // Tests must have @test annotation - ->withConfiguredRule(PhpUnitTestAnnotationFixer::class, ['style' => 'annotation']); + // Tests must have @test annotation. + ->withConfiguredRule(PhpUnitTestAnnotationFixer::class, ['style' => 'annotation']) + // Specify elements separation. + ->withConfiguredRule(ClassAttributesSeparationFixer::class, ['elements' => ['const' => 'none', 'method' => 'one', 'property' => 'none']]) + /* (...) */ ``` -See [EasyCodingStandard docs](https://github.com/symplify/easy-coding-standard#configuration) for more configuration options. +See [EasyCodingStandard docs][ecs-docs] for more configuration options. ### Exclude (skip) checks or files -You can configure your `ecs.php` to entirely skip some files, disable specific checks of suppress specific errors. - -Unlike adding/modifying checks, skips must be added **before** import of the base LMC code-style. +You can configure your `ecs.php` file to entirely skip some files, disable specific checks, or suppress specific errors. ```php -withSkip([ - // Ignore specific check only in specific files - ForbiddenFunctionsSniff::class => [__DIR__ . '/src-tests/bootstrap.php'], - // Disable check entirely - ArrayDeclarationSniff::class, - // Skip one file - __DIR__ . '/file/to/be/skipped.php', - // Skip entire directory - __DIR__ . '/ignored/directory/*', - ]) - ->withSets( + /* (...) */ + ->withSkip( [ - __DIR__ . '/vendor/lmc/coding-standard/ecs.php', + // Ignore specific check only in specific files + ForbiddenFunctionsSniff::class => [__DIR__ . '/src-tests/bootstrap.php'], + // Disable check entirely + ArrayDeclarationSniff::class, + // Skip one file + __DIR__ . '/file/to/be/skipped.php', + // Skip entire directory + __DIR__ . '/ignored/directory/*', ] - ); + ) + /* (...) */ ``` -See [EasyCodingStandard docs](https://github.com/symplify/easy-coding-standard#configuration) for more configuration options. +See [EasyCodingStandard docs][ecs-docs] for more configuration options. ### IDE integration -For integration with PHPStorm etc. follow instructions in EasyCodingStandard [README](https://github.com/symplify/easy-coding-standard#your-ide-integration). +For integration with PHPStorm and other IDEs, follow instructions in [EasyCodingStandard README][ecs-readme-ide]. ## Changelog -For latest changes see [CHANGELOG.md](CHANGELOG.md) file. We follow [Semantic Versioning](https://semver.org/). +For the latest changes, see [CHANGELOG.md](CHANGELOG.md) file. This library follows [Semantic Versioning](https://semver.org/). ## License -This library is open source software licensed under the [MIT license](LICENCE.md). +This library is open-source software licensed under the [MIT license](LICENSE.md). +[Alma Career]: https://www.almacareer.com/ [PHP-CS-Fixer]: https://github.com/FriendsOfPHP/PHP-CS-Fixer [PHP_CodeSniffer]: https://github.com/squizlabs/PHP_CodeSniffer -[EasyCodingStandard]: https://github.com/symplify/easy-coding-standard +[psr-12]: https://www.php-fig.org/psr/psr-12/ +[per-2]: https://www.php-fig.org/per/coding-style/ +[ecs]: https://github.com/easy-coding-standard/easy-coding-standard +[ecs-docs]: https://github.com/easy-coding-standard/easy-coding-standard#configure +[ecs-readme-ide]: https://github.com/easy-coding-standard/easy-coding-standard/blob/9.0.0/README.md#your-ide-integration diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 731b381..aa0d0b2 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -1,55 +1,122 @@ # Upgrading from 3.x to 4.0 ### 1. Update dependency in composer.json -In require-dev section change the version constraint: +In the `require-dev` section of `composer.json` change the version constraint. This also includes +renaming of the package to `almacareer/coding-standard`: ```diff - "lmc/coding-standard": "^3.3", -+ "lmc/coding-standard": "^4.0", ++ "almacareer/coding-standard": "^4.0", ``` Then run `composer update`. ### 2. Configuration updates +The configuration now uses `ECSConfig` class instead of `ContainerConfigurator`. -Configuration now uses ECSConfig class instead of ContainerConfigurator. Update your `ecs.php` to use the new configuration style: +Update your `ecs.php` file to use the new configuration style: ```diff ++use Lmc\CodingStandard\Set\SetList; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -- --return static function (ContainerConfigurator $containerConfigurator): void { +use Symplify\EasyCodingStandard\Config\ECSConfig; -+ + +-return static function (ContainerConfigurator $containerConfigurator): void { +return ECSConfig::configure() ++ ->withSets([ ++ SetList::ALMACAREER, ++ ]); + // ... ``` -Rules are now set using `ECSConfig::configure()->withRules([])` or `ECSConfig::configure()->withConfiguredRule()` instead of `$services->set()`. -Skiping tests is now done using `ECSConfig::configure()->withSkip()` instead of `$parameters->set(Option::SKIP, ...)`. -Imports are now done using `ECSConfig::configure()->withSets()` instead of `$containerConfigurator->import()`. +Now change the way you set rules, skip tests and import sets: -See [ECS documentation](https://github.com/easy-coding-standard/easy-coding-standard/tree/main?tab=readme-ov-file#configure) for more configuration options -Examples of configurations can be seen [here](https://tomasvotruba.com/blog/new-in-ecs-simpler-config) +| Old Method | New Method | +|---------------------------------------|-------------------------------------------------------------------------------------------| +| `$services->set()` | `ECSConfig::configure()->withRules([])` or `ECSConfig::configure()->withConfiguredRule()` | +| `$parameters->set(Option::SKIP, ...)` | `ECSConfig::configure()->withSkip()` | +| `$containerConfigurator->import()` | `ECSConfig::configure()->withSets()` | -### 3. Remove imports of `ecs-7.4.php` and/or `ecs-8.0.php` from your `ecs.php` +See [examples in Usage section of our README](https://github.com/alma-oss/php-coding-standard?tab=readme-ov-file#usage) +or more configuration options in [ECS documentation](https://github.com/easy-coding-standard/easy-coding-standard/tree/main?tab=readme-ov-file#configure). + +Some more reasoning and examples of configurations can also be seen [in ECS author blogpost](https://tomasvotruba.com/blog/new-in-ecs-simpler-config). + +### 3. Remove imports of `ecs-7.4.php`, `ecs-8.0.php` or `ecs-8.1.php` from your `ecs.php` ```diff ->withSets(__DIR__ . '/vendor/lmc/coding-standard/ecs.php') - ->withSets(__DIR__ . '/vendor/lmc/coding-standard/ecs-7.4.php') - ->withSets(__DIR__ . '/vendor/lmc/coding-standard/ecs-8.0.php') - ->withSets(__DIR__ . '/vendor/lmc/coding-standard/ecs-8.1.php') +- ->withSets(__DIR__ . '/vendor/lmc/coding-standard/ecs-8.1.php') ``` -### 4. Sanity check -Besides running your code style checks, you can ensure all predefined LMC checks are loaded as well, by running: +All the rules are now included in the main `ecs.php` file. Only rules compatible with your PHP version are applied. + +### 4. Configure paths directly in ecs.php + +Paths definition could now be included directly in `ecs.php` instead of repeating them on command line. + +In `ecs.php`: +```php +// ... +return ECSConfig::configure() + ->withPaths([__DIR__ . '/src', __DIR__ . '/tests']) // optionally add 'config' or other directories with PHP files + ->withRootFiles() // to include ecs.php and all other php files in the root directory +// ... +``` + +Now you can remove the explicit paths definition from `composer.json`: +```diff +{ + "scripts": { + "analyze": [ +- "vendor/bin/ecs check --ansi src/ tests/" ++ "vendor/bin/ecs check --ansi" + ], + "fix": [ +- "vendor/bin/ecs check --ansi --fix src/ tests/" ++ "vendor/bin/ecs check --ansi --fix" + ] + } +} +``` + +Or run directly from command line without a need of specifying them: +```bash +$ vendor/bin/ecs check --ansi src/ tests/ # old +$ vendor/bin/ecs check --ansi # new +``` + +### 6. Add some optional rules +On top of default rules included in ecs.php, there are some more opinionated ones you may want to add. + +These suggested rules are listed in [README.md](https://github.com/alma-oss/php-coding-standard?tab=readme-ov-file#add-custom-checks-or-override-default-settings). + +### 5. BE CAREFUL WITH SUGGESTED CHANGES! ⚠️ + +Some of the new default fixers introduced in php-coding-standard 4.0 and 4.1 suggest changes, which - if not +thoughtfully reviewed - can change the code behavior. Especially changes introduced by (but not limited to!): + +- PhpdocToPropertyTypeFixer + PropertyTypeHintSniff +- PhpdocToParamTypeFixer + ParameterTypeHintSniff +- PhpdocToReturnTypeFixer + ReturnTypeHintSniff + +**Always carefully review the changes suggested by all fixers!** You may want to skip some of the checks +(using `withSkip()`) in the first phase of upgrading to the new version of the coding standard +or you can introduce some of the rules gradually or on a file-by-file basis. + +### 6. Sanity check +Besides running your code style checks, you can ensure all predefined checks are loaded by running: ```sh vendor/bin/ecs list-checkers ``` -The result should end with something like: +With `php-coding-standard` 4.1, the result should end with something like this: ``` - 41 checkers from PHP_CodeSniffer: + 40 checkers from PHP_CodeSniffer: ... - 147 checkers from PHP-CS-Fixer: + 171 checkers from PHP-CS-Fixer: ... 2 checkers are skipped: ... diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md new file mode 100644 index 0000000..292c7c0 --- /dev/null +++ b/UPGRADE-5.0.md @@ -0,0 +1,99 @@ +# Upgrading from 4.x to 5.0 + +### 1. Update dependency in composer.json +In the `require-dev` section of `composer.json` change the version constraint: + +```diff +- "almacareer/coding-standard": "^4.2", ++ "almacareer/coding-standard": "^5.0", +``` + +Then run `composer update almacareer/coding-standard`. + +### 2. Update namespace imports + +**[BC break]** The namespace has been changed from `Lmc\CodingStandard` to `AlmaOss\CodingStandard`. + +Update your `ecs.php` file to use the new namespace: + +```diff +withSets( + [ + SetList::ALMACAREER, + ] + ); +``` + +If you are using any custom sniffs or fixers from this package in your configuration, update their namespace as well: + +```diff +-use Lmc\CodingStandard\Fixer\SpecifyArgSeparatorFixer; ++use AlmaOss\CodingStandard\Fixer\SpecifyArgSeparatorFixer; + +-use Lmc\CodingStandard\Sniffs\Naming\ClassNameSuffixByParentSniff; ++use AlmaOss\CodingStandard\Sniffs\Naming\ClassNameSuffixByParentSniff; +``` + +### 3. PSR-12 compliance change + +**[BC break]** The `BlankLineAfterOpeningTagFixer` is no longer skipped. This means that a blank line after the opening PHP tag (`withSkip([ + BlankLineAfterOpeningTagFixer::class, + ]); +``` + +### 4. Run the code style checks + +After updating the namespace, run the code style checks to ensure everything is working correctly: + +```bash +vendor/bin/ecs check +``` + +To automatically fix the code style issues (including the blank line after opening tag): + +```bash +vendor/bin/ecs check --fix +``` + +### 5. Sanity check + +You can ensure all predefined checks are loaded by running: + +```sh +vendor/bin/ecs list-checkers +``` + +The output should show all the loaded checkers from both PHP-CS-Fixer and PHP_CodeSniffer. diff --git a/composer.json b/composer.json index 4e8724a..81eea79 100644 --- a/composer.json +++ b/composer.json @@ -1,40 +1,42 @@ { - "name": "lmc/coding-standard", - "description": "Coding standard used in LMC projects", + "name": "almacareer/coding-standard", + "description": "Coding standard used in Alma Career projects", "license": "MIT", "type": "library", "authors": [ { - "name": "LMC s.r.o.", - "homepage": "https://github.com/lmc-eu" + "name": "Alma Career", + "homepage": "https://github.com/alma-oss/" } ], "require": { "php": "^8.0", - "friendsofphp/php-cs-fixer": "^3.0", - "nette/utils": "^3.2", - "slevomat/coding-standard": "^8.0", + "friendsofphp/php-cs-fixer": "^3.66", + "slevomat/coding-standard": "^8.6", "squizlabs/php_codesniffer": "^3.9", - "symplify/easy-coding-standard": "^12.1.4" + "symplify/easy-coding-standard": "^12.2.0 <12.4 || ^12.5.5" }, "require-dev": { - "ergebnis/composer-normalize": "^2.13.2", - "nikic/php-parser": "<5.0", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.3.0", - "phpstan/phpstan-phpunit": "^1.0.0", + "ergebnis/composer-normalize": "^2.42", + "nikic/php-parser": "^4.19", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.11.1", + "phpstan/phpstan-phpunit": "^1.4", "phpunit/phpunit": "^9.6.19" }, "prefer-stable": true, "autoload": { "psr-4": { - "Lmc\\CodingStandard\\": "src/" - } + "AlmaOss\\CodingStandard\\": "src/" + }, + "files": [ + "src/polyfill.php" + ] }, "autoload-dev": { "psr-4": { - "Lmc\\CodingStandard\\": "tests/" + "AlmaOss\\CodingStandard\\": "tests/" } }, "config": { @@ -55,15 +57,15 @@ "@test" ], "analyze": [ - "vendor/bin/ecs check src/ tests/ ecs.php ecs-8.1.php ecs-8.2.php ecs-8.3.php --ansi", + "vendor/bin/ecs check --config=ecs-internal.php --ansi", "vendor/bin/phpstan analyze -c phpstan.neon --ansi" ], "fix": [ "@composer normalize", - "vendor/bin/ecs check ./src/ ./tests/ ecs.php ecs-8.1.php ecs-8.2.php ecs-8.3.php --ansi --fix" + "vendor/bin/ecs check --config=ecs-internal.php --ansi --fix" ], "lint": [ - "vendor/bin/parallel-lint -j 10 -e php ./src ./tests ecs.php ecs-8.1.php ecs-8.2.php ecs-8.3.php", + "vendor/bin/parallel-lint -j 10 -e php ./src ./tests ecs.php", "@composer validate", "@composer normalize --dry-run" ], diff --git a/ecs-8.1.php b/ecs-8.1.php deleted file mode 100644 index f5adedb..0000000 --- a/ecs-8.1.php +++ /dev/null @@ -1,5 +0,0 @@ -withSets([__DIR__ . '/ecs-8.1.php']); diff --git a/ecs-8.3.php b/ecs-8.3.php deleted file mode 100644 index 3c0ad94..0000000 --- a/ecs-8.3.php +++ /dev/null @@ -1,6 +0,0 @@ -withSets([__DIR__ . '/ecs-8.2.php']); diff --git a/ecs-internal.php b/ecs-internal.php new file mode 100644 index 0000000..ed7b774 --- /dev/null +++ b/ecs-internal.php @@ -0,0 +1,36 @@ +withPaths([__DIR__ . '/src', __DIR__ . '/tests']) + ->withRootFiles() + ->withSets( + [ + SetList::ALMACAREER, + ], + ) + ->withConfiguredRule(PhpUnitTestAnnotationFixer::class, ['style' => 'prefix']) + ->withConfiguredRule( + LineLengthFixer::class, + ['line_length' => 120, 'break_long_lines' => true, 'inline_short_lines' => false], + ) + ->withConfiguredRule( + ClassAttributesSeparationFixer::class, + ['elements' => ['const' => 'none', 'method' => 'one', 'property' => 'none']], + ) + ->withSkip( + [ + ForbiddenFunctionsSniff::class => ['tests/Integration/CodingStandardTest.php'], + ], + ); diff --git a/ecs.php b/ecs.php index 9c8fb3c..56590bc 100644 --- a/ecs.php +++ b/ecs.php @@ -1,10 +1,12 @@ -` and `?->`. ObjectOperatorWithoutWhitespaceFixer::class, - + // Replace all `<>` with `!=`. StandardizeNotEqualsFixer::class, // Standardize spaces around ternary operator. TernaryOperatorSpacesFixer::class, @@ -305,11 +347,11 @@ TernaryToElvisOperatorFixer::class, // Use `null` coalescing operator `??` where possible. TernaryToNullCoalescingFixer::class, - + // Unary operators should be placed adjacent (without a space) to their operands. UnaryOperatorSpacesFixer::class, - + // There should not be blank lines between docblock and the documented element. NoBlankLinesAfterPhpdocFixer::class, - + // There should not be empty PHPDoc blocks. NoEmptyPhpdocFixer::class, // PHPDoc should contain `@param` for all params. PhpdocAddMissingParamAnnotationFixer::class, @@ -321,16 +363,18 @@ PhpdocNoEmptyReturnFixer::class, // `@package` and `@subpackage` annotations should be omitted from PHPDoc. PhpdocNoPackageFixer::class, - // Annotations in PHPDoc should be ordered. - PhpdocOrderFixer::class, // The type of `@return` annotations of methods returning a reference to itself must the configured one. PhpdocReturnSelfReferenceFixer::class, // Scalar types should always be written in the same form. PhpdocScalarFixer::class, // Single line `@var` PHPDoc should have proper spacing. PhpdocSingleLineVarSpacingFixer::class, + // Removes extra blank lines after summary and after description in PHPDoc. + PhpdocTrimConsecutiveBlankLineSeparationFixer::class, // PHPDoc should start and end with content PhpdocTrimFixer::class, + // Docblocks should only be used on structural elements. + PhpdocToCommentFixer::class, // The correct case must be used for standard PHP types in PHPDoc. PhpdocTypesFixer::class, // `@var` and `@type` annotations must have type and name in the correct order @@ -351,6 +395,8 @@ PhpUnitNoExpectationAnnotationFixer::class, // Usages of ->setExpectedException* methods MUST be replaced by ->expectException* methods PhpUnitExpectationFixer::class, + // PHPUnit annotations should be a FQCNs including a root namespace. + PhpUnitFqcnAnnotationFixer::class, // Visibility of setUp() and tearDown() method should be kept protected PhpUnitSetUpTearDownVisibilityFixer::class, // There should not be an empty `return` statement at the end of a function @@ -365,8 +411,14 @@ DeclareStrictTypesFixer::class, // Functions should be used with `$strict` param set to `true` StrictParamFixer::class, + // Comparisons should be strict, `===` or `!==` must be used for comparisons + StrictComparisonFixer::class, + // Converts explicit variables in double-quoted strings from simple to complex format (${ to {$). + SimpleToComplexStringVariableFixer::class, // Convert double quotes to single quotes for simple strings SingleQuoteFixer::class, + // Each element of an array must be indented exactly once. + ArrayIndentationFixer::class, // Remove extra spaces in a nullable typehint CompactNullableTypeDeclarationFixer::class, // Heredoc/nowdoc content must be properly indented. @@ -377,32 +429,22 @@ ReferenceThrowableOnlySniff::class, // The @param, @return, @var and inline @var annotations should keep standard format ParamReturnAndVarTagMalformsFixer::class, - // Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. + // Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature to a native PHP 7.4+ type-hint. PhpdocToPropertyTypeFixer::class, - // Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. + PropertyTypeHintSniff::class, + // Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature to a native type-hints. PhpdocToParamTypeFixer::class, + ParameterTypeHintSniff::class, // Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. PhpdocToReturnTypeFixer::class, + ReturnTypeHintSniff::class, + // Requires that only one attribute can be placed inside #[]. + DisallowAttributesJoiningSniff::class, + // Disallows multiple attributes of some target on same line. + DisallowMultipleAttributesPerLineSniff::class, // Promote constructor properties - // @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/5956 + // For php-cs-fixer implementation @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/5956 RequireConstructorPropertyPromotionSniff::class, - - // switch -> match - // @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/5894 - - // Require \Stringable interface in classes implementing __toString() method - // > it may probably be a phpstan rule, more than cs rule - since it needs a class hierarchy to solve this - // @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/6235 - PropertyTypeHintSniff::class, - - ParameterTypeHintSniff::class, - ReturnTypeHintSniff::class, - - // Multi-line arguments list in function/method declaration must have a trailing comma - RequireTrailingCommaInDeclarationSniff::class, - // Multi-line arguments list in function/method call must have a trailing comma - RequireTrailingCommaInCallSniff::class, - // Use `null-safe` operator `?->` where possible RequireNullSafeObjectOperatorSniff::class, ], @@ -453,6 +495,8 @@ 'var_dump' => null, ], ]) + // Master functions shall be used instead of aliases + ->withConfiguredRule(NoAliasFunctionsFixer::class, ['sets' => ['@all']]) // There should be exactly one blank line before a namespace declaration. ->withConfiguredRule(BlankLinesBeforeNamespaceFixer::class, ['min_line_breaks' => 2, 'max_line_breaks' => 2]) // Proper operator spacing @@ -461,7 +505,10 @@ ->withConfiguredRule(ArraySyntaxFixer::class, ['syntax' => 'short']) // The body of each structure MUST be enclosed by braces. Braces should be properly placed // @TODO move configuration to BracesPositionFixer after BracesFixer is not included in PSR-12 check anymore - ->withConfiguredRule(BracesFixer::class, ['allow_single_line_closure' => true, 'allow_single_line_anonymous_class_with_empty_body' => true]) + ->withConfiguredRule( + BracesFixer::class, + ['allow_single_line_closure' => true, 'allow_single_line_anonymous_class_with_empty_body' => true], + ) // Class, trait and interface elements must be separated with one or none blank line ->withConfiguredRule(ClassAttributesSeparationFixer::class, ['elements' => ['method' => 'one']]) // Visibility MUST be declared on all properties, methods and class constants @@ -472,12 +519,18 @@ ->withConfiguredRule(ConcatSpaceFixer::class, ['spacing' => 'one']) // Removes `@param` and `@return` tags that don't provide any useful information ->withConfiguredRule(NoSuperfluousPhpdocTagsFixer::class, [ - 'allow_mixed' => true, // allow `@mixed` annotations to be preserved 'allow_unused_params' => false, // whether param annotation without actual signature is allowed 'remove_inheritdoc' => true, // remove @inheritDoc tags ]) + // All items of the given PHPDoc tags must be left-aligned. + // @TODO: Re-enable if https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/8052 is fixed + //->withConfiguredRule(PhpdocAlignFixer::class, ['align' => 'left']) + // Annotations in PHPDoc should be ordered in defined sequence. + ->withConfiguredRule(PhpdocOrderFixer::class, ['order' => ['param', 'return', 'throws']]) // Order phpdoc tags by value. ->withConfiguredRule(PhpdocOrderByValueFixer::class, ['annotations' => ['covers', 'group', 'throws']]) + // Enforce camel case for PHPUnit test methods. + ->withConfiguredRule(PhpUnitMethodCasingFixer::class, ['case' => 'camel_case']) // Calls to `PHPUnit\Framework\TestCase` static methods must all be of the same type (`$this->...`) ->withConfiguredRule(PhpUnitTestCaseStaticMethodCallsFixer::class, ['call_type' => 'this']) // An empty line feed must precede any configured statement @@ -485,24 +538,71 @@ // Removes extra blank lines and/or blank lines following configuration ->withConfiguredRule(NoExtraBlankLinesFixer::class, [ 'tokens' => [ - 'break', + 'attribute', + 'case', 'continue', 'curly_brace_block', + 'default', 'extra', 'parenthesis_brace_block', - 'return', 'square_brace_block', + 'switch', 'throw', 'use', 'use_trait', ], ]) - // Format union types - ->withConfiguredRule(UnionTypeHintFormatSniff::class, ['withSpaces' => 'no']) + // Elements of classes/interfaces/traits/enums should be in the defined order + ->withConfiguredRule( + OrderedClassElementsFixer::class, + [ + 'order' => [ + 'use_trait', + 'case', // enum values should be before other elements + 'constant', + 'property', + 'construct', + 'destruct', + 'magic', + 'phpunit', // phpunit special methods like setUp should be before test methods + 'method', + ], + ], + ) + // Spaces should be properly placed in a function declaration. + ->withConfiguredRule( + FunctionDeclarationFixer::class, + ['closure_fn_spacing' => 'none'], // Defined in PER 2.0 + ) + // Removes unneeded parentheses around specified control statements. + ->withConfiguredRule( + NoUnneededControlParenthesesFixer::class, + ['statements' => ['break', 'clone', 'continue', 'echo_print', 'others', 'switch_case', 'yield', 'yield_from']], + ) + // Multi-line arrays, arguments list and parameters list must have a trailing comma + ->withConfiguredRule( + TrailingCommaInMultilineFixer::class, + ['after_heredoc' => true, 'elements' => ['arguments', 'arrays', 'match', 'parameters']], // Defined in PER 2.0 + ) + // Removes the leading part of FQCN + ->withConfiguredRule( + FullyQualifiedStrictTypesFixer::class, + ['import_symbols' => true], // Also import symbols from other namespaces than in current file + ) + // Spaces and newlines in method arguments and attributes + ->withConfiguredRule( + MethodArgumentSpaceFixer::class, + ['attribute_placement' => 'standalone', 'on_multiline' => 'ensure_fully_multiline'], + ) ->withSkip([ // We allow empty catch statements (but they must have comment - see EmptyCatchCommentSniff) EmptyStatementSniff::class . '.DetectedCatch' => null, + // Skip because of its attempts to add spaces in declare(strict_types=1); starting with ECS 12.2.0 + // @TODO: In future ECS versions try whether its is still broken or this skip could be removed + OperatorSpacingSniff::class . '.NoSpaceAfter' => null, + OperatorSpacingSniff::class . '.NoSpaceBefore' => null, + // Skip unwanted rules from DocCommentSniff DocCommentSniff::class . '.ContentAfterOpen' => null, DocCommentSniff::class . '.ContentBeforeClose' => null, @@ -539,7 +639,4 @@ // Skip unwanted rules from ReturnTypeHintSniff ReturnTypeHintSniff::class . '.' . ReturnTypeHintSniff::CODE_MISSING_TRAVERSABLE_TYPE_HINT_SPECIFICATION => null, ReturnTypeHintSniff::class . '.' . ReturnTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT => null, - - // We use declare(strict_types=1); after opening tag - BlankLineAfterOpeningTagFixer::class => null, ]); diff --git a/phpstan.neon b/phpstan.neon index 42705fa..7f3910e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,3 @@ parameters: bootstrapFiles: - vendor/symplify/easy-coding-standard/vendor/squizlabs/php_codesniffer/autoload.php - vendor/symplify/easy-coding-standard/vendor/squizlabs/php_codesniffer/src/Util/Tokens.php - ignoreErrors: - - message: '#Parameter \#1 \$code of static method PhpCsFixer\\Tokenizer\\Tokens::fromCode\(\) expects string, string\|false given#' - path: %currentWorkingDirectory%/tests/Fixer/SpecifyArgSeparatorFixerTest.php - checkMissingIterableValueType: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3721a05..852863a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,23 +1,17 @@ - - - - ./tests/ - - - - - - ./src - - - - - - - + + + + ./src + + + + + ./tests/ + + + + + + diff --git a/src/Fixer/SpecifyArgSeparatorFixer.php b/src/Fixer/SpecifyArgSeparatorFixer.php index 819b932..ae470a6 100644 --- a/src/Fixer/SpecifyArgSeparatorFixer.php +++ b/src/Fixer/SpecifyArgSeparatorFixer.php @@ -1,6 +1,8 @@ -getFqnClassName($file, $completeClassName, $classNameStartPosition); if ($fqnClassName !== '') { - return ltrim($fqnClassName, self::NAMESPACE_SEPARATOR); + return mb_ltrim($fqnClassName, self::NAMESPACE_SEPARATOR); } return $completeClassName; diff --git a/src/Helper/SniffClassWrapper.php b/src/Helper/SniffClassWrapper.php index 84d05f2..5d87171 100644 --- a/src/Helper/SniffClassWrapper.php +++ b/src/Helper/SniffClassWrapper.php @@ -1,4 +1,6 @@ -resolveExpectedSuffix($parentType); - if (Strings::endsWith($className, $suffix)) { + if (str_ends_with($className, $suffix)) { continue; } @@ -123,12 +118,12 @@ private function getClassToSuffixMap(): array */ private function resolveExpectedSuffix(string $parentType): string { - if (Strings::endsWith($parentType, 'Interface')) { - $parentType = Strings::substring($parentType, 0, -mb_strlen('Interface')); + if (str_ends_with($parentType, 'Interface')) { + $parentType = mb_substr($parentType, 0, -mb_strlen('Interface'), 'UTF-8'); } - if (Strings::startsWith($parentType, 'Abstract')) { - $parentType = Strings::substring($parentType, mb_strlen('Abstract')); + if (str_starts_with($parentType, 'Abstract')) { + $parentType = mb_substr($parentType, mb_strlen('Abstract'), null, 'UTF-8'); } return $parentType; diff --git a/src/Sniffs/Naming/InterfaceNameSniff.php b/src/Sniffs/Naming/InterfaceNameSniff.php index 497db63..28b412f 100644 --- a/src/Sniffs/Naming/InterfaceNameSniff.php +++ b/src/Sniffs/Naming/InterfaceNameSniff.php @@ -1,4 +1,6 @@ -file = $phpcsFile; $this->position = $stackPtr; - if (Strings::endsWith($this->getInterfaceName(), 'Interface')) { + if (str_ends_with($this->getInterfaceName(), 'Interface')) { return; } diff --git a/src/Sniffs/Naming/TraitNameSniff.php b/src/Sniffs/Naming/TraitNameSniff.php index d977e3a..ee5719e 100644 --- a/src/Sniffs/Naming/TraitNameSniff.php +++ b/src/Sniffs/Naming/TraitNameSniff.php @@ -1,4 +1,6 @@ -file = $phpcsFile; $this->position = $stackPtr; - if (Strings::endsWith($this->getTraitName(), 'Trait')) { + if (str_ends_with($this->getTraitName(), 'Trait')) { return; } diff --git a/src/polyfill.php b/src/polyfill.php new file mode 100644 index 0000000..8f18248 --- /dev/null +++ b/src/polyfill.php @@ -0,0 +1,35 @@ +getRealPath())); + $fileContents = file_get_contents($fileInfo->getRealPath()); + if ($fileContents === false) { + $this->fail('Could not read file ' . $fileInfo->getRealPath()); + } + $tokens = Tokens::fromCode($fileContents); $fixer->fix($fileInfo, $tokens); @@ -26,9 +31,9 @@ public function shouldFixCode(string $inputFile, string $expectedOutputFile): vo } /** - * @return array[] + * @return array */ - public function provideFiles(): array + public static function provideFiles(): array { return [ 'Correct file should not be changed' => ['Correct.php.inc', 'Correct.php.inc'], diff --git a/tests/Helper/NamingTest.php b/tests/Helper/NamingTest.php new file mode 100644 index 0000000..1c90b59 --- /dev/null +++ b/tests/Helper/NamingTest.php @@ -0,0 +1,336 @@ +naming = new Naming(); + } + + public function testShouldGetSimpleClassNameWithNamespace(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $classPosition = $this->findTokenPosition($file, T_STRING, 'UserService'); + + $result = $this->naming->getClassName($file, $classPosition); + + $this->assertSame('App\\Service\\UserService', $result); + } + + public function testShouldGetClassNameWithoutNamespace(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $classPosition = $this->findTokenPosition($file, T_STRING, 'SimpleClass'); + + $result = $this->naming->getClassName($file, $classPosition); + + $this->assertSame('SimpleClass', $result); + } + + public function testShouldGetFullyQualifiedClassNameFromExtends(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $parentClassPosition = $this->findTokenPosition($file, T_STRING, 'UserRepository', 2); + + $result = $this->naming->getClassName($file, $parentClassPosition); + + $this->assertSame('App\\Repository\\UserRepository', $result); + } + + public function testShouldGetFullyQualifiedClassNameFromImplements(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $interfacePosition = $this->findTokenPosition($file, T_STRING, 'ServiceInterface', 2); + + $result = $this->naming->getClassName($file, $interfacePosition); + + $this->assertSame('App\\Contract\\ServiceInterface', $result); + } + + public function testShouldCacheReferencedNamesForSameFile(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $position = $this->findTokenPosition($file, T_STRING, 'UserRepository', 2); + + // Call twice to test caching + $result1 = $this->naming->getClassName($file, $position); + $result2 = $this->naming->getClassName($file, $position); + + $this->assertSame($result1, $result2); + $this->assertSame('App\\Repository\\UserRepository', $result1); + } + + public function testShouldHandleNamespaceWithMultipleLevels(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $classPosition = $this->findTokenPosition($file, T_STRING, 'DeepClass'); + + $result = $this->naming->getClassName($file, $classPosition); + + $this->assertSame('Very\\Deep\\Nested\\Namespace\\Structure\\DeepClass', $result); + } + + public function testShouldHandleFullyQualifiedClassReference(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $baseClassPosition = $this->findTokenPosition($file, T_STRING, 'Fully'); + + $result = $this->naming->getClassName($file, $baseClassPosition); + + // When using fully qualified name, it should return the resolved name + $this->assertStringContainsString('Fully', $result); + } + + public function testShouldHandleAbstractClass(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $classPosition = $this->findTokenPosition($file, T_STRING, 'AbstractUserService'); + + $result = $this->naming->getClassName($file, $classPosition); + + $this->assertSame('App\\Service\\AbstractUserService', $result); + } + + public function testShouldHandleClassWithInterface(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $interfacePosition = $this->findTokenPosition($file, T_STRING, 'UserServiceInterface'); + + $result = $this->naming->getClassName($file, $interfacePosition); + + // For interface declarations, it returns just the interface name (not FQN) + $this->assertSame('UserServiceInterface', $result); + } + + public function testShouldHandleMultipleClassesInFile(): void + { + $code = <<<'PHP' + createFileFromCode($code); + + $firstPosition = $this->findTokenPosition($file, T_STRING, 'FirstClass'); + $secondPosition = $this->findTokenPosition($file, T_STRING, 'SecondClass'); + + $result1 = $this->naming->getClassName($file, $firstPosition); + $result2 = $this->naming->getClassName($file, $secondPosition); + + $this->assertSame('App\\Service\\FirstClass', $result1); + $this->assertSame('App\\Service\\SecondClass', $result2); + } + + public function testShouldHandleAliasedImport(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $parentPosition = $this->findTokenPosition($file, T_STRING, 'UserRepo', 2); + + $result = $this->naming->getClassName($file, $parentPosition); + + // Should resolve the aliased import + $this->assertSame('App\\Repository\\UserRepository', $result); + } + + public function testShouldReturnClassNameForReferencedClassNotInUseStatements(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $parentPosition = $this->findTokenPosition($file, T_STRING, 'SomeClass'); + + $result = $this->naming->getClassName($file, $parentPosition); + + // When class is not in use statements, it resolves relative to current namespace + $this->assertSame('App\\Service\\SomeClass', $result); + } + + public function testShouldHandleLeadingBackslashInResolvedName(): void + { + $code = <<<'PHP' + createFileFromCode($code); + $interfacePosition = $this->findTokenPosition($file, T_STRING, 'ServiceInterface', 2); + + $result = $this->naming->getClassName($file, $interfacePosition); + + // Should not start with backslash (mb_ltrim is used) + $this->assertStringStartsNotWith('\\', $result); + $this->assertSame('App\\Contract\\ServiceInterface', $result); + } + + /** + * Create a File object from PHP code string + */ + private function createFileFromCode(string $code): LocalFile + { + $tempFile = tempnam(sys_get_temp_dir(), 'phpcs_test_'); + if ($tempFile === false) { + $this->fail('Could not create temporary file'); + } + file_put_contents($tempFile, $code); + + $config = new Config(); + $ruleset = new Ruleset($config); + + $file = new LocalFile($tempFile, $ruleset, $config); + $file->parse(); + + return $file; + } + + /** + * Find the position of a token with specific content + */ + private function findTokenPosition(LocalFile $file, int $tokenType, string $content, int $occurrence = 1): int + { + $tokens = $file->getTokens(); + $found = 0; + + foreach ($tokens as $position => $token) { + if ($token['code'] === $tokenType && $token['content'] === $content) { + $found++; + if ($found === $occurrence) { + return $position; + } + } + } + + $this->fail("Could not find token {$content} (occurrence {$occurrence})"); + } +} diff --git a/tests/Helper/PolyfillTest.php b/tests/Helper/PolyfillTest.php new file mode 100644 index 0000000..0977e45 --- /dev/null +++ b/tests/Helper/PolyfillTest.php @@ -0,0 +1,178 @@ +assertSame($expected, $result); + } + + public function testShouldTrimMultipleLeadingBackslashes(): void + { + $input = '\\\\\\Foo\\Bar'; + $expected = 'Foo\\Bar'; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldNotTrimTrailingBackslashes(): void + { + $input = 'Foo\\Bar\\'; + $expected = 'Foo\\Bar\\'; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldNotTrimMiddleBackslashes(): void + { + $input = 'Foo\\Bar\\Baz'; + $expected = 'Foo\\Bar\\Baz'; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldReturnUnchangedStringWithoutLeadingCharacters(): void + { + $input = 'FooBarBaz'; + $expected = 'FooBarBaz'; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldTrimDefaultWhitespaceCharacters(): void + { + $input = " \t\n\r\v\0Hello World"; + $expected = 'Hello World'; + + $result = mb_ltrim($input); + + $this->assertSame($expected, $result); + } + + public function testShouldTrimCustomCharacters(): void + { + $input = 'aaabbbcccHello'; + $expected = 'Hello'; + + $result = mb_ltrim($input, 'abc'); + + $this->assertSame($expected, $result); + } + + public function testShouldTrimMultipleWhitespaceTypes(): void + { + $input = " \t\n Text"; + $expected = 'Text'; + + $result = mb_ltrim($input); + + $this->assertSame($expected, $result); + } + + public function testShouldHandleEmptyString(): void + { + $input = ''; + $expected = ''; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldHandleStringWithOnlyTrimmedCharacters(): void + { + $input = '\\\\\\'; + $expected = ''; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldHandleSpecialRegexCharacters(): void + { + $input = '...+++***Text'; + $expected = 'Text'; + + $result = mb_ltrim($input, '.+*'); + + $this->assertSame($expected, $result); + } + + public function testShouldHandleMultibyteCharacters(): void + { + $input = '€€€€Hello'; + $expected = 'Hello'; + + $result = mb_ltrim($input, '€'); + + $this->assertSame($expected, $result); + } + + public function testShouldHandleUnicodeCharacters(): void + { + $input = '👋👋👋Hello'; + $expected = 'Hello'; + + $result = mb_ltrim($input, '👋'); + + $this->assertSame($expected, $result); + } + + public function testShouldTrimMixedCharacters(): void + { + $input = ' \\ \\ Text'; + $expected = 'Text'; + + $result = mb_ltrim($input, ' \\'); + + $this->assertSame($expected, $result); + } + + public function testShouldWorkWithSingleCharacter(): void + { + $input = '\\'; + $expected = ''; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + } + + public function testShouldPreserveInternalStructure(): void + { + $input = '\\Namespace\\SubNamespace\\Class'; + $expected = 'Namespace\\SubNamespace\\Class'; + + $result = mb_ltrim($input, '\\'); + + $this->assertSame($expected, $result); + $this->assertStringContainsString('\\', $result); + $this->assertStringStartsNotWith('\\', $result); + } +} diff --git a/tests/Integration/CodingStandardTest.php b/tests/Integration/CodingStandardTest.php new file mode 100644 index 0000000..4552bbd --- /dev/null +++ b/tests/Integration/CodingStandardTest.php @@ -0,0 +1,117 @@ +tempFixtureFile); + } + + /** + * @dataProvider provideFilesToFix + */ + public function testShouldFixFile(string $wrongFile, string $correctFile): void + { + $fixedFile = $this->runEcsCheckOnFile($wrongFile); + $fileContents = file_get_contents($fixedFile); + if ($fileContents === false) { + $this->fail('Could not read file ' . $fixedFile); + } + + $this->assertStringEqualsFile($correctFile, $fileContents); + } + + /** + * @return array + */ + public static function provideFilesToFix(): array + { + return [ + 'Basic' => [__DIR__ . '/Fixtures/Basic.wrong.php.inc', __DIR__ . '/Fixtures/Basic.correct.php.inc'], + 'NewPhpFeatures' => [ + __DIR__ . '/Fixtures/NewPhpFeatures.wrong.php.inc', + __DIR__ . '/Fixtures/NewPhpFeatures.correct.php.inc', + ], + 'PhpDoc' => [__DIR__ . '/Fixtures/PhpDoc.wrong.php.inc', __DIR__ . '/Fixtures/PhpDoc.correct.php.inc'], + 'PhpUnit' => [__DIR__ . '/Fixtures/PhpUnit.wrong.php.inc', __DIR__ . '/Fixtures/PhpUnit.correct.php.inc'], + ]; + } + + /** + * @requires PHP >= 8.1 + */ + public function testShouldFixPhp81(): void + { + $fixedFile = $this->runEcsCheckOnFile(__DIR__ . '/Fixtures/Php81.wrong.php.inc'); + $fileContents = file_get_contents($fixedFile); + if ($fileContents === false) { + $this->fail('Could not read file ' . $fixedFile); + } + + $this->assertStringEqualsFile( + __DIR__ . '/Fixtures/Php81.correct.php.inc', + $fileContents, + ); + } + + /** + * @requires PHP >= 8.2 + */ + public function testShouldFixPhp82(): void + { + $fixedFile = $this->runEcsCheckOnFile(__DIR__ . '/Fixtures/Php82.wrong.php.inc'); + $fileContents = file_get_contents($fixedFile); + if ($fileContents === false) { + $this->fail('Could not read file ' . $fixedFile); + } + + $this->assertStringEqualsFile( + __DIR__ . '/Fixtures/Php82.correct.php.inc', + $fileContents, + ); + } + + private function runEcsCheckOnFile(string $file): string + { + $fixtureFile = $this->initTempFixtureFile(); + + // copy source of wrongFile to a temporary file which will be modified by ECS + copy($file, $fixtureFile); + + shell_exec( + sprintf( + '%s/../../vendor/bin/ecs check --no-progress-bar --no-ansi --no-interaction --fix %s', + __DIR__, + $fixtureFile, + ), + ); + + return $fixtureFile; + } + + private function initTempFixtureFile(): string + { + // Create new file in temporary directory + $fixtureFile = tempnam(sys_get_temp_dir(), 'ecs-test'); + if ($fixtureFile === false) { + $this->fail('Could not create temporary file'); + } + $this->tempFixtureFile = $fixtureFile; // store to be able to remove it later + + return $fixtureFile; + } +} diff --git a/tests/Integration/Fixtures/Basic.correct.php.inc b/tests/Integration/Fixtures/Basic.correct.php.inc new file mode 100644 index 0000000..ce716ff --- /dev/null +++ b/tests/Integration/Fixtures/Basic.correct.php.inc @@ -0,0 +1,167 @@ + $foo + $value; + + // PhpdocToCommentFixer + /* + * Phpdoc used instead of plain comment + */ + if ($foo === 'bar') { + // NoAliasFunctionsFixer + $baz = implode(',', ['foo', 'bar']); + } + + $i = 3; + $i += 6; // LongToShorthandOperatorFixer + $i *= 2; // LongToShorthandOperatorFixer + $text = 'foo'; + $text .= 'bar'; // LongToShorthandOperatorFixer + + switch ($i) { + case 1: // NoUnneededControlParenthesesFixer + $i++; + break; + default: + break; + } + + // HeredocIndentationFixer + $heredoc = << 'bar', + 'baz' => 'bat', + ]; + + $multiLineAssociative2 = [ + 'foo' => 'bar', + 'baz' => 'bat', + 'bak' => 'baz', + ]; + + $multiLineAssociative3 = [ + 'firstKey' => 'bar', + 'thisIsSecondKey' => 'bat', + 'third' => 'bat', + ]; + } + + public function emptyFunction1(): void {} // SingleLineEmptyBodyFixer + + public function emptyFunction2( + $arg, + ): void {} // SingleLineEmptyBodyFixer + + // TrailingCommaInMultilineFixer + public function multiline( + $arg1, + $arg2, + $arg3, + ): void { + // TrailingCommaInMultilineFixer + $isSet = isset( + $arg1, + $arg2, + $arg3, + ); + + $sprintf = sprintf( + '%s%s', + 'bar', + 'baz', + ); + + foo( + 1, + 2, + ); + } + + public function withParameters( + // NullableTypeDeclarationFixer + ?string $nullableValue, + // NullableTypeDeclarationForDefaultNullValueFixer + ?string $anotherNullableValue = null, + ): void {} +} diff --git a/tests/Integration/Fixtures/Basic.wrong.php.inc b/tests/Integration/Fixtures/Basic.wrong.php.inc new file mode 100644 index 0000000..77275b3 --- /dev/null +++ b/tests/Integration/Fixtures/Basic.wrong.php.inc @@ -0,0 +1,163 @@ + $foo + $value; + + // PhpdocToCommentFixer + /** + * Phpdoc used instead of plain comment + */ + if ($foo === 'bar') { + // NoAliasFunctionsFixer + $baz = join(',', ['foo', 'bar']); + } + + $i = 3; + $i = $i + 6; // LongToShorthandOperatorFixer + $i = $i * 2; // LongToShorthandOperatorFixer + $text = 'foo'; + $text = $text . 'bar'; // LongToShorthandOperatorFixer + + switch ($i) { + case (1): // NoUnneededControlParenthesesFixer + $i++; + break; + default: + break; + } + + // HeredocIndentationFixer + $heredoc = << 'bar', 'baz' => 'bat', + ]; + + $multiLineAssociative2 = [ + 'foo'=>'bar', + 'baz'=>'bat', + 'bak'=>'baz' + ]; + + $multiLineAssociative3 = [ + 'firstKey' => 'bar', + 'thisIsSecondKey' => 'bat', + 'third' => 'bat', + ]; + } + + public function emptyFunction1(): void { + } // SingleLineEmptyBodyFixer + + public function emptyFunction2( + $arg + ): void { + } // SingleLineEmptyBodyFixer + + // TrailingCommaInMultilineFixer + public function multiline( + $arg1, + $arg2, + $arg3 + ): void + { + // TrailingCommaInMultilineFixer + $isSet = isset( + $arg1, + $arg2, + $arg3 + ); + + $sprintf = sprintf( + '%s%s', + 'bar', + 'baz' + ); + + foo( + 1, + 2 + ); + } + + public function withParameters( + // NullableTypeDeclarationFixer + null|string $nullableValue, + // NullableTypeDeclarationForDefaultNullValueFixer + string $anotherNullableValue = null, + ): void {} + + // MagicMethodCasingFixer, OrderedClassElementsFixer + public function __ToString(): string + { + return ''; + } +} diff --git a/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc b/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc new file mode 100644 index 0000000..04de983 --- /dev/null +++ b/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc @@ -0,0 +1,48 @@ +mayReturnDateTimeOrNull(); + $timestamp = $dateOrNull?->getTimestamp(); // RequireNullSafeObjectOperatorSniff + + // AssignNullCoalescingToCoalesceEqualFixer + $name = $_GET['name'] ?? 'default'; + + return $foo; + } + + public function mayReturnDateTimeOrNull(): ?\DateTime + { + return null; + } + + // AttributeEmptyParenthesesFixer + #[SomeFunctionAttribute] + #[AnotherAttribute('bar')] + #[AnotherAttribute] + #[First] + #[Second] + public function functionWithAttributes( + // MethodArgumentSpaceFixer + #[ParamAttribute] + #[AnotherAttribute('foo')] + string $foo, + string $bar, + ): void {} +} diff --git a/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc b/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc new file mode 100644 index 0000000..cdeead6 --- /dev/null +++ b/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc @@ -0,0 +1,49 @@ +someString = $someString; + } + + public function php80features( + string | bool $foo, // TypesSpacesFixer + int $bar // RequireTrailingCommaInDeclarationSniff + ): string | bool { + $value = mt_rand( + 0, + $bar // RequireTrailingCommaInCallSniff + ); + + $dateOrNull = $this->mayReturnDateTimeOrNull(); + $timestamp = $dateOrNull !== null ? $dateOrNull->getTimestamp() : null; // RequireNullSafeObjectOperatorSniff + + // AssignNullCoalescingToCoalesceEqualFixer + $name = isset($_GET['name']) ? $_GET['name'] : 'default'; + + return $foo; + } + + public function mayReturnDateTimeOrNull(): ?\DateTime + { + return null; + } + + // AttributeEmptyParenthesesFixer + #[SomeFunctionAttribute()] + #[AnotherAttribute('bar')]#[AnotherAttribute()] + + #[First, Second] + public function functionWithAttributes( + // MethodArgumentSpaceFixer + #[ParamAttribute] #[AnotherAttribute('foo')] string $foo, + string $bar, + ): void {} +} diff --git a/tests/Integration/Fixtures/Php81.correct.php.inc b/tests/Integration/Fixtures/Php81.correct.php.inc new file mode 100644 index 0000000..843acf1 --- /dev/null +++ b/tests/Integration/Fixtures/Php81.correct.php.inc @@ -0,0 +1,14 @@ + 3 ? true : false; + } + + /** + * @param string $stringParam This phpdoc should be preserved, because it contains some comment for $stringParam. + */ + public function methodWithMeaningfulParamComment(int $intParam, string $stringParam): void + { + // Do nothing. + } +} diff --git a/tests/Integration/Fixtures/PhpDoc.wrong.php.inc b/tests/Integration/Fixtures/PhpDoc.wrong.php.inc new file mode 100644 index 0000000..f6d164f --- /dev/null +++ b/tests/Integration/Fixtures/PhpDoc.wrong.php.inc @@ -0,0 +1,57 @@ + 3 ? true : false; + } + + /** + * @param int $intParam + * @param string $stringParam This phpdoc should be preserved, because it contains some comment for $stringParam. + * @return void + */ + public function methodWithMeaningfulParamComment(int $intParam, string $stringParam): void + { + // Do nothing. + } +} diff --git a/tests/Integration/Fixtures/PhpUnit.correct.php.inc b/tests/Integration/Fixtures/PhpUnit.correct.php.inc new file mode 100644 index 0000000..e79740a --- /dev/null +++ b/tests/Integration/Fixtures/PhpUnit.correct.php.inc @@ -0,0 +1,41 @@ +assertTrue($data); + + // PhpUnitDedicateAssertFixer + $this->assertStringContainsString('o', 'foo'); + + $this->assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + $this->assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + $this->assertSame(1, 2); + } + + public function testMethodName(): void // PhpUnitMethodCasingFixer + { + $this->assertTrue(true); + } +} diff --git a/tests/Integration/Fixtures/PhpUnit.wrong.php.inc b/tests/Integration/Fixtures/PhpUnit.wrong.php.inc new file mode 100644 index 0000000..c2111ab --- /dev/null +++ b/tests/Integration/Fixtures/PhpUnit.wrong.php.inc @@ -0,0 +1,41 @@ +assertSame(true, $data); + + // PhpUnitDedicateAssertFixer + $this->assertTrue(str_contains('foo', 'o')); + + $this->assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + self::assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + static::assertSame(1, 2); + } + + public function test_method_name(): void // PhpUnitMethodCasingFixer + { + $this->assertTrue(true); + } +} diff --git a/tests/Sniffs/AbstractSniffTestCase.php b/tests/Sniffs/AbstractSniffTestCase.php index 1bf9882..cfd1952 100644 --- a/tests/Sniffs/AbstractSniffTestCase.php +++ b/tests/Sniffs/AbstractSniffTestCase.php @@ -1,6 +1,8 @@ - $expectedErrors - * @param array $actualErrors + * @param array>> $actualErrors */ protected function assertErrors(array $expectedErrors, array $actualErrors): void { $actualLinesToErrorsMap = []; foreach ($actualErrors as $line => $error) { $error = reset($error); - $errorMessage = reset($error)['message']; - $actualLinesToErrorsMap[$line] = $errorMessage; + if ($error === false) { + continue; + } + $errorMessage = reset($error); + if (!is_array($errorMessage) || !isset($errorMessage['message'])) { + continue; + } + $actualLinesToErrorsMap[$line] = $errorMessage['message']; } $this->assertSame($expectedErrors, $actualLinesToErrorsMap); diff --git a/tests/Sniffs/Naming/AbstractClassNameSniffTest.php b/tests/Sniffs/Naming/AbstractClassNameSniffTest.php index e440e9c..2458729 100644 --- a/tests/Sniffs/Naming/AbstractClassNameSniffTest.php +++ b/tests/Sniffs/Naming/AbstractClassNameSniffTest.php @@ -1,16 +1,19 @@ - $expectedErrors */ - public function shouldFixCode(string $fixtureFile, array $expectedErrors): void + public function testShouldFixCode(string $fixtureFile, array $expectedErrors): void { $sniff = $this->applyFixturesToSniff($fixtureFile); $sniff->process(); @@ -21,16 +24,16 @@ public function shouldFixCode(string $fixtureFile, array $expectedErrors): void } /** - * @return array[] + * @return array}> */ - public function provideFixtures(): array + public static function provideFixtures(): array { return [ 'wrongly named' => [ __DIR__ . '/Fixtures/AbstractClassNameSniffTest.wrong.php.inc', [ - 3 => 'Abstract class should have prefix "Abstract".', - 13 => 'Abstract class should have prefix "Abstract".', + 5 => 'Abstract class should have prefix "Abstract".', + 15 => 'Abstract class should have prefix "Abstract".', ], ], 'properly named' => [__DIR__ . '/Fixtures/AbstractClassNameSniffTest.correct.php.inc', []], diff --git a/tests/Sniffs/Naming/ClassNameSuffixByParentSniffTest.php b/tests/Sniffs/Naming/ClassNameSuffixByParentSniffTest.php index 941a520..869ffbd 100644 --- a/tests/Sniffs/Naming/ClassNameSuffixByParentSniffTest.php +++ b/tests/Sniffs/Naming/ClassNameSuffixByParentSniffTest.php @@ -1,16 +1,20 @@ -|null $classSuffixes + * @param array $expectedErrors */ - public function shouldFixCode(string $fixtureFile, ?array $classSuffixes, array $expectedErrors): void + public function testShouldFixCode(string $fixtureFile, ?array $classSuffixes, array $expectedErrors): void { $sniff = $this->applyFixturesToSniff($fixtureFile); @@ -28,15 +32,15 @@ public function shouldFixCode(string $fixtureFile, ?array $classSuffixes, array } /** - * @return array[] + * @return array|null, array}> */ - public function provideFixtures(): array + public static function provideFixtures(): array { return [ 'wrong with default ruleset' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/CommandWrong.php.inc', null, - [5 => 'Class "WronglyNamed" should have suffix "Command" by parent class/interface'], + [7 => 'Class "WronglyNamed" should have suffix "Command" by parent class/interface'], ], 'properly named with default ruleset' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/CommandCorrect.php.inc', @@ -46,7 +50,7 @@ public function provideFixtures(): array 'wrong with custom ruleset' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/CustomWrong.php.inc', ['ParentClass'], - [3 => 'Class "WronglyNamed" should have suffix "ParentClass" by parent class/interface'], + [5 => 'Class "WronglyNamed" should have suffix "ParentClass" by parent class/interface'], ], 'properly named with custom ruleset' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/CustomCorrect.php.inc', @@ -56,7 +60,7 @@ public function provideFixtures(): array 'wrong with interface' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/InterfaceWrong.php.inc', ['FooBarInterface'], - [3 => 'Class "WronglyNamed" should have suffix "FooBar" by parent class/interface'], + [5 => 'Class "WronglyNamed" should have suffix "FooBar" by parent class/interface'], ], 'properly named interface' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/InterfaceCorrect.php.inc', @@ -66,7 +70,7 @@ public function provideFixtures(): array 'wrong with abstract class' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/AbstractWrong.php.inc', ['AbstractSomething'], - [3 => 'Class "WronglyNamed" should have suffix "Something" by parent class/interface'], + [5 => 'Class "WronglyNamed" should have suffix "Something" by parent class/interface'], ], 'properly with abstract class' => [ __DIR__ . '/Fixtures/ClassNameSuffixByParentSniffTest/AbstractCorrect.php.inc', diff --git a/tests/Sniffs/Naming/Fixtures/AbstractClassNameSniffTest.correct.php.inc b/tests/Sniffs/Naming/Fixtures/AbstractClassNameSniffTest.correct.php.inc index b14a793..b0bff23 100644 --- a/tests/Sniffs/Naming/Fixtures/AbstractClassNameSniffTest.correct.php.inc +++ b/tests/Sniffs/Naming/Fixtures/AbstractClassNameSniffTest.correct.php.inc @@ -1,4 +1,6 @@ - $expectedErrors */ - public function shouldFixCode(string $fixtureFile, array $expectedErrors): void + public function testShouldFixCode(string $fixtureFile, array $expectedErrors): void { $sniff = $this->applyFixturesToSniff($fixtureFile); $sniff->process(); @@ -21,14 +24,14 @@ public function shouldFixCode(string $fixtureFile, array $expectedErrors): void } /** - * @return array[] + * @return array}> */ - public function provideFixtures(): array + public static function provideFixtures(): array { return [ 'wrongly named' => [ __DIR__ . '/Fixtures/InterfaceNameSniffTest.wrong.php.inc', - [3 => 'Interface should have suffix "Interface".'], + [5 => 'Interface should have suffix "Interface".'], ], 'properly named' => [__DIR__ . '/Fixtures/InterfaceNameSniffTest.correct.php.inc', []], ]; diff --git a/tests/Sniffs/Naming/TraitNameSniffTest.php b/tests/Sniffs/Naming/TraitNameSniffTest.php index f28048d..b293d97 100644 --- a/tests/Sniffs/Naming/TraitNameSniffTest.php +++ b/tests/Sniffs/Naming/TraitNameSniffTest.php @@ -1,16 +1,19 @@ - $expectedErrors */ - public function shouldFixCode(string $fixtureFile, array $expectedErrors): void + public function testShouldFixCode(string $fixtureFile, array $expectedErrors): void { $sniff = $this->applyFixturesToSniff($fixtureFile); $sniff->process(); @@ -21,14 +24,14 @@ public function shouldFixCode(string $fixtureFile, array $expectedErrors): void } /** - * @return array[] + * @return array}> */ - public function provideFixtures(): array + public static function provideFixtures(): array { return [ 'wrongly named' => [ __DIR__ . '/Fixtures/TraitNameSniffTest.wrong.php.inc', - [3 => 'Trait should have suffix "Trait".'], + [5 => 'Trait should have suffix "Trait".'], ], 'properly named' => [__DIR__ . '/Fixtures/TraitNameSniffTest.correct.php.inc', []], ];