From 5c7ceddab9d0433560b02c4366e8fceb3faebd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Gon=C3=A7alves?= Date: Fri, 11 Sep 2026 15:58:01 +0100 Subject: [PATCH 1/7] Restructure the package for the 5.0 release Move every class to the Kununu\CodeTools namespace under a conventional src/ layout, with the PHP_CodeSniffer standard at src/PHPCodeSniffer/Kununu so installed_paths resolves inside the package. Tests mirror src/ one-for-one under Kununu\CodeTools\Tests, with fixtures in tests/resources. - Make the pre-commit hook honour the project's own php-cs-fixer.php when it has one, so paths that config excludes are no longer reformatted - Install the git hook from post-install-cmd, since Composer never loads the root package as a plugin and so never reaches the plugin events - Add a PHPStan config template, publishable with `code-tools publish:config phpstan` - Mark the four sniffs final and require ext-mbstring and ext-tokenizer - Rename the repo's own tool configs to *.dist, and Rector's to rector-ci.php - Run every quality gate from vendor/bin, so `composer ci` needs nothing installed globally - Add UPGRADE.md and reduce README.md to a tool index linking per-tool pages Co-Authored-By: Claude Opus 5 --- .editorconfig | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 24 +- .github/dependabot.yml | 14 +- .github/workflows/branch-name-validator.yml | 41 +++ .github/workflows/continuous-integration.yml | 50 +-- .gitignore | 23 +- AGENTS.md | 148 +++++++- CODING-GUIDELINES.md | 331 ++++++++++++++++++ CONTRIBUTING.md | 172 ++++++++- .../CsFixer/Command/CsFixerGitHookCommand.php | 197 ----------- README.md | 99 ++---- UPGRADE.md | 147 ++++++++ bin/code-tools | 84 +++-- bin/php-in-k8s | 42 ++- composer-dependency-analyser.php | 11 +- composer-require-checker.json | 17 +- composer.json | 98 ++++-- dist/.editorconfig.dist | 2 +- dist/php-cs-fixer.php.dist | 5 +- dist/phpcs.xml.dist | 2 +- dist/phpstan.neon.dist | 30 ++ dist/rector.php.dist | 10 +- docs/ArchitectureSniffer/README.md | 37 +- docs/CodeTools/README.md | 4 + docs/CsFixer/README.md | 22 -- docs/EditorConfig/README.md | 4 + docs/PHPCSFixer/README.md | 33 ++ .../{CodeSniffer => PHPCodeSniffer}/README.md | 81 +++-- ...-check-empty-line-after-class-elements.png | Bin .../screenshots/cs-check-no-empty-line.png | Bin ...cs-fix-empty-line-after-class-elements.png | Bin .../screenshots/cs-fix-no-empty-line.png | Bin .../git-diff-MethodSignatureArguments.png | Bin ...t-diff-empty-line-after-class-elements.png | Bin .../screenshots/git-diff-line-length.png | Bin .../screenshots/git-diff-no-empty-line.png | Bin .../phpcbf-MethodSignatureArguments.png | Bin .../phpcs-MethodSignatureArguments.png | Bin .../screenshots/phpcs-line-length.png | Bin docs/{PhpInK8s => PHPInK8s}/README.md | 4 +- docs/PHPStan/README.md | 145 ++++++++ docs/Psalm/README.md | 4 + docs/Rector/README.md | 114 +++--- php-cs-fixer.php | 88 ----- php-cs-fixer.php.dist | 19 + phpcs.xml => phpcs.xml.dist | 4 +- phpstan.neon | 8 - phpstan.neon.dist | 14 + phpunit.xml => phpunit.xml.dist | 15 +- psalm.xml => psalm.xml.dist | 10 +- rector-ci.php | 15 + rector.php | 21 -- sonar-project.properties | 6 +- .../ArchitectureSniffer.php | 19 +- .../Configuration/ArchitectureLibrary.php | 52 ++- .../Configuration/Group.php | 10 +- .../Configuration/Rules/AbstractRule.php | 22 +- .../Configuration/Rules/MustBeFinal.php | 6 +- .../Configuration/Rules/MustBeReadonly.php | 6 +- .../Configuration/Rules/MustExtend.php | 14 +- .../Configuration/Rules/MustImplement.php | 16 +- .../Configuration/Rules/MustNotDependOn.php | 6 +- .../Configuration/Rules/MustOnlyDependOn.php | 6 +- .../Rules/MustOnlyHaveOnePublicMethod.php | 6 +- .../MustOnlyHaveOnePublicMethodNamed.php | 6 +- .../Configuration/Selector/ClassSelector.php | 2 +- .../Selector/InterfaceClassSelector.php | 2 +- .../Selector/NamespaceSelector.php | 2 +- .../Configuration/Selector/RegexTrait.php | 2 +- .../Configuration/Selector/Selectable.php | 2 +- .../Helper/GroupFlattener.php | 16 +- .../Helper/ProjectPathResolver.php | 12 +- .../Helper/RuleBuilder.php | 26 +- .../Helper/SelectorBuilder.php | 10 +- .../Helper/TypeChecker.php | 6 +- .../PHPCSFixer}/Command/CsFixerCommand.php | 29 +- .../Command/CsFixerGitHookCommand.php | 268 ++++++++++++++ .../PHPCSFixer}/CsFixerPlugin.php | 30 +- .../PHPCSFixer}/Hooks/git-pre-commit | 25 ++ .../Provider/CsFixerCommandProvider.php | 6 +- .../EmptyLineAfterClassElementsSniff.php | 12 +- .../Kununu}/Sniffs/Files/LineLengthSniff.php | 2 +- .../MethodSignatureArgumentsSniff.php | 52 +-- .../PHP/NoNewLineBeforeDeclareStrictSniff.php | 8 +- .../PHPCodeSniffer/Kununu}/ruleset.xml | 2 +- .../ArchitectureSnifferTest.php | 76 ++-- .../Configuration/ArchitectureLibraryTest.php | 21 +- .../Configuration/GroupTest.php | 90 +++-- .../Configuration/Rules/AbstractRuleTest.php | 16 +- .../Configuration/Rules/MustBeFinalTest.php | 14 +- .../Rules/MustBeReadonlyTest.php | 12 +- .../Configuration/Rules/MustExtendTest.php | 16 +- .../Configuration/Rules/MustImplementTest.php | 16 +- .../Rules/MustNotDependOnTest.php | 14 +- .../Rules/MustOnlyDependOnTest.php | 14 +- .../MustOnlyHaveOnePublicMethodNamedTest.php | 14 +- .../Rules/MustOnlyHaveOnePublicMethodTest.php | 12 +- .../Selector/ClassSelectorTest.php | 12 +- .../Selector/InterfaceClassSelectorTest.php | 12 +- .../Selector/NamespaceSelectorTest.php | 12 +- .../Configuration/Selector/RegexTraitTest.php | 33 ++ .../Helper/GroupFlattenerTest.php | 28 +- .../Helper/ProjectPathResolverTest.php | 4 +- .../Helper/RuleBuilderTest.php | 12 +- .../Helper/SelectorBuilderTest.php | 10 +- .../Helper/TypeCheckerTest.php | 75 ++++ tests/PHPCSFixer/Command/CommandTestCase.php | 26 ++ .../PHPCSFixer/Command/CsFixerCommandTest.php | 150 ++++++++ .../Command/CsFixerGitHookCommandTest.php | 292 +++++++++++++++ tests/PHPCSFixer/CsFixerPluginTest.php | 160 +++++++++ .../Provider/CsFixerCommandProviderTest.php | 8 +- .../EmptyLineAfterClassElementsSniffTest.php | 6 +- .../Sniffs/Files/LineLengthSniffTest.php | 6 +- .../MethodSignatureArgumentsSniffTest.php | 6 +- .../NoNewLineBeforeDeclareStrictSniffTest.php | 6 +- .../Kununu/Sniffs}/SniffTestCase.php | 31 +- .../Configuration/Selector/RegexTraitTest.php | 32 -- .../Helper/TypeCheckerTest.php | 73 ---- .../CsFixer/Command/CsFixerCommandTest.php | 154 -------- .../Command/CsFixerGitHookCommandTest.php | 234 ------------- .../Unit/Kununu/CsFixer/CsFixerPluginTest.php | 86 ----- .../PHPCSFixer}/fixer_test_cases.php | 8 +- .../after/Fixme.php | 0 .../before/Fixme.php | 0 .../LineLength/before/Fixme.php | 0 .../MethodSignatureArguments/after/Fixme.php | 0 .../MethodSignatureArguments/before/Fixme.php | 0 .../after/Fixme.php | 0 .../before/Fixme.php | 0 129 files changed, 2922 insertions(+), 1749 deletions(-) create mode 100644 .github/workflows/branch-name-validator.yml create mode 100644 CODING-GUIDELINES.md delete mode 100644 Kununu/CsFixer/Command/CsFixerGitHookCommand.php create mode 100644 UPGRADE.md create mode 100644 dist/phpstan.neon.dist delete mode 100644 docs/CsFixer/README.md create mode 100644 docs/PHPCSFixer/README.md rename docs/{CodeSniffer => PHPCodeSniffer}/README.md (74%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/cs-check-empty-line-after-class-elements.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/cs-check-no-empty-line.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/cs-fix-empty-line-after-class-elements.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/cs-fix-no-empty-line.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/git-diff-MethodSignatureArguments.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/git-diff-empty-line-after-class-elements.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/git-diff-line-length.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/git-diff-no-empty-line.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/phpcbf-MethodSignatureArguments.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/phpcs-MethodSignatureArguments.png (100%) rename docs/{CodeSniffer => PHPCodeSniffer}/screenshots/phpcs-line-length.png (100%) rename docs/{PhpInK8s => PHPInK8s}/README.md (95%) create mode 100644 docs/PHPStan/README.md delete mode 100644 php-cs-fixer.php create mode 100644 php-cs-fixer.php.dist rename phpcs.xml => phpcs.xml.dist (80%) delete mode 100644 phpstan.neon create mode 100644 phpstan.neon.dist rename phpunit.xml => phpunit.xml.dist (61%) rename psalm.xml => psalm.xml.dist (77%) create mode 100644 rector-ci.php delete mode 100644 rector.php rename {Kununu => src}/ArchitectureSniffer/ArchitectureSniffer.php (89%) rename {Kununu => src}/ArchitectureSniffer/Configuration/ArchitectureLibrary.php (94%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Group.php (92%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/AbstractRule.php (91%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustBeFinal.php (73%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustBeReadonly.php (74%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustExtend.php (77%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustImplement.php (76%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustNotDependOn.php (80%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustOnlyDependOn.php (82%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethod.php (75%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamed.php (81%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Selector/ClassSelector.php (89%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Selector/InterfaceClassSelector.php (91%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Selector/NamespaceSelector.php (90%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Selector/RegexTrait.php (88%) rename {Kununu => src}/ArchitectureSniffer/Configuration/Selector/Selectable.php (68%) rename {Kununu => src}/ArchitectureSniffer/Helper/GroupFlattener.php (89%) rename {Kununu => src}/ArchitectureSniffer/Helper/ProjectPathResolver.php (87%) rename {Kununu => src}/ArchitectureSniffer/Helper/RuleBuilder.php (63%) rename {Kununu => src}/ArchitectureSniffer/Helper/SelectorBuilder.php (57%) rename {Kununu => src}/ArchitectureSniffer/Helper/TypeChecker.php (90%) rename {Kununu/CsFixer => src/PHPCSFixer}/Command/CsFixerCommand.php (83%) create mode 100644 src/PHPCSFixer/Command/CsFixerGitHookCommand.php rename {Kununu/CsFixer => src/PHPCSFixer}/CsFixerPlugin.php (62%) rename {Kununu/CsFixer => src/PHPCSFixer}/Hooks/git-pre-commit (64%) rename {Kununu/CsFixer => src/PHPCSFixer}/Provider/CsFixerCommandProvider.php (64%) rename {Kununu => src/PHPCodeSniffer/Kununu}/Sniffs/Classes/EmptyLineAfterClassElementsSniff.php (94%) rename {Kununu => src/PHPCodeSniffer/Kununu}/Sniffs/Files/LineLengthSniff.php (93%) rename {Kununu => src/PHPCodeSniffer/Kununu}/Sniffs/Formatting/MethodSignatureArgumentsSniff.php (86%) rename {Kununu => src/PHPCodeSniffer/Kununu}/Sniffs/PHP/NoNewLineBeforeDeclareStrictSniff.php (83%) rename {Kununu => src/PHPCodeSniffer/Kununu}/ruleset.xml (90%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/ArchitectureSnifferTest.php (76%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php (88%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/GroupTest.php (58%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/AbstractRuleTest.php (69%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php (76%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustBeReadonlyTest.php (64%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php (81%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php (81%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustNotDependOnTest.php (75%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustOnlyDependOnTest.php (75%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php (74%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php (65%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Selector/ClassSelectorTest.php (64%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Selector/InterfaceClassSelectorTest.php (63%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Configuration/Selector/NamespaceSelectorTest.php (64%) create mode 100644 tests/ArchitectureSniffer/Configuration/Selector/RegexTraitTest.php rename tests/{Unit/Kununu => }/ArchitectureSniffer/Helper/GroupFlattenerTest.php (86%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Helper/ProjectPathResolverTest.php (82%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Helper/RuleBuilderTest.php (94%) rename tests/{Unit/Kununu => }/ArchitectureSniffer/Helper/SelectorBuilderTest.php (69%) create mode 100644 tests/ArchitectureSniffer/Helper/TypeCheckerTest.php create mode 100644 tests/PHPCSFixer/Command/CommandTestCase.php create mode 100644 tests/PHPCSFixer/Command/CsFixerCommandTest.php create mode 100644 tests/PHPCSFixer/Command/CsFixerGitHookCommandTest.php create mode 100644 tests/PHPCSFixer/CsFixerPluginTest.php rename tests/{Unit/Kununu/CsFixer => PHPCSFixer}/Provider/CsFixerCommandProviderTest.php (67%) rename tests/{Unit => PHPCodeSniffer}/Kununu/Sniffs/Classes/EmptyLineAfterClassElementsSniffTest.php (66%) rename tests/{Unit => PHPCodeSniffer}/Kununu/Sniffs/Files/LineLengthSniffTest.php (69%) rename tests/{Unit => PHPCodeSniffer}/Kununu/Sniffs/Formatting/MethodSignatureArgumentsSniffTest.php (66%) rename tests/{Unit => PHPCodeSniffer}/Kununu/Sniffs/PHP/NoNewLineBeforeDeclareStrictSniffTest.php (67%) rename tests/{ => PHPCodeSniffer/Kununu/Sniffs}/SniffTestCase.php (73%) delete mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/RegexTraitTest.php delete mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Helper/TypeCheckerTest.php delete mode 100644 tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php delete mode 100644 tests/Unit/Kununu/CsFixer/Command/CsFixerGitHookCommandTest.php delete mode 100644 tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php rename tests/{Unit/Kununu/CsFixer/_data => resources/PHPCSFixer}/fixer_test_cases.php (97%) rename tests/{_data => resources/PHPCodeSniffer}/EmptyLineAfterClassElements/after/Fixme.php (100%) rename tests/{_data => resources/PHPCodeSniffer}/EmptyLineAfterClassElements/before/Fixme.php (100%) rename tests/{_data => resources/PHPCodeSniffer}/LineLength/before/Fixme.php (100%) rename tests/{_data => resources/PHPCodeSniffer}/MethodSignatureArguments/after/Fixme.php (100%) rename tests/{_data => resources/PHPCodeSniffer}/MethodSignatureArguments/before/Fixme.php (100%) rename tests/{_data => resources/PHPCodeSniffer}/NoNewLineBeforeDeclareStrict/after/Fixme.php (100%) rename tests/{_data => resources/PHPCodeSniffer}/NoNewLineBeforeDeclareStrict/before/Fixme.php (100%) diff --git a/.editorconfig b/.editorconfig index be76d7c..dcd8392 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[*.{json,yaml,yml,tf}] +[*.{json,yaml,neon,yml,tf}] indent_size = 2 [*.php] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f3c4a61..75d9029 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,25 @@ - + +### 💡 Read our [guidelines](https://github.com/kununu/code-tools/blob/main/CONTRIBUTING.md#pull-requests) before opening the PR! 💡 + + + ### JIRA issue -[KUNSOMETHING-NNNN](https://new-work.atlassian.net/browse/KUNSOMETHING-NNNN) + +[KUNSOMETHING-NNNN](https://kununu-team.atlassian.net/browse/KUNSOMETHING-NNNN) + +## Issues + +- https://github.com/kununu/code-tools/issues/NNN + + # Description -The objective of this PR is to... +The objective of this PR is to ... - + ## Details - diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0132e05..49b57b8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ version: 2 updates: - package-ecosystem: "composer" - directory: "/services" + directory: "/" schedule: interval: "weekly" day: "saturday" @@ -10,6 +10,16 @@ updates: # Disable automatic rebasing to prevent overloading our CICD system rebase-strategy: "disabled" versioning-strategy: auto - # By default Dependabot has a limit of the number of open PR for version updates + # By default, Dependabot has a limit of the number of open PR for version updates # and security updates. # See other defaults in here: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates + # Keeps the action versions pinned in .github/workflows/ up to date + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "saturday" + labels: + - "dependencies" + # Disable automatic rebasing to prevent overloading our CICD system + rebase-strategy: "disabled" diff --git a/.github/workflows/branch-name-validator.yml b/.github/workflows/branch-name-validator.yml new file mode 100644 index 0000000..2752ac1 --- /dev/null +++ b/.github/workflows/branch-name-validator.yml @@ -0,0 +1,41 @@ +name: Branch Name Validator + +on: + pull_request: + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + +jobs: + validate: + name: Validate Branch Name + runs-on: ubuntu-latest + # Bots do not follow the naming convention (e.g. dependabot/composer/...) + if: github.event.pull_request.user.type != 'Bot' + + steps: + - name: Validate Branch Name + env: + # Never interpolate ${{ }} into the script: branch names are author-controlled + BRANCH_NAME: ${{ github.head_ref }} + # Keep in sync with the pattern documented in CONTRIBUTING.md + PATTERN: '^((KUN[A-Z][A-Z0-9]*-[0-9]+(-[a-z0-9]+)*)|([a-z]{2}[-a-z0-9]*[a-z0-9]))$' + run: | + if [[ "$BRANCH_NAME" =~ $PATTERN ]]; then + echo "✅ Branch name '$BRANCH_NAME' is valid." + exit 0 + fi + + echo "❌ Branch name '$BRANCH_NAME' does not follow the naming convention." + echo + echo "Expected either:" + echo " - a JIRA issue branch, e.g. KUNSOMETHING-9999 or KUNSOMETHING-9999-my-feature" + echo " (the KUN prefix and issue key stay uppercase, any suffix is kebab case)" + echo " - a meaningful kebab case name of at least three characters, e.g. implement-my-feature" + echo + echo "Underscores are not allowed, and a name must not start with a digit." + echo "See the Branches section in CONTRIBUTING.md for the full rules." + echo + echo "Pattern: $PATTERN" + exit 1 diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 693a507..41facd8 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -26,28 +26,33 @@ jobs: with: php-version: 8.4 coverage: none - tools: composer-dependency-analyser, composer-normalize, composer-require-checker - name: Install Composer Dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@v4 with: dependency-versions: "highest" composer-options: "--prefer-stable --optimize-autoloader --no-progress --no-interaction" + - name: Run Composer Audit + run: composer audit --abandoned=report + - name: Run Composer Dependency Analyser - run: composer-dependency-analyser + run: vendor/bin/composer-dependency-analyser - name: Run Composer Require Checker - run: composer-require-checker + run: vendor/bin/composer-require-checker - name: Run Composer Normalize - run: composer-normalize --dry-run --indent-size 2 --indent-style space --no-check-lock --no-update-lock - - - name: Run code style sniffer - run: vendor/bin/phpcs --standard=phpcs.xml Kununu/ tests/ + run: composer normalize --dry-run --indent-size 2 --indent-style space --no-check-lock --no-update-lock - name: Run PHP CS Fixer - run: vendor/bin/php-cs-fixer check --using-cache=no --config php-cs-fixer.php + run: vendor/bin/php-cs-fixer check --using-cache=no --config php-cs-fixer.php.dist + + - name: Run PHP_CodeSniffer + run: vendor/bin/phpcs --standard=phpcs.xml.dist src/ tests/ + + - name: Run Rector + run: vendor/bin/rector process --ansi --dry-run --config rector-ci.php src/ tests/ - name: Run PHPStan run: vendor/bin/phpstan analyse @@ -55,9 +60,6 @@ jobs: - name: Run Psalm run: vendor/bin/psalm --no-cache - - name: Run Rector - run: vendor/bin/rector process --ansi --dry-run --config rector.php Kununu/ tests/ - build: needs: checks name: PHPUnit @@ -69,8 +71,8 @@ jobs: - highest steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout Code + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -81,19 +83,23 @@ jobs: coverage: pcov - name: Install Composer Dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@v4 with: dependency-versions: ${{ matrix.dependencies }} composer-options: "--prefer-stable --optimize-autoloader --no-progress --no-interaction" - name: Run PHPUnit run: | - vendor/bin/phpunit --colors=always --testdox \ + php -d zend.assertions=1 \ + vendor/bin/phpunit \ + --colors=always \ + --testdox \ + --testdox-summary \ --log-junit tests/.results/tests-junit.xml \ --coverage-clover tests/.results/tests-clover.xml - name: Upload coverage files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ github.job }}-8.4-${{ matrix.dependencies }}-coverage include-hidden-files: true @@ -104,11 +110,13 @@ jobs: name: SonarCloud Checks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout Code + uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/download-artifact@v4 + - name: Download coverage files + uses: actions/download-artifact@v8 with: name: build-8.4-highest-coverage path: tests/.results/ @@ -120,7 +128,7 @@ jobs: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' tests-junit.xml - name: SonarCloud Scan - uses: SonarSource/sonarqube-scan-action@v7.0.0 + uses: SonarSource/sonarqube-scan-action@v8.1.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore index 1f1aa01..6a1f401 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,17 @@ -.idea/ -vendor/ -composer.lock -composer.phar -.phpunit.result.cache -.phpunit.cache -/tests/.results/ -.DS_Store *.local - +.DS_Store .claude .cursor +.idea/ +.php-cs-fixer.cache +.phpunit.cache +.phpunit.result.cache +/phpcs.xml +/phpstan.neon +/phpunit.xml +/psalm.xml +/rector.php +/tests/.results/ +composer.lock +composer.phar +vendor/ diff --git a/AGENTS.md b/AGENTS.md index 6760383..ac8d2d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,14 @@ # AGENTS.md -Guidance for AI agents working in this repository. For install and usage see `README.md`; for dev workflow see `CONTRIBUTING.md`. +Guidance for AI agents working in this repository. For install and usage see `README.md`. + +## Documentation map (read the relevant doc before working) + +| You need to… | Read | +|--------------------------------------------------------|-----------------------------------------------------------------------------| +| Understand structure, naming, class layout, test style | `CODING-GUIDELINES.md` (imported below) | +| Open a PR / branch / commit / coverage rules | `CONTRIBUTING.md` + the PR body template `.github/PULL_REQUEST_TEMPLATE.md` | +| Work on a specific feature | Its page in `docs/` (one folder per tool, indexed from `README.md`) | ## What this is @@ -8,34 +16,138 @@ Guidance for AI agents working in this repository. For install and usage see `RE ## Bundled tools -- PHP-CS-Fixer, PHP_CodeSniffer (with custom `Kununu` sniffs), Psalm, PHPStan, Rector. -- Architecture Sniffer, built on PHPAT, for architecture/dependency rules. -- `bin/code-tools`: publishes `dist/*.dist` configs into a consuming project. -- `bin/php-in-k8s`: runs PHP commands inside a local Kubernetes pod. +- PHP-CS-Fixer +- PHP_CodeSniffer (with custom `Kununu` sniffs) +- Psalm +- PHPStan +- Rector. +- PHPAT (Architecture Sniffer for architecture/dependency rules). +- `bin/code-tools` + - Publishes `dist/*.dist` configs into a consuming project. +- `bin/php-in-k8s` + - Runs PHP commands inside a local Kubernetes pod. ## Code layout -- `Kununu/` — PSR-4 source (`Kununu\` namespace): `CsFixer/` (composer plugin, command, git hook, provider), `Sniffs/` (custom PHP_CodeSniffer sniffs), `ArchitectureSniffer/`. -- `bin/` — the `code-tools` and `php-in-k8s` executables. -- `dist/` — `*.dist` config templates published into consuming projects. -- `docs/` — per-tool deep documentation, one folder per tool. -- `tests/` — PHPUnit tests under `tests/Unit/`, mirroring the `Kununu/` tree. +- `src/` — PSR-4 source (`Kununu\CodeTools\` namespace): + - `PHPCSFixer/` (composer plugin, command, git hook, provider) + - `ArchitectureSniffer/` (PHPAT architecture rules) + - `PHPCodeSniffer/Kununu/` — the PHP_CodeSniffer standard: `ruleset.xml` plus `Sniffs/`. + These are the one exception to the namespace above; see Conventions below. +- `bin/` + - The `code-tools` and `php-in-k8s` executables. +- `dist/` + - `*.dist` config templates published into consuming projects. +- Root configs — the repo's own tool configs are `*.dist` too (`php-cs-fixer.php.dist`, + `phpcs.xml.dist`, `phpstan.neon.dist`, `phpunit.xml.dist`, `psalm.xml.dist`), which is a + naming preference and unrelated to `dist/`. Rector's is `rector-ci.php`, named for the CI + gate that enforces it. Only PHP-CS-Fixer and Rector need an explicit `--config`; PHPStan, + Psalm, PHPUnit and PHP_CodeSniffer all auto-discover their `*.dist` file. +- `docs/` + - Per-tool deep documentation, one folder per tool. +- `tests/` + - PHPUnit tests, mirroring `src/` one-for-one under the `Kununu\CodeTools\Tests\` namespace. + - `bootstrap.php` and `resources/` at the root; everything else mirrors `src/`. ## Conventions -- `declare(strict_types=1);` in every PHP file. -- Follow the repo's own PHP-CS-Fixer and PHP_CodeSniffer rulesets; changes must stay clean under both. -- Custom sniffs live under `Kununu\Sniffs`; register them in `Kununu/Sniffs/ruleset.xml`. -- Tests mirror the source namespace under `tests/Unit/`. -- PHP version is pinned in `composer.json` (`require.php`). +### General conventions + +- Follow the repo's own PHP-CS-Fixer and PHP_CodeSniffer rulesets + - Changes must stay clean under both. +- Custom sniffs are the one exception to the `Kununu\CodeTools\` namespace: they live in + `src/PHPCodeSniffer/Kununu/Sniffs/` under `Kununu\Sniffs\`, mapped by a second PSR-4 entry. + - They are auto-discovered by `src/PHPCodeSniffer/Kununu/ruleset.xml`; no registration step. + - The `Kununu` directory name and the `Kununu\Sniffs\` namespace are both load-bearing, and + neither may be renamed. PHP_CodeSniffer takes the standard name from the directory holding + `ruleset.xml`, resolves `` to `/Sniffs/X/YSniff.php`, + and builds the reported sniff code from the namespace segment before `\Sniffs\`. Renaming + either would change what every consumer writes in `phpcs.xml.dist`. + - `installed_paths` names the directory *containing* the standard, so it must stay in step with + where the `Kununu` directory sits (`.../code-tools/src/PHPCodeSniffer`). Only this grouping + directory is free to change. +- Tests mirror `src/` exactly: `src/PHPCSFixer/Command/CsFixerCommand.php` is tested by + `tests/PHPCSFixer/Command/CsFixerCommandTest.php` in `Kununu\CodeTools\Tests\PHPCSFixer\Command`. + - There is no `Unit/` level. Every test here is a unit test, so the single PHPUnit test suite is + named `Full` and takes all of `tests/`. Do not reintroduce a suite-per-type split. + - The namespace is `Kununu\CodeTools\Tests\` + the path under `tests/`, including for the sniffs, + whose tests live at `tests/PHPCodeSniffer/Kununu/Sniffs/` to match where their source sits. + - `Kununu\CodeTools\Tests\` (`tests/`) deliberately overlaps `Kununu\CodeTools\` (`src/`). + Composer matches the longest PSR-4 prefix first, so this resolves; it would only break if a + `src/Tests/` directory were ever added. + - Fixtures are not part of the mirror. They live under `tests/resources//`, one folder per + consumer: `tests/resources/PHPCodeSniffer/` (the sniff `before`/`after` files) and + `tests/resources/PHPCSFixer/` (`fixer_test_cases.php`). + - Fixtures are deliberately excluded from the tools, and the scope differs on purpose: + `php-cs-fixer.php.dist` excludes all of `tests/resources`, while `phpcs.xml.dist`, + `phpstan.neon.dist` and `rector-ci.php` exclude only `tests/resources/PHPCodeSniffer` — those files are intentionally + malformed, whereas `fixer_test_cases.php` is valid PHP and stays under analysis. +- Minimum PHP version and required extensions are declared in `composer.json` (`require.php` and the `ext-*` entries). + +### Coding conventions + +@CODING-GUIDELINES.md ## Quality gates -Run `composer ci-checks` (cs-fixer, code sniffer, PHPStan, Rector, Psalm, PHPUnit). PHPStan runs at max level and Psalm must pass with no cache. See `scripts` in `composer.json` for individual commands and `CONTRIBUTING.md` for details. +Run `composer ci` (composer audit, composer-dependency-analyser, composer-require-checker, +composer-normalize, cs-fixer, code sniffer, Rector, PHPStan, Psalm, PHPUnit with coverage). + +`composer ci` needs nothing installed globally: a fresh clone plus `composer install` is enough. +`composer-dependency-analyser` and `composer-require-checker` come from `require-dev` and are run +from `vendor/bin` (Composer puts the bin-dir first on `PATH` for scripts, so the vendored copy wins +over any global one), `composer-normalize` is a `require-dev` plugin providing the `normalize` +subcommand, and `audit` is built into Composer. + +`composer audit` runs with `--abandoned=report`: `maglnet/composer-require-checker` pulls in the +abandoned `azjezz/psl`, which would otherwise fail the step. Security advisories still fail the +build, for production and dev dependencies alike — only the abandoned notice is informational. + +Note that `composer ci` calls `@cs-fix`, so it rewrites files rather than only checking them. +CI runs `php-cs-fixer check` instead. + +PHPStan runs at level 8 (see `phpstan.neon.dist`) and Psalm must pass with no cache. + +PHP_CodeSniffer publishes no composer `autoload` section, so its symbols are invisible to composer's +autoloader and to every static analyser. Each tool needs its own escape hatch, and none are removable: +`scanDirectories` plus `bootstrapFiles` in `phpstan.neon.dist` (dropping `scanDirectories` brings back +intermittent `interface.notFound` errors on a warm result cache), `` in `psalm.xml.dist`, the +`PHP_CodeSniffer\*` and PHPCS-only `T_*` entries in `composer-require-checker.json`, and the +`UNKNOWN_CLASS` ignore for `src/Kununu/Sniffs/` in `composer-dependency-analyser.php`. + +See `scripts` in `composer.json` for individual commands and `CONTRIBUTING.md` for details. + +## Git pre-commit hook + +`CsFixerPlugin` installs the PHP-CS-Fixer pre-commit hook on `post-install-cmd`/`post-update-cmd`. +In consuming projects that happens through the Composer plugin events. **It cannot here**: Composer +loads plugins only from installed packages (`PluginManager::loadInstalledPlugins()` reads the local +repository), never from the root package, so this repository's own plugin never activates — which is +also why `composer list` offers no `kununu:*` commands here. Adding the package to `allow-plugins` +does not change that. + +So `composer.json` wires the same code in as a script callable instead: +`post-install-cmd` → `@git-hook` → `CsFixerPlugin::installGitHooks`. Keep that wiring if the plugin +is refactored, or `composer install` stops installing the hook in this repository. Consumers who +follow the README's `--no-plugins` install can use the same callable, or run +`composer kununu:cs-fixer-git-hook` once. + +The installed config differs by case, and the tests cover all three: this repository links its own +`php-cs-fixer.php.dist`, a project that published `php-cs-fixer.php` links that, and anything else +links the packaged `dist/php-cs-fixer.php.dist`. Only the first two get the +`.git/kununu/filter-by-config` marker, which tells the hook it may narrow the staged files through +the config's finder; the packaged template's finder is rooted inside `vendor/` and would match +nothing. + +Plugin output goes to `php://output`, not `php://stdout`, so tests can capture it with `ob_start()`. +Tests that exercise the installer must `chdir()` into a throwaway git repository first — the command +resolves its target from the working directory, so without that they rewrite this repository's own +hooks while the suite runs. ## Hard constraints - Do not edit `vendor/`. -- Do not hardcode tool versions in docs; reference `composer.json`. -- Changes to `dist/*.dist` affect every consuming repo — treat them as public API. +- Do not hardcode tool versions in docs + - Reference `composer.json`. +- Changes to `dist/*.dist` affect every consuming repo, so treat them as public API. - Keep the `code-tools` binary interface backward compatible. diff --git a/CODING-GUIDELINES.md b/CODING-GUIDELINES.md new file mode 100644 index 0000000..376f861 --- /dev/null +++ b/CODING-GUIDELINES.md @@ -0,0 +1,331 @@ +# Coding guidelines + +## Patterns + +- As a rule of thumb, follow SOLID principles (some examples [here](https://dev.to/dalelantowork/solid-principles-object-oriented-programming-in-php-3p3e)) + +## General + +- Use strict types + - `declare(strict_types=1);` in every PHP file. +- Use descriptive names for classes and variables. +- In development environment, you should call the code fixer when you are committing your changes. +- Promoted properties are preferred + - If these decrease the legibility of the code then stick to "classic" properties and add the attributes there +- Order of elements of a class (this also **applies to test classes**): + - Traits uses first (alphabetically) + - Constants + - public + - protected + - private + - Properties + - public + - protected + - private + - Constructor (no matter what visibility) + - The constructor must **ALWAYS** be the first method even if it is not public + - Methods + - public static + - public + - protected static + - protected + - private static + - private + - When using promoted properties we are more relaxed, but ideally try to keep the visibility order in the constructor + - If possible put all the promoted properties first ordered by visibility and "regular" constructor parameters at the end + + ###### EXAMPLE + ```php + final readonly class MyClass + { + use FirstTrait; + use SecondTrait; + + public const string HELLO = 'hello'; + + protected const string WORLD = 'world'; + protected const string EUROPE = 'europe'; + + private const int VALUE = 9000; + + public float $score; + + // Yeah... it does not make sense to be protected as the class is final :) + // But on some cases it can be useful ;) + protected string $name; + + private string $whatever; + private int $value; + private AnotherClass $anotherClass; + + private function __construct( + public int $age, + protected float $salary, + string $name + ) { + $this->name = $name; + $this->score = $this->calculate(); + } + + public static function create(): self + { + return new self(self::europe()); + } + + public function name(): string + { + return $this->name; + } + + protected static function hello(): void + { + } + + protected function world(): string + { + return self::WORLD; + } + + private static function europe(): string + { + return self::EUROPE; + } + + private function calculate(): float + { + return ($this->salary + self::VALUE) * $this->age; + } + } + ``` +- Do not use PHPDoc annotations unless it's an absolute necessity (e.g. to signal that methods are throwing exceptions) + - Most libraries/bundles now switch to using PHP attributes, so this should be the preferred notation. + - `@method` annotations are fine for dynamic methods + - Example for PHPUnit tests: + ###### DO + ```php + #[DataProvider('myDataProvider')] + ``` + ###### DON'T + ```php + /** @dataProvider myDataProvider */ + ``` +- Use one-line comments when the comment has only one line. + ###### DO + ```php + /** @throws Exception1|Exception2 */ + ``` + ###### DON'T + ```php + /** + * @throws Exception1 + * @throws Exception2 + */ + ``` +- Place method parameters in multiple lines only if they don’t fit in one line (valid for constructors and other methods). + ###### DO + ```php + public function __construct(private string $profileUuid) + { + } + ``` + ###### DON'T + ```php + public function __construct( + private string $profileUuid + ) { + } + ``` +- When a call's arguments **do** span multiple lines (e.g. because one of them is a multi-line array), place **every** argument on its own line. Don't keep the first argument on the opening line while the rest wrap. + ###### DO + ```php + $this->logger->warning( + 'Failed to do something', + [ + 'message' => $exception->getMessage(), + 'uuid' => $object->uuid, + ] + ); + ``` + ###### DON'T + ```php + $this->logger->warning('Failed to do something', [ + 'message' => $exception->getMessage(), + 'uuid' => $object->uuid, + ]); + ``` +- Type your constants + ###### DO + ```php + private const string MY_CONSTANT = 'value'; + ``` + ###### DON´T + ```php + private const MY_CONSTANT = 'value'; + ``` +- Use constants instead of methods and variables to store static values . + ###### DO + ```php + private const FIELDS = ['industry_id', 'location', 'name']; + ``` + ###### DON´T + ```php + private array $fields = ['industry_id', 'location', 'name']; + + private function fields(): array + { + return ['industry_id', 'location', 'name']; + } + ``` +- Use PHP Enumerations instead of constants if you need a fixed set of allowed values. + ###### DO + ```php + enum MyEnum: string + { + case Excellent = 'excellent'; + case Good = 'good'; + case Regular = 'regular'; + case Mediocre = 'mediocre'; + case Bad = 'bad'; + } + + final readonly class MyClass + { + private MyEnum $value; + + public function __construct(string $value) + { + $this->value = MyEnum::from($value); + } + } + ``` + ###### DON´T + ```php + final readonly class MyClass + { + private const string EXCELLENT = 'excellent'; + private const string GOOD = 'good'; + private const string REGULAR = 'regular'; + private const string MEDIOCRE = 'mediocre'; + private const string BAD = 'bad'; + + private const array ALLOWED_VALUES = [ + self::EXCELLENT, + self::GOOD, + self::REGULAR, + self::MEDIOCRE, + self::BAD, + ]; + + private string $value; + + public function __construct(string $value) + { + if (!in_array($value, self::ALLOWED_VALUES)) { + throw new InvalidArgumentException(sprintf('Invalid value "%s', $value)); + } + $this->value = $value; + } + } + ``` +- Make your classes **readonly** when possible. If not make properties **readonly** if they aren't meant to be changed after initialization. + - This also allow to make the properties **public** and avoid the necessity of creating public getters. +- Evaluate if duplicated code can be refactored. + - For example, a set of similar `if` conditions maybe can be refactored into a `match` expression. +- Use fluent setters **when a class is mutable** (e.g. builders, collections). An immutable value object should instead be `readonly` with public properties and no setters. Pick one model per class, don't mix the two. +- Include the final keyword in all the classes that will not be extended, **including** tests. + - If your class is meant to be extended you should consider making them **abstract**. + - Do not make a class not final just for unit-testing reasons. Define an **interface** instead. + +# Tests + +- You should use the **static assertion** methods of PHPUnit and the **non-static** methods for the expectations: + ###### DO + ```php + self::assertEquals(...); + + $mock + ->expects($this->once()) + ->method('method'); + } + ``` + ###### DON´T + ```php + $this->assertEquals(...); + + $mock + ->expects(self::once()) + ->method('method'); + ``` + - This does not apply if you are using or extending test cases that have non-static assertion methods. + - For those case you should still use the `$this->assertSomethingFromCustomTestCase()` call. + - Matcher methods like `once()`/`never()`/`any()`/`exactly()` are **not** static assertions, so call them as `$this->once()`, not `self::once()` (as shown in the DO/DON'T above). +- Default to `assertEquals`. Use `assertSame` only when you are asserting the **same instance** (identity), not merely equal value. +- For tests with multiple cases you should use PHPUnit `DataProvider` attribute (not the old annotation) +- The data provider method should come **after** the test (unless it is a data provider used for multiple tests) + - The data provider must be a static method + - If you need to create mock objects on the data provider, then consider using `callable`/`Closure` as the type of your argument. Then, in your test method call it, passing the current instance (e.g. the `TestCase` instance being executed). + ###### EXAMPLE + ```php + + public function testMyMethod(callable $myArgument): void + { + // Call the callable to get the actual value + $myArgumentValue = $myArgument($this); + + // Now you can use the mock object created by the callable + $myArgumentValue + ->expects($this->once()) + ->method('doStuff') + ->willReturn([1, 2, 3]); + } + + public static function myMethodDataProvider(): array + { + // We can not call `createMock` in a static context, so we pass a callable to the test method + return [ + 'first_case' => [ + // This function will be called by the test method and will pass the current instance of this test. + // Inside the callable we can use the $testCase argument to call the `createMock` method + fn(self $testCase): MockObject&MyArgument => $testCase->createMock(MyArgument::class), + ], + ]; + } + ``` + +- The data provider method should be named according to the test method: + - The format is `testMethod` => `methodDataProvider` + - Example: if the test method is `testSomething` then the data provider method should be `somethingDataProvider` + - If the data provider is meant to be shared across several test it still should be name as `sharedNameDataProvider` + - If you only have one test, then you can name it just `dataProvider` + - 💡 **TL;DR** - Convention for data provider methods is ***something*DataProvider** + - Datasets names should be in snake case to facilitate copy and paste and running individual datasets (`phpunit --filter testName@dataset_name`) + ```php + #[DataProvider('successOnCreationProfileDataProvider')] + public function testSuccessOnCreationProfile(array $data): void + { + // your test code + } + + public static function successOnCreationProfileDataProvider(): array + { + return [ + // test cases data + 'my_first_test' => [ + ] + ]; + } + ``` +- Class properties that are mocks should have `MockObject` as a **union type** as the first type of the property. + - Logic is that you can read it as `MockObject of XXXX` + + ```php + private MockObject&LoggerInterface $logger; + ``` + +- If a mock object does not define any expectation, then it should not be `MockObject` but rather's PHPUnit `Stub` + - And if defined as property then it should also be a **union type** of `Stub` + - Logic is that you can read it as `Stub of XXXX` + +--- + +[Back to Contributing](CONTRIBUTING.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1136d6f..abd1807 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,47 +1,181 @@ # Contributing -How to develop, test, and ship changes to `kununu/code-tools`. +- Do you have a user story that requires changes in this library? +- Are you fixing a bug? +- You want to update some component/library, and you are not *dependabot*? +- You want to add an awesome feature or do a refactor on some part? + +If the answer was **YES** to any of the questions then you can contribute by creating a Pull Request on [GitHub](https://github.com/kununu/code-tools). ## Development setup -Requires PHP as pinned in `composer.json` (`require.php`) and Composer. +### Requirements + +- The minimum PHP version and the required extensions are declared in `composer.json` (`require.php` and the `ext-*` entries). + +### Setup + +Install dependencies: ```console composer install ``` -## Testing +### Coding standards and static analysis + +- We use a slightly modified version of the [PSR-12 Coding Standard](https://www.php-fig.org/psr/psr-12/) and so should you on your Pull Requests. +- We use the following linters/code standard tools: + - PHP CS Fixer + - PHP_CodeSniffer +- We have configured some static analysis tools: + - Rector + - PHPStan + - Psalm -Run the unit test suite: +#### Scripts ```console -composer test-unit +composer ci # Run same checks as in CI (note: applies CS fixes, CI only checks) +composer cs # PHP CS Fixer (kununu code standards) (dry-run) +composer cs-fix # PHP CS Fixer (kununu code standards) (apply-fixes) +composer sniffer # PHP_CodeSniffer (dry-run) +composer sniffer-fix # PHP_CodeSniffer (apply fixes) +composer rector # Rector (dry-run) +composer rector-fix # Rector (apply fixes) +composer phpstan # PHPStan static analysis +composer psalm # Psalm static analysis +composer test # Run the full PHPUnit test suite +composer test-coverage # Run the full PHPUnit test suite with a coverage report ``` -## Quality checks +All scripts are defined in `composer.json` under `scripts`. + +The same tools run in CI (`.github/workflows/continuous-integration.yml`), which also checks `composer-audit`, `composer-normalize`, `composer-require-checker`, and `composer-dependency-analyser`. + +`composer-dependency-analyser`, `composer-require-checker` and `composer-normalize` are global tools, installed in CI via `setup-php`. Install them locally to run `composer ci` in full. + +`composer ci` runs the same set of tools as in CI: +- Composer Audit +- Composer Normalizer +- Composer Require Checker +- Composer Dependency Analyser +- PHP CS Fixer +- PHP_CodeSniffer +- Rector +- PHPStan +- Psalm +- PHPUnit tests + +#### Shell scripts -Run the full check suite before opening a PR: +The executables in `bin/` must keep working on **bash 3.2**, which is what macOS still ships. Modern +bash accepts constructs 3.2 rejects, and vice versa, so a script that runs on Linux can still break +for a colleague on a Mac. + +Nothing in `composer ci` covers this, so check it with docker or podman (or similar tools): ```console -composer ci-checks +podman run --rm -v "$PWD:/repo:ro" -w /repo docker.io/library/bash:3.2 \ + bash -c 'for f in bin/*; do echo "$f"; bash -n "$f" || exit 1; done' ``` -This runs PHP-CS-Fixer, PHP_CodeSniffer, PHPStan, Rector, Psalm, and PHPUnit. Auto-fixers are available for style and refactors: +Swap `podman` for `docker` if that is what you have. Note that `bash -n` only accepts one script, so +the loop is what makes it cover every file. It also only parses: to exercise a script's behaviour, +mount the repo and run it inside the same container. + +### Code Guidelines + +- Follow our [coding guidelines](CODING-GUIDELINES.md). If you have any doubts let us know. + +### Tests + +- We want to ensure that our code is stable and has high quality standards. + - We use unit tests. + - 💡 We must keep our coverage at or above **95%**. Make sure to run and generate a coverage report and check if your changes keep the global coverage at or above that level. + - So add coverage to your new code, fix any broken tests resulting from your code and add coverage if your code change something. + - 💡 For the "fresh" code (i.e. code added or changed in your pull request) coverage should be at or above **90%**. + - This will be enforced by a SonarQube Cloud check by the CI (`.github/workflows/continuous-integration.yml`) + +Run the test suite: ```console -composer cs-fixer-fix -composer cs-fix -composer rector-fix +composer test ``` -See the `scripts` section of `composer.json` for the complete list of commands. - -## Pull requests +Run the test suite with a coverage report: -- Branch from `main`. -- Fill in `.github/PULL_REQUEST_TEMPLATE.md`, including the linked JIRA issue. -- Ensure CI is green; the Continuous Integration workflow runs the checks above plus composer dependency and normalization checks, and reports coverage to SonarCloud. -- `@kununu/backend-libraries` is the code owner and reviews all changes. +```console +composer test-coverage +``` +### Documentation + +Each feature under `src/` has a corresponding per-tool deep Markdown page document in `docs/`, one folder per tool, indexed from `README.md`. +When adding or changing a feature, update its `docs/` page and the index. + +## Pull Requests + +Take into consideration the following guidelines: + +### Branches +- **main** is the stable branch, create new feature branches for your changes: + - If implementing a user story name your branches according to your user story. E.g. ***KUNSOMETHING-9999***. + - If your user story requires multiple pull requests then add some meaning to the branch. E.g.: ***KUNSOMETHING-9999-first-feature***, ***KUNSOMETHING-9999-second-feature***. + - If not on the context of a user story, then create a meaningful branch name. E.g.: ***implement-my-feature***. + - Do not cluster all the words into a single one + - **DO**: `implement-my-feature` + - **DON'T DO**: `implementmyfeature` + - Keep the case of the branches like in the examples (***KUNSOMETHING-9999***, ***KUNSOMETHING-9999-my-feature***, ***implement-my-feature***). + - So, besides the (potential) JIRA issue prefix (KUNSOMETHING-9999), use **kebab case** (lowercase words separated by hyphens). Underscores are **not** allowed + - Branch names are validated in CI against this pattern: `^((KUN[A-Z][A-Z0-9]*-[0-9]+(-[a-z0-9]+)*)|([a-z]{2}[-a-z0-9]*[a-z0-9]))$` + +### Pull Requests Titles +- Like the branches you need to give good title to the PR: + - If implementing a user story. E.g.: ***KUNSOMETHING-9999 Name of the story on JIRA***. + - This format is **mandatory** for PRs based on user stories. + - If multiple PRs for a user story then use the same pattern as depicted in the branches sections. Examples: + - ***KUNSOMETHING-9999 Name of the story on JIRA - Part 1*** + - ***KUNSOMETHING-9999 Name of the story on JIRA - Migrations*** + - ***KUNSOMETHING-9999 Name of the story on JIRA - Queue readers*** + - etc. + - If not, a meaningful title. E.g. ***Implementation of my feature***. + +### Pull Requests Descriptions +- It's always nice to have context regarding the pull request: + - If your PR is related to a user story then is **mandatory to add a link to JIRA**. + - If your PR is related to a GitHub issue then is **mandatory to add a link to that issue**. + - Is **mandatory to add a description** of what is the purpose of the PR and/or the changes being done on it. + - Ideally add some technical details. + - Keep it readable: prefer short, distinct paragraphs over one long block of text. + - When listing the changes, lead each item with the area/file it affects and nest the explanation beneath it, instead of burying it in one long inline sentence. + - Do not add AI-tool "generated by" / attribution footers to the PR body (e.g. "Generated with ..."). Tool or co-author attribution belongs in the commit (e.g. a `Co-Authored-By` trailer), not the description. + +### Pull Requests Template + +Every PR that you open will be pre-filled with the [template](./.github/PULL_REQUEST_TEMPLATE.md) to help you follow these guidelines. Make sure to fill in the blanks/change and **remove the comments** (the lines with ``). + +### Pull Requests Status +- Opening a Pull Request: + - On opening the PR it will be auto-assigned to you. + - If you are still working on you PR then create your PR as a **Draft Pull Request**. + - The reason is that if you don't the code owners of the repository will receive emails notifications and your PR is not ready yet. + - **Coding agents must always open the PR as a draft** (`gh pr create --draft`) and must not mark it ready for review or request reviewers until the person who requested the work confirms it is ready. + - Optionally, add the **"work in progress"** label. +- Additional labels: + - If your PR is not meant to be merged right away then you use the label **"do not merge"**. +- Code review of the Pull Request: + - Ensure all quality gates pass before requesting review + - All PRs will assign the `CODEOWNERS` (`@kununu/backend-libraries`) team members as code reviewers. + - You can also add additional people for code review. + - If your PR is a draft then convert it to a regular PR. + - Remove the label **"work in progress"** if you have added it before. +- Merging PRs: + - The repository is configured to delete the branches on this action. + +### Commit messages +- Write good commit messages! +- *"Small improvement"*, *"Fix"*, *"Code improvement"* are **NOT** good messages (you improved what? you fixed what?). +- Try to use something like **[IMPERATIVE VERB]** **[SUBJECT]**. E.g.: "Introduce new queue reader", "Fix typo in class XPTO", etc. +- A good read [here](https://chris.beams.io/posts/git-commit/). ## Releasing diff --git a/Kununu/CsFixer/Command/CsFixerGitHookCommand.php b/Kununu/CsFixer/Command/CsFixerGitHookCommand.php deleted file mode 100644 index e622566..0000000 --- a/Kununu/CsFixer/Command/CsFixerGitHookCommand.php +++ /dev/null @@ -1,197 +0,0 @@ -setName('kununu:cs-fixer-git-hook') - ->setAliases(['cs-fixer-git-hook']) - ->setDescription('Installs PHP CS Fixer as a Git pre-commit hook.'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $io->title('Installing PHP CS Fixer Git Pre‑Commit Hook'); - - try { - $rootPath = $this->getGitRootPath(); - $gitPath = $rootPath . '/.git'; - - if (!is_dir($gitPath)) { - throw new RuntimeException(sprintf( - '.git directory not found at "%s".', - $gitPath - )); - } - - $this->installHook($gitPath); - $this->linkConfigAndBinary($gitPath); - - $io->success('PHP CS Fixer Git pre‑commit hook installed successfully.'); - - return self::SUCCESS; - } catch (Throwable $e) { - $io->error('Installation failed: ' . $e->getMessage()); - - return self::FAILURE; - } - } - - private function getGitRootPath(): string - { - $cwd = getcwd(); - if ($cwd === false) { - throw new RuntimeException('Could not determine current working directory.'); - } - - // Mark the directory as safe - $process = new Process(['git', 'config', '--global', '--add', 'safe.directory', $cwd]); - $process->run(); - - $process = new Process(['git', 'rev-parse', '--show-toplevel']); - $process->run(); - - if (!$process->isSuccessful()) { - throw new RuntimeException('Not a Git repository or Git not available.'); - } - - return trim($process->getOutput()); - } - - private function installHook(string $gitPath): void - { - $hooksDir = $gitPath . '/hooks'; - $sourceHook = __DIR__ . '/../Hooks/git-pre-commit'; - $destHook = $hooksDir . '/pre-commit'; - - if (!is_dir($hooksDir) && !@mkdir($hooksDir, 0777, true) && !is_dir($hooksDir)) { - throw new RuntimeException(sprintf( - 'Could not create hooks directory: "%s".', - $hooksDir - )); - } - - if (file_exists($destHook) && !@unlink($destHook)) { - throw new RuntimeException(sprintf( - 'Could not remove existing hook at "%s".', - $destHook - )); - } - - if (!@copy($sourceHook, $destHook)) { - throw new RuntimeException(sprintf( - 'Failed to copy hook from "%s" to "%s".', - $sourceHook, - $destHook - )); - } - - if (!@chmod($destHook, 0755)) { - throw new RuntimeException(sprintf( - 'Failed to make hook executable at "%s".', - $destHook - )); - } - } - - private function linkConfigAndBinary(string $gitPath): void - { - $vendorDir = $this->resolveVendorDir($gitPath); - - $this->ensureSymlinkRelative( - $vendorDir . '/kununu/code-tools/php-cs-fixer.php', - $gitPath . '/kununu/.php-cs-fixer.php' - ); - - $this->ensureSymlinkRelative( - $vendorDir . '/bin/php-cs-fixer', - $gitPath . '/kununu/php-cs-fixer' - ); - } - - private function resolveVendorDir(string $rootGitPath): string - { - $repoRoot = basename($rootGitPath) === '.git' ? dirname($rootGitPath) : $rootGitPath; - - // Candidates where vendor/ might live: repo root, repo root/services, parent of repo root, parent/services - $parentRoot = dirname($repoRoot); - $candidates = [ - $repoRoot . '/vendor', - $repoRoot . '/services/vendor', - $parentRoot . '/vendor', - $parentRoot . '/services/vendor', - ]; - - foreach ($candidates as $candidate) { - if (is_dir($candidate)) { - $resolved = realpath($candidate); - - return $resolved !== false ? $resolved : $candidate; - } - } - - throw new RuntimeException('Could not find vendor directory in project root or its parent.'); - } - - private function ensureSymlinkRelative(string $target, string $linkPath): void - { - $linkDir = dirname($linkPath); - - if (!is_dir($linkDir) && !@mkdir($linkDir, 0777, true) && !is_dir($linkDir)) { - throw new RuntimeException(sprintf( - 'Could not create directory for symlink: "%s".', - $linkDir - )); - } - - if (is_link($linkPath) || file_exists($linkPath)) { - unlink($linkPath); - } - - $relativeTarget = $this->makeRelativePath($linkDir, $target); - - if (!@symlink($relativeTarget, $linkPath)) { - throw new RuntimeException(sprintf( - 'Failed to create symlink from "%s" to "%s".', - $linkPath, - $relativeTarget - )); - } - } - - private function makeRelativePath(string $from, string $to): string - { - $fromReal = realpath($from); - $toReal = realpath($to); - - if ($fromReal === false || $toReal === false) { - throw new RuntimeException('Invalid path(s) provided.'); - } - - $from = explode(DIRECTORY_SEPARATOR, $fromReal); - $to = explode(DIRECTORY_SEPARATOR, $toReal); - - while (count($from) && count($to) && ($from[0] === $to[0])) { - array_shift($from); - array_shift($to); - } - - return str_repeat('../', count($from)) . implode('/', $to); - } -} diff --git a/README.md b/README.md index 07b079e..5b5cf0a 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,44 @@ -![Continuous Integration](https://github.com/kununu/code-tools/actions/workflows/continuous-integration.yml/badge.svg) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=kununu_code-tools&metric=alert_status)](https://sonarcloud.io/dashboard?id=kununu_code-tools)

- Brancher + kununu Code Tools

-# code-tools - -- This repository contains code tools you can use in your project. -- It is a collection of tools and scripts that help us to maintain our codebase. - -## Tools -### `.editorconfig` -- EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. The EditorConfig project consists of a file format for defining coding styles and a collection of text editor plugins that enable editors to read the file format and adhere to defined styles. EditorConfig files are easily readable and they work nicely with version control systems. -- Learn more about `.editorconfig` at official page [here](https://editorconfig.org/). - -### `PHP-CS-Fixer` -- This project uses **PHP-CS-Fixer** to automatically format and fix PHP code according to defined coding standards. It helps maintain clean, consistent, and readable code across the codebase. - -### `PHP_CodeSniffer` -- PHP_CodeSniffer is a set of two PHP scripts; the main `phpcs` script that tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard, and a second `phpcbf` script to automatically correct coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent. -- Though the usage of this tool is not mandatory, it is highly recommended to use it to ensure the quality of the codebase. -- Learn more about PHP_CodeSniffer at official page [here](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki). - -### `Psalm` -- Psalm is a static analysis tool for PHP that helps identify bugs, type errors, and other issues in your codebase before runtime. It provides advanced type inference and can detect complex issues that other tools might miss. -- Learn more about Psalm at the official page [here](https://psalm.dev/). +# kununu Code Tools -### `Rector` -- Rector is a tool that automatically upgrades and refactors your PHP code. It is a tool that helps you to keep your code up-to-date and clean. -- Learn more about Rector at official page [here](https://getrector.com/documentation). +Shared development tooling for kununu PHP projects. It centralizes the static-analysis and code-style configuration our repositories use, and ships two helper executables. -### `bin/code-tools` -- Though each tool can be used "out-of-the-box", this is a helper script that allows you to copy configuration files of each, or all, tools to your project so you can customize them. +## Installation -### `bin/php-in-k8s` -- This is a helper script that allows you to run PHP commands inside a local Kubernetes pod without having to connect to it via a terminal manually. +```console +composer require --dev kununu/code-tools --no-plugins +``` -### `Architecture Sniffer & PHPAT` -- **Architecture Sniffer** enforces architectural and dependency rules in your PHP codebase, helping you maintain a clean and consistent architecture. -- It is powered by [PHPAT](https://github.com/carlosas/phpat), a static analysis tool for PHP architecture testing. -- Architecture Sniffer uses a YAML configuration file (`architecture.yaml`) where you define your architectural groups and their allowed dependencies. Each group is a key under the `architecture` root, e.g.: +`--no-plugins` keeps composer plugins from running during installation, which would otherwise generate unwanted configuration files, particularly in projects using `symfony/flex`. - ```yaml - architecture: - $controllers: - includes: - - "App\\Controller\\*Controller" - depends_on: - - "$services" - $services: - includes: - - "App\\Service\\*Service" - ``` -- To use Architecture Sniffer with PHPStan, add the extension to your `phpstan.neon`: - ```neon - includes: - - vendor/carlosas/phpat/extension.neon - services: - - - class: PHPAT\PHPStan\PHPStanExtension - tags: [phpstan.extension] - ``` -- For more details and advanced configuration, see [Kununu/ArchitectureSniffer/README.md](docs/ArchitectureSniffer/README.md). +## Tools -### Require Library as a dev dependency +Every tool works out of the box. Use [`bin/code-tools`](docs/CodeTools/README.md) to publish a tool's configuration template into your project when you need to customise it. -You can use this library by issuing the following command: +| Tool | Purpose | +|------------------------------------------------------------|------------------------------------------------------------------------------| +| [`.editorconfig`](docs/EditorConfig/README.md) | Consistent editor and IDE settings across the team | +| [PHP-CS-Fixer](docs/PHPCSFixer/README.md) | Formats and fixes code against the kununu coding standard | +| [PHP_CodeSniffer](docs/PHPCodeSniffer/README.md) | Reports and fixes coding-standard violations, including custom kununu sniffs | +| [PHPStan](docs/PHPStan/README.md) | Static analysis | +| [Psalm](docs/Psalm/README.md) | Static analysis with type inference | +| [Rector](docs/Rector/README.md) | Automated refactoring and PHP version upgrades | +| [Architecture Sniffer](docs/ArchitectureSniffer/README.md) | Architecture and dependency rules, powered by PHPAT | +| [`bin/code-tools`](docs/CodeTools/README.md) | Publishes the configuration templates above into your project | +| [`bin/php-in-k8s`](docs/PHPInK8s/README.md) | Runs PHP commands inside a local Kubernetes pod | -```console -composer require --dev kununu/code-tools --no-plugins -``` -- The `--no-plugins` is used to avoid the composer plugins to be executed and prevent generating unwanted configuration files, specially in projects with `symfony/flex` installed. +## Upgrading -## Usage -- [.editorconfig](docs/EditorConfig/README.md) instructions. -- [PHP-CS-Fixer](docs/CsFixer/README.md) instructions. -- [PHP_CodeSniffer](docs/CodeSniffer/README.md) instructions. -- [Psalm](docs/Psalm/README.md) instructions. -- [Rector](docs/Rector/README.md) instructions. -- [bin/code-tools](docs/CodeTools/README.md) instructions. -- [bin/php-in-k8s](docs/PhpInK8s/README.md) instructions. -- [Architecture Sniffer & PHPAT](docs/ArchitectureSniffer/README.md) instructions. +Coming from 4.x? See [UPGRADE.md](UPGRADE.md). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and the PR/release process. + +------------------------------ + +![Continuous Integration](https://github.com/kununu/code-tools/actions/workflows/continuous-integration.yml/badge.svg) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=kununu_code-tools&metric=alert_status)](https://sonarcloud.io/dashboard?id=kununu_code-tools) diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..65325fe --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,147 @@ +# Upgrade guide + +## From 4.x to 5.0 + +Most of this release is internal restructuring, but a few things need a hand: + +- Work through [Required changes](#required-changes) first + - Until those are done PHP_CodeSniffer, PHPStan and the pre-commit hook will all fail. +- Then skim [Behaviour changes](#behaviour-changes) for things that still work but work differently. + +Publishing a config never overwrites a file you already have (`code-tools` warns and skips), so nothing below is fixed by re-running `publish:config`. + +### Required changes + +#### 1. Update `installed_paths` in your `phpcs.xml` + +The custom sniffs moved inside the package. If you have published a `phpcs.xml`, edit it: + +```diff +- ++ +``` + +Without this PHP_CodeSniffer cannot find the `Kununu` standard and every run aborts. + +#### 2. Update the Architecture Sniffer class in your `phpstan.neon` + +```diff + services: +- - class: Kununu\ArchitectureSniffer\ArchitectureSniffer ++ - class: Kununu\CodeTools\ArchitectureSniffer\ArchitectureSniffer +``` + +Every class in the package except the sniffs moved from `Kununu\` to `Kununu\CodeTools\`. +This is the only one that consumers normally name explicitly. + +#### 3. Stop extending the sniffs + +`LineLengthSniff`, `EmptyLineAfterClassElementsSniff`, `MethodSignatureArgumentsSniff` and `NoNewLineBeforeDeclareStrictSniff` are now `final`. +If you subclassed one, copy it into your own project instead. + +#### 4. Make sure `ext-mbstring` and `ext-tokenizer` are available + +Both are now required in `composer.json`. +They were always used indirectly: the requirement is now explicit, so installs fail on hosts without them. + +The PHP requirement itself is unchanged (`>=8.4`). + +#### 5. Repair the pre-commit hook symlink + +If you use the git hook, `.git/kununu/.php-cs-fixer.php` currently points at `vendor/kununu/code-tools/php-cs-fixer.php`, which no longer exists. + +A dangling link makes the hook print `Missing php-cs-fixer rules file` and **exit 1**, blocking every commit that touches a `.php` file. + +A normal `composer install` or `composer update` repairs it, because the plugin re-installs the hook afterwards. + +If you install with `--no-plugins`, as the README recommends, run it yourself once: + +```console +composer kununu:cs-fixer-git-hook +``` + +### Behaviour changes + +#### The pre-commit hook now respects your own PHP-CS-Fixer config + +Previously the hook always used the package's internal config, so anything your project excluded was reformatted anyway. + +Now: + +| Your project | Hook uses | +|-------------------------------------|----------------------------------------------| +| has a published `php-cs-fixer.php` | that file, and honours the paths it excludes | +| has none | the shipped `dist/php-cs-fixer.php.dist` | + +When your own config is used, the installer writes a marker at `.git/kununu/filter-by-config` and the hook narrows the staged files through the config's finder. + +This is needed because PHP-CS-Fixer ignores its own finder as soon as explicit paths are passed on the command line, which is how the hook invokes it. + +Nothing to configure, but if you previously relied on the hook formatting files your config excludes, it will now leave them alone. + +#### `dist/rector.php.dist` changed + +The template now uses `withPhpSets()` and `withComposerBased(phpunit: true)` instead of a pinned `PHPUnitSetList::PHPUNIT_100`, and no longer skips `AddOverrideAttributeToOverriddenMethodsRector`. + +An existing `rector.php` in your project is untouched; this only affects projects publishing it for the first time. If you adopt it, expect a larger first run. + +#### Minimum tool versions raised + +| Tool | Version Constraint | +|-----------------------------|--------------------| +| `composer/composer` | `^2.10` | +| `friendsofphp/php-cs-fixer` | `^3.95` | +| `phpstan/phpstan` | `^2.2` | +| `rector/rector` | `^2.6` | +| `squizlabs/php_codesniffer` | `^3.13` | +| `vimeo/psalm` | `^6.17` | + +Plus these new dependencies were added + +| Tool | Version Constraint | +|---------------------------------|--------------------| +| `phpstan/phpstan-phpunit` | `^2.10` | +| `jetbrains/phpstorm-attributes` | `^2.0` | + + +See `composer.json` for the authoritative list. + +### New in 5.0 + +- **PHPStan config template.** + - `vendor/bin/code-tools publish:config phpstan` + - Writes a `phpstan.neon` starting at level 1 with the PHPUnit extension wired in + See [docs/PHPStan](docs/PHPStan/README.md). +- **`.editorconfig`** + - Covers `.neon` files**, in the same 2-space group as JSON and YAML. +- **Hook installation without plugins.** + - `Kununu\CodeTools\PHPCSFixer\CsFixerPlugin::installGitHooks` + - Can be wired into your own `post-install-cmd`/`post-update-cmd` if you install with `--no-plugins`. + +### What did not change + +Worth stating, because the namespace move suggests otherwise: + +- **Your `phpcs.xml` rule references.** + - The standard is still called `Kununu` and the sniffs still live in `Kununu\Sniffs\` + - So `` and friends keep working untouched + - Only `installed_paths` moved. +- **The `code-tools` binary** + - Same `publish:config` command, same tool names, same published filenames + - `phpstan` is added alongside them. +- **The composer commands** + - `kununu:cs-fixer` + - `kununu:cs-fixer-git-hook` + - Including their arguments and aliases. +- **`dist/psalm.xml.dist`** + - Is byte-identical to 4.x. +- **`dist/php-cs-fixer.php.dist` rules.** + - The only edits are cosmetic. + +### Changes + +- Documentation folders were renamed + - `CodeSniffer` → `PHPCodeSniffer` + - `CsFixer` → `PHPCSFixer` + - `PhpInK8s` → `PHPInK8s` + - Bookmarks and deep links need updating, but no code references them. diff --git a/bin/code-tools b/bin/code-tools index 6bb4b03..64b6d6e 100755 --- a/bin/code-tools +++ b/bin/code-tools @@ -10,19 +10,23 @@ if [ -d "$PROJECT_ROOT/services/vendor/kununu/code-tools" ]; then readonly CONFIG_DIR="$PROJECT_ROOT/services/vendor/kununu/code-tools" elif [ -d "$PROJECT_ROOT/vendor/kununu/code-tools" ]; then readonly CONFIG_DIR="$PROJECT_ROOT/vendor/kununu/code-tools" -elif [ -d "$PROJECT_ROOT" ]; then +elif [ -d "$PROJECT_ROOT/dist" ]; then + # Running from inside the code-tools repository itself readonly CONFIG_DIR="$PROJECT_ROOT" else echo "❌ Could not find 'vendor/kununu/code-tools'." exit 1 fi +# Single source of truth: ":