From c7884bfc9e067e1ff5970ceeab06c60627103cad Mon Sep 17 00:00:00 2001 From: Linus Metzler Date: Thu, 26 Jun 2025 12:06:59 +0200 Subject: [PATCH 1/5] rewrite repo --- .github/dependabot.yml | 9 ++ .github/workflows/php-cs-fixer.yml | 27 ++++ .github/workflows/phpstan.yml | 27 ++++ .github/workflows/phpunit.yml | 27 ++++ .github/workflows/rector.yml | 27 ++++ .github/workflows/test.yaml | 20 --- .gitignore | 5 + .php-cs-fixer.dist.php | 16 ++- README.md | 116 ++++++++++++++- composer.json | 38 ++++- phpstan.neon | 10 ++ phpunit.xml | 17 +++ rector.php | 24 ++++ src/Config.php | 211 ---------------------------- src/ConfigFactory.php | 32 +++++ src/RuleSet.php | 173 +++++++++++++++++++++++ tests/Unit/PhpCsFixerConfigTest.php | 164 +++++++++++++++++++++ 17 files changed, 702 insertions(+), 241 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/php-cs-fixer.yml create mode 100644 .github/workflows/phpstan.yml create mode 100644 .github/workflows/phpunit.yml create mode 100644 .github/workflows/rector.yml delete mode 100644 .github/workflows/test.yaml create mode 100644 phpstan.neon create mode 100644 phpunit.xml create mode 100644 rector.php delete mode 100644 src/Config.php create mode 100644 src/ConfigFactory.php create mode 100644 src/RuleSet.php create mode 100644 tests/Unit/PhpCsFixerConfigTest.php diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a8d57dd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "dependencies" diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml new file mode 100644 index 0000000..e834bc8 --- /dev/null +++ b/.github/workflows/php-cs-fixer.yml @@ -0,0 +1,27 @@ +name: PHP-CS-Fixer + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + php-cs-fixer: + name: PHP-CS-Fixer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run PHP-CS-Fixer + run: composer cs-check diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..3854536 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,27 @@ +name: PHPStan + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run PHPStan + run: composer phpstan diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 0000000..a85fab0 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,27 @@ +name: PHPUnit + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + phpunit: + name: PHPUnit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run PHPUnit + run: composer test diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml new file mode 100644 index 0000000..074798a --- /dev/null +++ b/.github/workflows/rector.yml @@ -0,0 +1,27 @@ +name: Rector + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + rector: + name: Rector + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + tools: composer:v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run Rector + run: composer rector-dry-run diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index cf1a1d8..0000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: test -on: - push: - -jobs: - test: - strategy: - matrix: - php: ['8.0', '8.2'] - composer: ['--prefer-lowest', ''] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: composer:v2 - - run: composer update ${{ matrix.composer }} - - run: composer validate - - run: vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.gitignore b/.gitignore index 3f1433d..54f5fb2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ vendor/ .php-cs-fixer.cache composer.lock +.idea +.phpstan-cache + +/vendor/ +/.phpunit.cache/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 78d4513..5b3c5ad 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,11 +1,13 @@ in('src') -; +require_once __DIR__ . '/vendor/autoload.php'; -$config = new Valantic\PhpCsFixerConfig\Config(); +use Valantic\PhpCsFixerConfig\ConfigFactory; -return $config - ->setFinder($finder) -; +return ConfigFactory::createValanticOpinionatedConfig() + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + ) + ->setRiskyAllowed(true); diff --git a/README.md b/README.md index e298fcc..2476d30 100644 --- a/README.md +++ b/README.md @@ -1 +1,115 @@ -# php-cs-fixer-config +# PHP-CS-Fixer Config for Valantic Projects + +This package provides standard PHP-CS-Fixer configurations used in projects built by Valantic. + +## Installation + +```bash +composer require --dev valantic/php-cs-fixer-config +``` + +## Usage + +Create a `.php-cs-fixer.php` file in your project root with one of the following configurations: + +### Basic Configuration + +```php +setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') +); + +return $config; +``` + +### Opinionated Configuration + +```php +setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') +); + +return $config; +``` + +### Custom Configuration + +You can also customize the configuration by adding additional rules: + +```php + ['syntax' => 'short'], + // Add your custom rules here +]); + +$config->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') +); + +return $config; +``` + +## Available Rulesets + +- **valantic**: Basic ruleset that every project agrees with (automatically includes PHP version specific migration rules based on the current PHP version) +- **valantic:risky**: Risky ruleset that includes additional rules marked as risky (automatically includes PHP version specific migration rules based on the current PHP version) +- **valantic:opinionated**: Opinionated ruleset based on discussions + +## Development + +This package provides several Composer scripts to help with development: + +```bash +# Run PHP-CS-Fixer in dry-run mode with diff output +composer cs-check + +# Run PHP-CS-Fixer to fix code style issues +composer cs-fix + +# Run PHPStan for static analysis +composer phpstan + +# Run Rector in dry-run mode +composer rector-dry-run + +# Run Rector and apply changes +composer rector + +# Run PHPUnit tests +composer test + +# Run all checks (cs-check, phpstan, rector-dry-run, test) +composer check +``` + +## License + +MIT diff --git a/composer.json b/composer.json index 8d569f4..8ca7c9b 100644 --- a/composer.json +++ b/composer.json @@ -3,12 +3,46 @@ "description": "Provides a standard php-cs-fixer configuration used in projects built by Valantic.", "license": "MIT", "require": { - "php": "^8.0", - "friendsofphp/php-cs-fixer": "^3.16" + "php": "^8.1", + "friendsofphp/php-cs-fixer": "^3.76" + }, + "require-dev": { + "phpunit/phpunit": "^12.2.5", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-strict-rules": "^2.0.4", + "rector/rector": "^2.1", + "roave/security-advisories": "dev-latest", + "phpstan/extension-installer": "^1.4.3" }, "autoload": { "psr-4": { "Valantic\\PhpCsFixerConfig\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "Valantic\\PhpCsFixerConfig\\Tests\\": "tests" + } + }, + "scripts": { + "cs-check": "php-cs-fixer fix --dry-run --diff", + "cs-fix": "php-cs-fixer fix", + "phpstan": "phpstan analyse", + "test": "phpunit", + "rector": "rector process", + "rector-dry-run": "rector process --dry-run", + "check": [ + "@cs-check", + "@phpstan", + "@rector-dry-run", + "@test" + ], + "post-update-cmd": "composer bump -D" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..65dcb04 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,10 @@ +parameters: + level: 8 + paths: + - src + - tests + excludePaths: + - vendor + tmpDir: .phpstan-cache + strictRules: + dynamicCallOnStaticMethod: false diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..49f3705 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests/Unit + + + + + src + + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..2b2efea --- /dev/null +++ b/rector.php @@ -0,0 +1,24 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + privatization: true, + naming: false, + instanceOf: true, + earlyReturn: true, + strictBooleans: true + ) + ->withPhpSets() + ->withAttributesSets(symfony: true, phpunit: true); diff --git a/src/Config.php b/src/Config.php deleted file mode 100644 index cc1de1f..0000000 --- a/src/Config.php +++ /dev/null @@ -1,211 +0,0 @@ - true, - 'align_multiline_comment' => true, - 'array_indentation' => true, - 'array_syntax' => true, - 'backtick_to_shell_exec' => true, - 'binary_operator_spaces' => true, - 'blank_line_before_statement' => [ - 'statements' => [ - 'return', - ], - ], - 'cast_spaces' => true, - 'class_attributes_separation' => [ - 'elements' => [ - 'method' => 'one', - ], - ], - 'class_definition' => [ - 'single_line' => true, - ], - 'class_reference_name_casing' => true, - 'clean_namespace' => true, - 'concat_space' => ['spacing' => 'one'], - 'curly_braces_position' => [ - 'allow_single_line_anonymous_functions' => true, - 'allow_single_line_empty_anonymous_classes' => true, - ], - 'declare_parentheses' => true, - 'echo_tag_syntax' => true, - 'empty_loop_body' => ['style' => 'braces'], - 'empty_loop_condition' => true, - 'fully_qualified_strict_types' => true, - 'function_typehint_space' => true, - 'general_phpdoc_tag_rename' => [ - 'replacements' => [ - 'inheritDocs' => 'inheritDoc', - ], - ], - 'global_namespace_import' => [ - 'import_classes' => false, - 'import_constants' => false, - 'import_functions' => false, - ], - 'include' => true, - 'increment_style' => true, - 'integer_literal_case' => true, - 'lambda_not_used_import' => true, - 'linebreak_after_opening_tag' => true, - 'magic_constant_casing' => true, - 'magic_method_casing' => true, - 'method_argument_space' => [ - 'on_multiline' => 'ignore', - ], - 'method_chaining_indentation' => true, - 'multiline_whitespace_before_semicolons' => [ - 'strategy' => 'new_line_for_chained_calls', - ], - 'native_function_casing' => true, - 'native_function_type_declaration_casing' => true, - 'no_alias_language_construct_call' => true, - 'no_alternative_syntax' => true, - 'no_binary_string' => true, - 'no_blank_lines_after_phpdoc' => true, - 'no_empty_comment' => true, - 'no_empty_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_blank_lines' => [ - 'tokens' => [ - 'attribute', - 'case', - 'continue', - 'curly_brace_block', - 'default', - 'extra', - 'parenthesis_brace_block', - 'square_brace_block', - 'switch', - 'throw', - 'use', - ], - ], - 'no_leading_namespace_whitespace' => true, - 'no_mixed_echo_print' => true, - 'no_multiline_whitespace_around_double_arrow' => true, - 'no_null_property_initialization' => true, - 'no_short_bool_cast' => true, - 'no_singleline_whitespace_before_semicolons' => true, - 'no_spaces_around_offset' => true, - 'no_superfluous_phpdoc_tags' => [ - 'allow_mixed' => true, - 'allow_unused_params' => true, - ], - 'no_trailing_comma_in_singleline' => true, - 'no_unneeded_control_parentheses' => [ - 'statements' => [ - 'break', - 'clone', - 'continue', - 'echo_print', - 'others', - 'return', - 'switch_case', - 'yield', - 'yield_from', - ], - ], - 'no_unneeded_curly_braces' => [ - 'namespaces' => true, - ], - 'no_unneeded_import_alias' => true, - 'no_unset_cast' => true, - 'no_unused_imports' => true, - 'no_useless_concat_operator' => true, - 'no_useless_nullsafe_operator' => true, - 'no_whitespace_before_comma_in_array' => true, - 'normalize_index_brace' => true, - 'object_operator_without_whitespace' => true, - 'operator_linebreak' => [ - 'only_booleans' => true, - ], - 'ordered_imports' => [ - 'imports_order' => [ - 'class', - 'function', - 'const', - ], - 'sort_algorithm' => 'alpha', - ], - 'php_unit_fqcn_annotation' => true, - 'php_unit_method_casing' => true, - 'phpdoc_align' => [ - 'align' => 'left', - ], - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_indent' => true, - 'phpdoc_inline_tag_normalizer' => true, - 'phpdoc_no_access' => true, - 'phpdoc_no_alias_tag' => true, - 'phpdoc_no_package' => true, - 'phpdoc_no_useless_inheritdoc' => true, - 'phpdoc_order' => [ - 'order' => [ - 'param', - 'return', - 'throws', - ], - ], - 'phpdoc_return_self_reference' => true, - 'phpdoc_scalar' => true, - 'phpdoc_separation' => true, - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_summary' => true, - 'phpdoc_tag_type' => [ - 'tags' => [ - 'inheritDoc' => 'inline', - ], - ], - 'phpdoc_to_comment' => true, - 'phpdoc_trim' => true, - 'phpdoc_trim_consecutive_blank_line_separation' => true, - 'phpdoc_types' => true, - 'phpdoc_types_order' => [ - 'null_adjustment' => 'always_last', - 'sort_algorithm' => 'none', - ], - 'phpdoc_var_without_name' => true, - 'protected_to_private' => true, - 'return_assignment' => true, - 'semicolon_after_instruction' => true, - 'simple_to_complex_string_variable' => true, - 'single_class_element_per_statement' => true, - 'single_import_per_statement' => true, - 'single_line_comment_spacing' => true, - 'single_line_comment_style' => [ - 'comment_types' => [ - 'hash', - ], - ], - 'single_line_throw' => true, - 'single_quote' => true, - 'single_space_around_construct' => true, - 'space_after_semicolon' => [ - 'remove_in_empty_for_expressions' => true, - ], - 'standardize_increment' => true, - 'standardize_not_equals' => true, - 'switch_continue_to_break' => true, - 'trailing_comma_in_multiline' => [ - 'elements' => ['arguments', 'arrays', 'match', 'parameters'], - ], - 'trim_array_spaces' => true, - 'types_spaces' => true, - 'unary_operator_spaces' => true, - 'whitespace_after_comma_in_array' => true, - 'yoda_style' => [ - 'equal' => false, - 'identical' => false, - 'less_and_greater' => false, - ], - ]; - } -} diff --git a/src/ConfigFactory.php b/src/ConfigFactory.php new file mode 100644 index 0000000..c5bfe9a --- /dev/null +++ b/src/ConfigFactory.php @@ -0,0 +1,32 @@ +> $additionalRules + */ + public static function createValanticConfig(array $additionalRules = []): Config + { + $config = new Config(); + $config->setRules(array_merge(RuleSet::getValanticRules(), $additionalRules)); + + return $config; + } + + /** + * @param array> $additionalRules + */ + public static function createValanticOpinionatedConfig(array $additionalRules = []): Config + { + $config = new Config(); + $config->setRules(array_merge(RuleSet::getValanticOpinionatedRules(), $additionalRules)); + + return $config; + } +} diff --git a/src/RuleSet.php b/src/RuleSet.php new file mode 100644 index 0000000..a4f7232 --- /dev/null +++ b/src/RuleSet.php @@ -0,0 +1,173 @@ +> + */ + public static function getValanticRules(): array + { + $rules = [ + '@PER-CS2.0' => true, + '@PER-CS2.0:risky' => true, + '@Symfony' => true, + '@Symfony:risky' => true, + 'array_push' => false, + 'native_constant_invocation' => false, + 'native_function_invocation' => false, + 'no_useless_return' => true, + 'self_accessor' => false, // do not enable self_accessor as it breaks pimcore models relying on get_called_class() + 'strict_comparison' => true, + 'strict_param' => true, + 'yoda_style' => [ + 'equal' => false, + 'identical' => false, + 'less_and_greater' => false, + ], + ]; + + return self::addPhpVersionSpecificRules($rules); + } + + /** + * @return array> + */ + public static function getValanticOpinionatedRules(): array + { + $opinionatedRules = [ + 'blank_line_before_statement' => [ + 'statements' => [ + 'break', + 'case', + 'continue', + 'declare', + 'default', + 'do', + 'exit', + 'for', + 'foreach', + 'goto', + 'if', + 'include', + 'include_once', + // 'phpdoc', + 'require', + 'require_once', + 'return', + 'switch', + 'throw', + 'try', + 'while', + 'yield', + 'yield_from', + ], + ], + 'concat_space' => ['spacing' => 'one'], + // TODO: 'declare_strict_types' => '...', + // TODO: 'function_declaration' => ['closure_fn_spacing' => '...'], + // TODO: 'global_namespace_import' => '...', + 'increment_style' => [ + 'style' => 'post', + ], + // TODO: 'method_argument_space' => ['attribute_placement' => '...', 'on_multiline' => '...'], + // TODO: 'method_chaining_indentation' => '...', + // TODO: 'multiline_comment_opening_closing' => '...', + 'multiline_whitespace_before_semicolons' => true, + // TODO: 'no_superfluous_phpdoc_tags' => '...', + // TODO: 'no_unset_on_property' => '...', + // TODO: 'no_useless_else' => '...', + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant_public', + 'constant_protected', + 'constant_private', + 'case', + 'property_public', + 'property_public_static', + 'property_protected', + 'property_protected_static', + 'property_private', + 'property_private_static', + 'construct', + 'destruct', + 'magic', + 'phpunit', + 'method_public', + 'method_public_abstract', + 'method_public_static', + 'method_protected', + 'method_protected_abstract', + 'method_protected_static', + 'method_private', + ], + ], + // TODO: 'phpdoc_align' => '...', + // TODO: 'phpdoc_order' => '...', + // TODO: 'phpdoc_tag_casing' => '...', + // TODO: 'phpdoc_to_comment' => '...', + // TODO: 'phpdoc_annotation_without_dot' => '...', + 'phpdoc_summary' => false, + 'ordered_imports' => [ + 'imports_order' => ['class', 'function', 'const'], + 'sort_algorithm' => 'alpha', + ], + // TODO: 'regular_callable_call' => '...', + // TODO: 'return_assignment' => '...', + 'single_line_throw' => false, + 'trailing_comma_in_multiline' => [ + 'after_heredoc' => true, + 'elements' => ['arguments', 'array_destructuring', 'arrays', 'match', 'parameters'], + ], + ]; + + // Extend the valantic rules with the opinionated rules + return [ + ...self::getValanticRules(), + ...$opinionatedRules, + ]; + } + + private static function getCurrentPhpVersion(): string + { + return PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; + } + + /** + * @param array> $rules + * + * @return array> + */ + private static function addPhpVersionSpecificRules(array $rules): array + { + $phpVersion = self::getCurrentPhpVersion(); + + $version = (int) str_replace('.', '', $phpVersion); + + $availableRuleSets = \PhpCsFixer\RuleSet\RuleSets::getSetDefinitionNames(); + + for ($majorMinor = 80; $majorMinor <= 99; $majorMinor++) { + if ($majorMinor > $version) { + break; + } + + $migrationSet = sprintf('@PHP%dMigration', $majorMinor); + + if (in_array($migrationSet, $availableRuleSets, true)) { + $rules[$migrationSet] = true; + } + + $riskyMigrationSet = sprintf('%s:risky', $migrationSet); + + if (in_array($riskyMigrationSet, $availableRuleSets, true)) { + $rules[$riskyMigrationSet] = true; + } + } + + return $rules; + } +} diff --git a/tests/Unit/PhpCsFixerConfigTest.php b/tests/Unit/PhpCsFixerConfigTest.php new file mode 100644 index 0000000..5979d9f --- /dev/null +++ b/tests/Unit/PhpCsFixerConfigTest.php @@ -0,0 +1,164 @@ +assertNotEmpty($rules); + + $this->assertArrayHasKey('@PER-CS2.0', $rules); + $this->assertArrayHasKey('@PER-CS2.0:risky', $rules); + $this->assertArrayHasKey('@Symfony', $rules); + $this->assertArrayHasKey('@Symfony:risky', $rules); + $this->assertArrayHasKey('array_push', $rules); + $this->assertArrayHasKey('yoda_style', $rules); + + // Check that PHP version specific rules are included + $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; + $version = (int) str_replace('.', '', $phpVersion); + + if ($version >= 80) { + $this->assertArrayHasKey('@PHP80Migration', $rules); + + // Check if the risky rule set exists + $availableRuleSets = RuleSets::getSetDefinitionNames(); + + if (in_array('@PHP80Migration:risky', $availableRuleSets, true)) { + $this->assertArrayHasKey('@PHP80Migration:risky', $rules); + } + } + + if ($version >= 81) { + $this->assertArrayHasKey('@PHP81Migration', $rules); + } + } + + /** + * Test that getValanticOpinionatedRules() returns a non-empty array of rules. + */ + public function testGetValanticOpinionatedRules(): void + { + $rules = RuleSet::getValanticOpinionatedRules(); + + $this->assertNotEmpty($rules); + + // Check if specific opinionated rules are included + $expectedOpinionatedRules = [ + 'blank_line_before_statement', + 'concat_space', + 'ordered_imports', + 'trailing_comma_in_multiline', + ]; + + foreach ($expectedOpinionatedRules as $rule) { + $this->assertArrayHasKey($rule, $rules, sprintf("Rule '%s' should be included in the opinionated ruleset", $rule)); + } + + // Check that the opinionated rules extend the valantic rules + $valanticRules = RuleSet::getValanticRules(); + + foreach (array_keys($valanticRules) as $rule) { + $this->assertArrayHasKey($rule, $rules, sprintf("valantic rule '%s' should be included in the opinionated ruleset", $rule)); + } + } + + /** + * Test that createValanticConfig() returns a Config object with the basic ruleset. + */ + public function testCreateValanticConfig(): void + { + $config = ConfigFactory::createValanticConfig(); + + $this->assertInstanceOf(Config::class, $config); + + $rules = $config->getRules(); + $this->assertNotEmpty($rules); + + // Check that basic rules are included + $this->assertArrayHasKey('@PER-CS2.0', $rules); + $this->assertArrayHasKey('@PER-CS2.0:risky', $rules); + $this->assertArrayHasKey('@Symfony', $rules); + $this->assertArrayHasKey('@Symfony:risky', $rules); + + // Check that risky is not allowed by default + $this->assertFalse($config->getRiskyAllowed()); + } + + /** + * Test that createValanticConfig() with additional rules merges them correctly. + */ + public function testCreateValanticConfigWithAdditionalRules(): void + { + $additionalRules = [ + 'array_syntax' => ['syntax' => 'short'], + 'custom_rule' => true, + ]; + + $config = ConfigFactory::createValanticConfig($additionalRules); + + $rules = $config->getRules(); + $this->assertArrayHasKey('array_syntax', $rules); + $this->assertArrayHasKey('custom_rule', $rules); + $this->assertEquals(['syntax' => 'short'], $rules['array_syntax']); + $this->assertTrue($rules['custom_rule']); + } + + /** + * Test that createValanticOpinionatedConfig() returns a Config object with the opinionated ruleset. + */ + public function testCreateValanticOpinionatedConfig(): void + { + $config = ConfigFactory::createValanticOpinionatedConfig(); + + $this->assertInstanceOf(Config::class, $config); + + $rules = $config->getRules(); + $this->assertNotEmpty($rules); + + // Check if specific rules are included + $expectedRules = [ + 'blank_line_before_statement', + 'concat_space', + 'ordered_imports', + 'trailing_comma_in_multiline', + ]; + + foreach ($expectedRules as $rule) { + $this->assertArrayHasKey($rule, $rules, sprintf("Rule '%s' should be included in the opinionated ruleset", $rule)); + } + } + + /** + * Test that createValanticOpinionatedConfig() with additional rules merges them correctly. + */ + public function testCreateValanticOpinionatedConfigWithAdditionalRules(): void + { + $additionalRules = [ + 'array_syntax' => ['syntax' => 'short'], + 'custom_rule' => true, + ]; + + $config = ConfigFactory::createValanticOpinionatedConfig($additionalRules); + + $rules = $config->getRules(); + $this->assertArrayHasKey('array_syntax', $rules); + $this->assertArrayHasKey('custom_rule', $rules); + $this->assertEquals(['syntax' => 'short'], $rules['array_syntax']); + $this->assertTrue($rules['custom_rule']); + } +} From 6c8ad84ac34c305c4309a77062a06ae28e47d543 Mon Sep 17 00:00:00 2001 From: Linus Metzler Date: Wed, 9 Jul 2025 16:34:49 +0200 Subject: [PATCH 2/5] update rules from team discussion --- src/RuleSet.php | 61 +++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/RuleSet.php b/src/RuleSet.php index a4f7232..4f39c18 100644 --- a/src/RuleSet.php +++ b/src/RuleSet.php @@ -67,57 +67,68 @@ public static function getValanticOpinionatedRules(): array ], ], 'concat_space' => ['spacing' => 'one'], - // TODO: 'declare_strict_types' => '...', - // TODO: 'function_declaration' => ['closure_fn_spacing' => '...'], - // TODO: 'global_namespace_import' => '...', + 'declare_strict_types' => true, 'increment_style' => [ 'style' => 'post', ], - // TODO: 'method_argument_space' => ['attribute_placement' => '...', 'on_multiline' => '...'], - // TODO: 'method_chaining_indentation' => '...', - // TODO: 'multiline_comment_opening_closing' => '...', - 'multiline_whitespace_before_semicolons' => true, - // TODO: 'no_superfluous_phpdoc_tags' => '...', - // TODO: 'no_unset_on_property' => '...', - // TODO: 'no_useless_else' => '...', + 'method_argument_space' => [ + 'attribute_placement' => 'standalone', + 'on_multiline' => 'ensure_fully_multiline', + ], + 'method_chaining_indentation' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], + 'no_superfluous_phpdoc_tags' => ['allow_hidden_params' => false, 'remove_inheritdoc' => true], + 'no_unset_on_property' => true, + 'no_useless_else' => true, 'ordered_class_elements' => [ 'order' => [ 'use_trait', + 'case', + 'constant', 'constant_public', 'constant_protected', 'constant_private', - 'case', - 'property_public', + 'property', + 'property_public_abstract', + 'property_protected_abstract', + 'property_static', 'property_public_static', - 'property_protected', 'property_protected_static', - 'property_private', 'property_private_static', + 'property_public', + 'property_protected', + 'property_private', 'construct', 'destruct', 'magic', 'phpunit', - 'method_public', + 'method', + 'method_abstract', 'method_public_abstract', - 'method_public_static', - 'method_protected', + 'method_public_abstract_static', 'method_protected_abstract', + 'method_protected_abstract_static', + 'method_private_abstract', + 'method_private_abstract_static', + 'method_static', + 'method_public_static', 'method_protected_static', + 'method_private_static', + 'method_public', + 'method_protected', 'method_private', ], ], - // TODO: 'phpdoc_align' => '...', - // TODO: 'phpdoc_order' => '...', - // TODO: 'phpdoc_tag_casing' => '...', - // TODO: 'phpdoc_to_comment' => '...', - // TODO: 'phpdoc_annotation_without_dot' => '...', - 'phpdoc_summary' => false, + 'phpdoc_align' => ['align' => 'left', 'spacing' => 1], + 'phpdoc_to_comment' => true, + 'phpdoc_annotation_without_dot' => false, 'ordered_imports' => [ 'imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha', ], - // TODO: 'regular_callable_call' => '...', - // TODO: 'return_assignment' => '...', + 'regular_callable_call' => true, + 'return_assignment' => true, 'single_line_throw' => false, 'trailing_comma_in_multiline' => [ 'after_heredoc' => true, From ff853e50fa3449c2974da63eca281241f98109a7 Mon Sep 17 00:00:00 2001 From: Linus Metzler Date: Wed, 9 Jul 2025 21:11:32 +0200 Subject: [PATCH 3/5] merge opinionated ruleset into default ruleset --- .php-cs-fixer.dist.php | 2 +- README.md | 22 --------- composer.json | 2 +- src/ConfigFactory.php | 11 ----- src/RuleSet.php | 17 +------ tests/Unit/PhpCsFixerConfigTest.php | 73 ----------------------------- 6 files changed, 3 insertions(+), 124 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5b3c5ad..a7656b6 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -4,7 +4,7 @@ use Valantic\PhpCsFixerConfig\ConfigFactory; -return ConfigFactory::createValanticOpinionatedConfig() +return ConfigFactory::createValanticConfig() ->setFinder( PhpCsFixer\Finder::create() ->in(__DIR__ . '/src') diff --git a/README.md b/README.md index 2476d30..eb4954e 100644 --- a/README.md +++ b/README.md @@ -32,26 +32,6 @@ $config->setFinder( return $config; ``` -### Opinionated Configuration - -```php -setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') -); - -return $config; -``` - ### Custom Configuration You can also customize the configuration by adding additional rules: @@ -80,8 +60,6 @@ return $config; ## Available Rulesets - **valantic**: Basic ruleset that every project agrees with (automatically includes PHP version specific migration rules based on the current PHP version) -- **valantic:risky**: Risky ruleset that includes additional rules marked as risky (automatically includes PHP version specific migration rules based on the current PHP version) -- **valantic:opinionated**: Opinionated ruleset based on discussions ## Development diff --git a/composer.json b/composer.json index 8ca7c9b..6695adb 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "friendsofphp/php-cs-fixer": "^3.76" }, "require-dev": { - "phpunit/phpunit": "^12.2.5", + "phpunit/phpunit": "^12.2.6", "phpstan/phpstan": "^2.1.17", "phpstan/phpstan-deprecation-rules": "^2.0.3", "phpstan/phpstan-strict-rules": "^2.0.4", diff --git a/src/ConfigFactory.php b/src/ConfigFactory.php index c5bfe9a..e9b4434 100644 --- a/src/ConfigFactory.php +++ b/src/ConfigFactory.php @@ -18,15 +18,4 @@ public static function createValanticConfig(array $additionalRules = []): Config return $config; } - - /** - * @param array> $additionalRules - */ - public static function createValanticOpinionatedConfig(array $additionalRules = []): Config - { - $config = new Config(); - $config->setRules(array_merge(RuleSet::getValanticOpinionatedRules(), $additionalRules)); - - return $config; - } } diff --git a/src/RuleSet.php b/src/RuleSet.php index 4f39c18..a8f70b0 100644 --- a/src/RuleSet.php +++ b/src/RuleSet.php @@ -28,17 +28,6 @@ public static function getValanticRules(): array 'identical' => false, 'less_and_greater' => false, ], - ]; - - return self::addPhpVersionSpecificRules($rules); - } - - /** - * @return array> - */ - public static function getValanticOpinionatedRules(): array - { - $opinionatedRules = [ 'blank_line_before_statement' => [ 'statements' => [ 'break', @@ -136,11 +125,7 @@ public static function getValanticOpinionatedRules(): array ], ]; - // Extend the valantic rules with the opinionated rules - return [ - ...self::getValanticRules(), - ...$opinionatedRules, - ]; + return self::addPhpVersionSpecificRules($rules); } private static function getCurrentPhpVersion(): string diff --git a/tests/Unit/PhpCsFixerConfigTest.php b/tests/Unit/PhpCsFixerConfigTest.php index 5979d9f..0567a1f 100644 --- a/tests/Unit/PhpCsFixerConfigTest.php +++ b/tests/Unit/PhpCsFixerConfigTest.php @@ -48,35 +48,6 @@ public function testGetValanticRules(): void } } - /** - * Test that getValanticOpinionatedRules() returns a non-empty array of rules. - */ - public function testGetValanticOpinionatedRules(): void - { - $rules = RuleSet::getValanticOpinionatedRules(); - - $this->assertNotEmpty($rules); - - // Check if specific opinionated rules are included - $expectedOpinionatedRules = [ - 'blank_line_before_statement', - 'concat_space', - 'ordered_imports', - 'trailing_comma_in_multiline', - ]; - - foreach ($expectedOpinionatedRules as $rule) { - $this->assertArrayHasKey($rule, $rules, sprintf("Rule '%s' should be included in the opinionated ruleset", $rule)); - } - - // Check that the opinionated rules extend the valantic rules - $valanticRules = RuleSet::getValanticRules(); - - foreach (array_keys($valanticRules) as $rule) { - $this->assertArrayHasKey($rule, $rules, sprintf("valantic rule '%s' should be included in the opinionated ruleset", $rule)); - } - } - /** * Test that createValanticConfig() returns a Config object with the basic ruleset. */ @@ -117,48 +88,4 @@ public function testCreateValanticConfigWithAdditionalRules(): void $this->assertEquals(['syntax' => 'short'], $rules['array_syntax']); $this->assertTrue($rules['custom_rule']); } - - /** - * Test that createValanticOpinionatedConfig() returns a Config object with the opinionated ruleset. - */ - public function testCreateValanticOpinionatedConfig(): void - { - $config = ConfigFactory::createValanticOpinionatedConfig(); - - $this->assertInstanceOf(Config::class, $config); - - $rules = $config->getRules(); - $this->assertNotEmpty($rules); - - // Check if specific rules are included - $expectedRules = [ - 'blank_line_before_statement', - 'concat_space', - 'ordered_imports', - 'trailing_comma_in_multiline', - ]; - - foreach ($expectedRules as $rule) { - $this->assertArrayHasKey($rule, $rules, sprintf("Rule '%s' should be included in the opinionated ruleset", $rule)); - } - } - - /** - * Test that createValanticOpinionatedConfig() with additional rules merges them correctly. - */ - public function testCreateValanticOpinionatedConfigWithAdditionalRules(): void - { - $additionalRules = [ - 'array_syntax' => ['syntax' => 'short'], - 'custom_rule' => true, - ]; - - $config = ConfigFactory::createValanticOpinionatedConfig($additionalRules); - - $rules = $config->getRules(); - $this->assertArrayHasKey('array_syntax', $rules); - $this->assertArrayHasKey('custom_rule', $rules); - $this->assertEquals(['syntax' => 'short'], $rules['array_syntax']); - $this->assertTrue($rules['custom_rule']); - } } From 73e45beab582ad2a2979db0722af3c17c480cdb4 Mon Sep 17 00:00:00 2001 From: Linus Metzler Date: Wed, 9 Jul 2025 21:15:20 +0200 Subject: [PATCH 4/5] update README --- README.md | 58 ++++++++++++++----------------------------------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index eb4954e..e915bc1 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ This package provides standard PHP-CS-Fixer configurations used in projects buil composer require --dev valantic/php-cs-fixer-config ``` +> **Note:** This package requires PHP 8.1 or higher. + ## Usage -Create a `.php-cs-fixer.php` file in your project root with one of the following configurations: +Create a `.php-cs-fixer.php` or `.php-cs-fixer.dist.php` file in your project root with one of the following configurations: ### Basic Configuration @@ -21,46 +23,20 @@ require_once __DIR__ . '/vendor/autoload.php'; use Valantic\PhpCsFixerConfig\ConfigFactory; -$config = ConfigFactory::createValanticConfig(); - -$config->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') -); - -return $config; -``` - -### Custom Configuration - -You can also customize the configuration by adding additional rules: - -```php - ['syntax' => 'short'], - // Add your custom rules here -]); - -$config->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') -); - -return $config; +return ConfigFactory::createValanticConfig([ + 'declare_strict_types' => false, + // Add your custom rules here + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + ) + // Enable risky rules (recommended as the ruleset includes risky rules) + ->setRiskyAllowed(true) +; ``` -## Available Rulesets - -- **valantic**: Basic ruleset that every project agrees with (automatically includes PHP version specific migration rules based on the current PHP version) - ## Development This package provides several Composer scripts to help with development: @@ -87,7 +63,3 @@ composer test # Run all checks (cs-check, phpstan, rector-dry-run, test) composer check ``` - -## License - -MIT From 58a386a92dacec5704cafa21d7d880ccf9f3df43 Mon Sep 17 00:00:00 2001 From: Linus Metzler Date: Wed, 9 Jul 2025 21:16:42 +0200 Subject: [PATCH 5/5] add license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3be2557 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 valantic CEC Schweiz AG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.