diff --git a/README.md b/README.md index 62e17be..469db44 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,12 @@ return ECSConfig::configure() __DIR__ . '/vendor/lmc/coding-standard/ecs.php', ] ) + ->withRules( + [ + // PHPUnit attributes must be used over their respective PHPDoc-based annotations. (Use with PHPUnit 10+.) + PhpUnitAttributesFixer::class, + ] + ) // Enforce line-length to 120 characters ->withConfiguredRule(LineLengthSniff::class, ['absoluteLineLimit' => 120]) // Tests must have @test annotation diff --git a/composer.json b/composer.json index e328c91..b785d32 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,10 @@ ], "require": { "php": "^8.0", - "friendsofphp/php-cs-fixer": "^3.0", - "slevomat/coding-standard": "^8.0", + "friendsofphp/php-cs-fixer": "^3.47.1", + "slevomat/coding-standard": "^8.6", "squizlabs/php_codesniffer": "^3.9", - "symplify/easy-coding-standard": "^12.1.5" + "symplify/easy-coding-standard": "^12.1.9" }, "require-dev": { "ergebnis/composer-normalize": "^2.13.2", diff --git a/ecs-internal.php b/ecs-internal.php index ac83458..e4fa5ee 100644 --- a/ecs-internal.php +++ b/ecs-internal.php @@ -5,7 +5,7 @@ use Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer; use Symplify\EasyCodingStandard\Config\ECSConfig; -/** +/* * Internal rules configuration for the lmc/coding-standard project itself */ return ECSConfig::configure() diff --git a/ecs.php b/ecs.php index 5d49511..60049c6 100644 --- a/ecs.php +++ b/ecs.php @@ -43,9 +43,14 @@ use PhpCsFixer\Fixer\ArrayNotation\NormalizeIndexBraceFixer; use PhpCsFixer\Fixer\ArrayNotation\TrimArraySpacesFixer; use PhpCsFixer\Fixer\ArrayNotation\WhitespaceAfterCommaInArrayFixer; +use PhpCsFixer\Fixer\AttributeNotation\AttributeEmptyParenthesesFixer; use PhpCsFixer\Fixer\Basic\BracesFixer; use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer; +use PhpCsFixer\Fixer\Basic\OctalNotationFixer; use PhpCsFixer\Fixer\Basic\PsrAutoloadingFixer; +use PhpCsFixer\Fixer\Basic\SingleLineEmptyBodyFixer; +use PhpCsFixer\Fixer\Casing\ClassReferenceNameCasingFixer; +use PhpCsFixer\Fixer\Casing\MagicConstantCasingFixer; use PhpCsFixer\Fixer\Casing\MagicMethodCasingFixer; use PhpCsFixer\Fixer\Casing\NativeFunctionCasingFixer; use PhpCsFixer\Fixer\Casing\NativeTypeDeclarationCasingFixer; @@ -54,10 +59,13 @@ use PhpCsFixer\Fixer\CastNotation\ShortScalarCastFixer; use PhpCsFixer\Fixer\ClassNotation\ClassAttributesSeparationFixer; use PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer; +use PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer; use PhpCsFixer\Fixer\ClassNotation\SelfAccessorFixer; use PhpCsFixer\Fixer\ClassNotation\SingleTraitInsertPerStatementFixer; use PhpCsFixer\Fixer\ClassNotation\VisibilityRequiredFixer; use PhpCsFixer\Fixer\Comment\NoEmptyCommentFixer; +use PhpCsFixer\Fixer\Comment\SingleLineCommentSpacingFixer; +use PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer; use PhpCsFixer\Fixer\ControlStructure\NoUselessElseFixer; use PhpCsFixer\Fixer\ControlStructure\SwitchContinueToBreakFixer; use PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer; @@ -65,17 +73,26 @@ use PhpCsFixer\Fixer\FunctionNotation\CombineNestedDirnameFixer; use PhpCsFixer\Fixer\FunctionNotation\FopenFlagOrderFixer; use PhpCsFixer\Fixer\FunctionNotation\FopenFlagsFixer; +use PhpCsFixer\Fixer\FunctionNotation\FunctionDeclarationFixer; use PhpCsFixer\Fixer\FunctionNotation\ImplodeCallFixer; use PhpCsFixer\Fixer\FunctionNotation\LambdaNotUsedImportFixer; +use PhpCsFixer\Fixer\FunctionNotation\MethodArgumentSpaceFixer; use PhpCsFixer\Fixer\FunctionNotation\NoUnreachableDefaultArgumentValueFixer; use PhpCsFixer\Fixer\FunctionNotation\NoUselessSprintfFixer; +use PhpCsFixer\Fixer\FunctionNotation\NullableTypeDeclarationForDefaultNullValueFixer; +use PhpCsFixer\Fixer\FunctionNotation\PhpdocToParamTypeFixer; +use PhpCsFixer\Fixer\FunctionNotation\PhpdocToPropertyTypeFixer; +use PhpCsFixer\Fixer\FunctionNotation\PhpdocToReturnTypeFixer; use PhpCsFixer\Fixer\FunctionNotation\ReturnTypeDeclarationFixer; use PhpCsFixer\Fixer\FunctionNotation\VoidReturnFixer; +use PhpCsFixer\Fixer\Import\FullyQualifiedStrictTypesFixer; use PhpCsFixer\Fixer\Import\NoLeadingImportSlashFixer; +use PhpCsFixer\Fixer\Import\NoUnneededImportAliasFixer; use PhpCsFixer\Fixer\Import\NoUnusedImportsFixer; use PhpCsFixer\Fixer\Import\OrderedImportsFixer; use PhpCsFixer\Fixer\LanguageConstruct\DeclareEqualNormalizeFixer; use PhpCsFixer\Fixer\LanguageConstruct\IsNullFixer; +use PhpCsFixer\Fixer\LanguageConstruct\NullableTypeDeclarationFixer; use PhpCsFixer\Fixer\LanguageConstruct\SingleSpaceAroundConstructFixer; use PhpCsFixer\Fixer\ListNotation\ListSyntaxFixer; use PhpCsFixer\Fixer\NamespaceNotation\BlankLinesBeforeNamespaceFixer; @@ -83,7 +100,9 @@ use PhpCsFixer\Fixer\NamespaceNotation\NoLeadingNamespaceWhitespaceFixer; use PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer; use PhpCsFixer\Fixer\Operator\ConcatSpaceFixer; +use PhpCsFixer\Fixer\Operator\LongToShorthandOperatorFixer; use PhpCsFixer\Fixer\Operator\NewWithParenthesesFixer; +use PhpCsFixer\Fixer\Operator\NoSpaceAroundDoubleColonFixer; use PhpCsFixer\Fixer\Operator\ObjectOperatorWithoutWhitespaceFixer; use PhpCsFixer\Fixer\Operator\StandardizeNotEqualsFixer; use PhpCsFixer\Fixer\Operator\TernaryOperatorSpacesFixer; @@ -94,6 +113,7 @@ use PhpCsFixer\Fixer\Phpdoc\NoEmptyPhpdocFixer; use PhpCsFixer\Fixer\Phpdoc\NoSuperfluousPhpdocTagsFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocAddMissingParamAnnotationFixer; +use PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocIndentFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocNoAccessFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocNoEmptyReturnFixer; @@ -103,6 +123,8 @@ use PhpCsFixer\Fixer\Phpdoc\PhpdocReturnSelfReferenceFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocScalarFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocSingleLineVarSpacingFixer; +use PhpCsFixer\Fixer\Phpdoc\PhpdocToCommentFixer; +use PhpCsFixer\Fixer\Phpdoc\PhpdocTrimConsecutiveBlankLineSeparationFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocTrimFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocTypesFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocVarAnnotationCorrectOrderFixer; @@ -112,6 +134,8 @@ use PhpCsFixer\Fixer\PhpUnit\PhpUnitDedicateAssertFixer; use PhpCsFixer\Fixer\PhpUnit\PhpUnitDedicateAssertInternalTypeFixer; use PhpCsFixer\Fixer\PhpUnit\PhpUnitExpectationFixer; +use PhpCsFixer\Fixer\PhpUnit\PhpUnitFqcnAnnotationFixer; +use PhpCsFixer\Fixer\PhpUnit\PhpUnitMethodCasingFixer; use PhpCsFixer\Fixer\PhpUnit\PhpUnitMockFixer; use PhpCsFixer\Fixer\PhpUnit\PhpUnitMockShortWillReturnFixer; use PhpCsFixer\Fixer\PhpUnit\PhpUnitNoExpectationAnnotationFixer; @@ -123,18 +147,26 @@ use PhpCsFixer\Fixer\Semicolon\SpaceAfterSemicolonFixer; use PhpCsFixer\Fixer\Strict\DeclareStrictTypesFixer; use PhpCsFixer\Fixer\Strict\StrictParamFixer; +use PhpCsFixer\Fixer\StringNotation\MultilineStringToHeredocFixer; +use PhpCsFixer\Fixer\StringNotation\SimpleToComplexStringVariableFixer; use PhpCsFixer\Fixer\StringNotation\SingleQuoteFixer; +use PhpCsFixer\Fixer\Whitespace\ArrayIndentationFixer; use PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer; use PhpCsFixer\Fixer\Whitespace\CompactNullableTypeDeclarationFixer; use PhpCsFixer\Fixer\Whitespace\HeredocIndentationFixer; use PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer; use PhpCsFixer\Fixer\Whitespace\NoWhitespaceInBlankLineFixer; use PhpCsFixer\Fixer\Whitespace\TypeDeclarationSpacesFixer; +use PhpCsFixer\Fixer\Whitespace\TypesSpacesFixer; +use SlevomatCodingStandard\Sniffs\Attributes\DisallowAttributesJoiningSniff; +use SlevomatCodingStandard\Sniffs\Attributes\DisallowMultipleAttributesPerLineSniff; +use SlevomatCodingStandard\Sniffs\Classes\RequireConstructorPropertyPromotionSniff; use SlevomatCodingStandard\Sniffs\ControlStructures\RequireNullSafeObjectOperatorSniff; use SlevomatCodingStandard\Sniffs\Exceptions\ReferenceThrowableOnlySniff; use SlevomatCodingStandard\Sniffs\Functions\RequireTrailingCommaInCallSniff; -use SlevomatCodingStandard\Sniffs\Functions\RequireTrailingCommaInDeclarationSniff; -use SlevomatCodingStandard\Sniffs\TypeHints\UnionTypeHintFormatSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\ParameterTypeHintSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\PropertyTypeHintSniff; +use SlevomatCodingStandard\Sniffs\TypeHints\ReturnTypeHintSniff; use Symplify\CodingStandard\Fixer\Commenting\ParamReturnAndVarTagMalformsFixer; use Symplify\EasyCodingStandard\Config\ECSConfig; @@ -206,24 +238,30 @@ ArrayPushFixer::class, // Replace non multibyte-safe functions with corresponding mb function MbStrFunctionsFixer::class, - // Master functions shall be used instead of aliases - NoAliasFunctionsFixer::class, // Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs RandomApiMigrationFixer::class, // Cast shall be used, not `settype()` SetTypeToCastFixer::class, + // Attributes declared without arguments must not be followed by empty parentheses. + AttributeEmptyParenthesesFixer::class, // Array index should always be written by using square braces NormalizeIndexBraceFixer::class, - // PHP single-line arrays should not have trailing comma + // Empty body of class, interface, trait, enum or function must be abbreviated as {} and placed on the same line as the previous symbol, separated by a single space. + SingleLineEmptyBodyFixer::class, // Defined in PER 2.0 + // Values separated by a comma on a single line should not have a trailing comma. NoTrailingCommaInSinglelineFixer::class, - // Multi-line arrays, arguments list and parameters list must have a trailing comma - TrailingCommaInMultilineFixer::class, + // Literal octal must be in 0o notation. + OctalNotationFixer::class, // Arrays should be formatted like function/method arguments TrimArraySpacesFixer::class, // In array declaration, there MUST be a whitespace after each comma WhitespaceAfterCommaInArrayFixer::class, // Classes must be in a path that matches their namespace PsrAutoloadingFixer::class, + // When referencing an internal class it must be written using the correct casing. + ClassReferenceNameCasingFixer::class, + // Magic constants should be referred to using the correct casing. + MagicConstantCasingFixer::class, // Magic method definitions and calls must be using the correct casing MagicMethodCasingFixer::class, // Function defined by PHP should be called using the correct casing @@ -244,6 +282,8 @@ SingleTraitInsertPerStatementFixer::class, // There should not be any empty comments NoEmptyCommentFixer::class, + // Single-line comments must have proper spacing. + SingleLineCommentSpacingFixer::class, // There should not be useless `else` cases NoUselessElseFixer::class, // Switch case must not be ended with `continue` but with `break`. @@ -256,6 +296,8 @@ FopenFlagsFixer::class, // Add missing space between function's argument and its typehint. TypeDeclarationSpacesFixer::class, + // None space should be around union type and intersection type operators. + TypesSpacesFixer::class, // Function `implode` must be called with 2 arguments in the documented order. ImplodeCallFixer::class, // Lambda must not import variables it doesn't use. @@ -264,33 +306,43 @@ NoUnreachableDefaultArgumentValueFixer::class, // There must be no `sprintf` calls with only the first argument. NoUselessSprintfFixer::class, - + // Add `?` before single type declarations when parameters have a default null value. + NullableTypeDeclarationForDefaultNullValueFixer::class, + // There must not be a space before colon in return type declarations. ReturnTypeDeclarationFixer::class, - + // Add `void` return type to functions with missing or empty return statements. VoidReturnFixer::class, - + // Remove leading slashes in `use` clauses. NoLeadingImportSlashFixer::class, - + // Imports should not be aliased as the same name. + NoUnneededImportAliasFixer::class, + // Unused `use` statements must be removed. NoUnusedImportsFixer::class, - + // Order `use` statements. OrderedImportsFixer::class, - + // Equal sign in declare statement should not be surrounded by spaces. DeclareEqualNormalizeFixer::class, // Replaces `is_null($var)` expression with `null === $var` IsNullFixer::class, + // Nullable single type declaration should be standardised using question mark syntax. + NullableTypeDeclarationFixer::class, // Ensures a single space around language constructs. SingleSpaceAroundConstructFixer::class, // Namespace must not contain spacing, comments or PHPDoc. CleanNamespaceFixer::class, // The namespace declaration line shouldn't contain leading whitespace. NoLeadingNamespaceWhitespaceFixer::class, - + // Binary operators should be surrounded by exactly one single space. BinaryOperatorSpacesFixer::class, - + // Shorthand notation for operators should be used if possible. + LongToShorthandOperatorFixer::class, + // There must be no space around scope resolution double colons + NoSpaceAroundDoubleColonFixer::class, + // All instances created with new keyword must be followed by parentheses. NewWithParenthesesFixer::class, - + // There should not be space before or after object operators `->` and `?->`. ObjectOperatorWithoutWhitespaceFixer::class, - + // Replace all `<>` with `!=`. StandardizeNotEqualsFixer::class, // Standardize spaces around ternary operator. TernaryOperatorSpacesFixer::class, @@ -298,11 +350,11 @@ TernaryToElvisOperatorFixer::class, // Use `null` coalescing operator `??` where possible. TernaryToNullCoalescingFixer::class, - + // Unary operators should be placed adjacent (without a space) to their operands. UnaryOperatorSpacesFixer::class, - + // There should not be blank lines between docblock and the documented element. NoBlankLinesAfterPhpdocFixer::class, - + // There should not be empty PHPDoc blocks. NoEmptyPhpdocFixer::class, // PHPDoc should contain `@param` for all params. PhpdocAddMissingParamAnnotationFixer::class, @@ -314,16 +366,18 @@ PhpdocNoEmptyReturnFixer::class, // `@package` and `@subpackage` annotations should be omitted from PHPDoc. PhpdocNoPackageFixer::class, - // Annotations in PHPDoc should be ordered. - PhpdocOrderFixer::class, // The type of `@return` annotations of methods returning a reference to itself must the configured one. PhpdocReturnSelfReferenceFixer::class, // Scalar types should always be written in the same form. PhpdocScalarFixer::class, // Single line `@var` PHPDoc should have proper spacing. PhpdocSingleLineVarSpacingFixer::class, + // Removes extra blank lines after summary and after description in PHPDoc. + PhpdocTrimConsecutiveBlankLineSeparationFixer::class, // PHPDoc should start and end with content PhpdocTrimFixer::class, + // Docblocks should only be used on structural elements. + PhpdocToCommentFixer::class, // The correct case must be used for standard PHP types in PHPDoc. PhpdocTypesFixer::class, // `@var` and `@type` annotations must have type and name in the correct order @@ -344,6 +398,8 @@ PhpUnitNoExpectationAnnotationFixer::class, // Usages of ->setExpectedException* methods MUST be replaced by ->expectException* methods PhpUnitExpectationFixer::class, + // PHPUnit annotations should be a FQCNs including a root namespace. + PhpUnitFqcnAnnotationFixer::class, // Visibility of setUp() and tearDown() method should be kept protected PhpUnitSetUpTearDownVisibilityFixer::class, // There should not be an empty `return` statement at the end of a function @@ -358,8 +414,14 @@ DeclareStrictTypesFixer::class, // Functions should be used with `$strict` param set to `true` StrictParamFixer::class, + // Convert multiline string to heredoc or nowdoc. + MultilineStringToHeredocFixer::class, + // Converts explicit variables in double-quoted strings from simple to complex format (${ to {$). + SimpleToComplexStringVariableFixer::class, // Convert double quotes to single quotes for simple strings SingleQuoteFixer::class, + // Each element of an array must be indented exactly once. + ArrayIndentationFixer::class, // Remove extra spaces in a nullable typehint CompactNullableTypeDeclarationFixer::class, // Heredoc/nowdoc content must be properly indented. @@ -370,19 +432,24 @@ ReferenceThrowableOnlySniff::class, // The @param, @return, @var and inline @var annotations should keep standard format ParamReturnAndVarTagMalformsFixer::class, - - // switch -> match - // @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/5894 - - // Require \Stringable interface in classes implementing __toString() method - // > it may probably be a phpstan rule, more than cs rule - since it needs a class hierarchy to solve this - // @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/6235 - - // Multi-line arguments list in function/method declaration must have a trailing comma - RequireTrailingCommaInDeclarationSniff::class, + // Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature to a native PHP 7.4+ type-hint. + PhpdocToPropertyTypeFixer::class, + PropertyTypeHintSniff::class, + // Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature to a native type-hints. + PhpdocToParamTypeFixer::class, + ParameterTypeHintSniff::class, + // Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. + PhpdocToReturnTypeFixer::class, + ReturnTypeHintSniff::class, + // Requires that only one attribute can be placed inside #[]. + DisallowAttributesJoiningSniff::class, + // Disallows multiple attributes of some target on same line. + DisallowMultipleAttributesPerLineSniff::class, + // Promote constructor properties + // For php-cs-fixer implementation @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/5956 + RequireConstructorPropertyPromotionSniff::class, // Multi-line arguments list in function/method call must have a trailing comma - RequireTrailingCommaInCallSniff::class, - + RequireTrailingCommaInCallSniff::class, // TODO: will be redundant after https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7989 is merged and released // Use `null-safe` operator `?->` where possible RequireNullSafeObjectOperatorSniff::class, ], @@ -433,6 +500,8 @@ 'var_dump' => null, ], ]) + // Master functions shall be used instead of aliases + ->withConfiguredRule(NoAliasFunctionsFixer::class, ['sets' => ['@all']]) // There should be exactly one blank line before a namespace declaration. ->withConfiguredRule(BlankLinesBeforeNamespaceFixer::class, ['min_line_breaks' => 2, 'max_line_breaks' => 2]) // Proper operator spacing @@ -455,12 +524,17 @@ ->withConfiguredRule(ConcatSpaceFixer::class, ['spacing' => 'one']) // Removes `@param` and `@return` tags that don't provide any useful information ->withConfiguredRule(NoSuperfluousPhpdocTagsFixer::class, [ - 'allow_mixed' => true, // allow `@mixed` annotations to be preserved 'allow_unused_params' => false, // whether param annotation without actual signature is allowed 'remove_inheritdoc' => true, // remove @inheritDoc tags ]) + // All items of the given PHPDoc tags must be left-aligned. + ->withConfiguredRule(PhpdocAlignFixer::class, ['align' => 'left']) + // Annotations in PHPDoc should be ordered in defined sequence. + ->withConfiguredRule(PhpdocOrderFixer::class, ['order' => ['param', 'return', 'throws']]) // Order phpdoc tags by value. ->withConfiguredRule(PhpdocOrderByValueFixer::class, ['annotations' => ['covers', 'group', 'throws']]) + // Enforce camel case for PHPUnit test methods. + ->withConfiguredRule(PhpUnitMethodCasingFixer::class, ['case' => 'camel_case']) // Calls to `PHPUnit\Framework\TestCase` static methods must all be of the same type (`$this->...`) ->withConfiguredRule(PhpUnitTestCaseStaticMethodCallsFixer::class, ['call_type' => 'this']) // An empty line feed must precede any configured statement @@ -468,20 +542,62 @@ // Removes extra blank lines and/or blank lines following configuration ->withConfiguredRule(NoExtraBlankLinesFixer::class, [ 'tokens' => [ - 'break', + 'attribute', + 'case', 'continue', 'curly_brace_block', + 'default', 'extra', 'parenthesis_brace_block', - 'return', 'square_brace_block', + 'switch', 'throw', 'use', 'use_trait', ], ]) - // Format union types - ->withConfiguredRule(UnionTypeHintFormatSniff::class, ['withSpaces' => 'no']) + // Elements of classes/interfaces/traits/enums should be in the defined order + ->withConfiguredRule( + OrderedClassElementsFixer::class, + [ + 'order' => [ + 'use_trait', + 'case', // enum values should be before other elements + 'constant', + 'property', + 'construct', + 'destruct', + 'magic', + 'phpunit', // phpunit special methods like setUp should be before test methods + 'method', + ], + ], + ) + // Spaces should be properly placed in a function declaration. + ->withConfiguredRule( + FunctionDeclarationFixer::class, + ['closure_fn_spacing' => 'none'], // Defined in PER 2.0 + ) + // Removes unneeded parentheses around specified control statements. + ->withConfiguredRule( + NoUnneededControlParenthesesFixer::class, + ['statements' => ['break', 'clone', 'continue', 'echo_print', 'others', 'switch_case', 'yield', 'yield_from']], + ) + // Multi-line arrays, arguments list and parameters list must have a trailing comma + ->withConfiguredRule( + TrailingCommaInMultilineFixer::class, + ['after_heredoc' => true, 'elements' => ['arguments', 'arrays', 'match', 'parameters']], // Defined in PER 2.0 + ) + // Removes the leading part of FQCN + ->withConfiguredRule( + FullyQualifiedStrictTypesFixer::class, + ['import_symbols' => true], // Also import symbols from other namespaces than in current file + ) + // Spaces and newlines in method arguments and attributes + ->withConfiguredRule( + MethodArgumentSpaceFixer::class, + ['attribute_placement' => 'standalone', 'on_multiline' => 'ensure_fully_multiline'], + ) ->withSkip([ // We allow empty catch statements (but they must have comment - see EmptyCatchCommentSniff) EmptyStatementSniff::class . '.DetectedCatch' => null, @@ -511,6 +627,18 @@ // Allow single line closures ScopeClosingBraceSniff::class . '.ContentBefore' => null, + // Skip unwanted rules from PropertyTypeHintSniff + PropertyTypeHintSniff::class . '.' . PropertyTypeHintSniff::CODE_MISSING_TRAVERSABLE_TYPE_HINT_SPECIFICATION => null, + PropertyTypeHintSniff::class . '.' . PropertyTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT => null, + + // Skip unwanted rules from ParameterTypeHintSniff + ParameterTypeHintSniff::class . '.' . ParameterTypeHintSniff::CODE_MISSING_TRAVERSABLE_TYPE_HINT_SPECIFICATION => null, + ParameterTypeHintSniff::class . '.' . ParameterTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT => null, + + // Skip unwanted rules from ReturnTypeHintSniff + ReturnTypeHintSniff::class . '.' . ReturnTypeHintSniff::CODE_MISSING_TRAVERSABLE_TYPE_HINT_SPECIFICATION => null, + ReturnTypeHintSniff::class . '.' . ReturnTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT => null, + // We use declare(strict_types=1); after opening tag BlankLineAfterOpeningTagFixer::class => null, ]); diff --git a/src/Helper/SniffClassWrapper.php b/src/Helper/SniffClassWrapper.php index 84d05f2..470fda6 100644 --- a/src/Helper/SniffClassWrapper.php +++ b/src/Helper/SniffClassWrapper.php @@ -33,9 +33,7 @@ final class SniffClassWrapper { - public function __construct(private File $file, private int $position, private Naming $naming) - { - } + public function __construct(private File $file, private int $position, private Naming $naming) {} public function getClassName(): ?string { diff --git a/tests/Integration/CodingStandardTest.php b/tests/Integration/CodingStandardTest.php index 42cf1eb..4c92ccb 100644 --- a/tests/Integration/CodingStandardTest.php +++ b/tests/Integration/CodingStandardTest.php @@ -9,25 +9,23 @@ */ class CodingStandardTest extends TestCase { + private string $tempFixtureFile; + + /** @after */ + protected function cleanUpTempFixtureFile(): void + { + unlink($this->tempFixtureFile); + } + /** * @test * @dataProvider provideFilesToFix */ public function shouldFixFile(string $wrongFile, string $correctFile): void { - // copy wrongFile to a new temporary file - $fixtureFile = tempnam(sys_get_temp_dir(), 'ecs-test'); - if ($fixtureFile === false) { - $this->fail('Could not create temporary file'); - } - copy($wrongFile, $fixtureFile); - - shell_exec( - __DIR__ . '/../../vendor/bin/ecs check --no-progress-bar --no-ansi --no-interaction --fix ' . $fixtureFile, - ); + $fixedFile = $this->runEcsCheckOnFile($wrongFile); - $this->assertStringEqualsFile($correctFile, file_get_contents($fixtureFile)); - unlink($fixtureFile); + $this->assertStringEqualsFile($correctFile, file_get_contents($fixedFile)); } public function provideFilesToFix(): array @@ -38,6 +36,66 @@ public function provideFilesToFix(): array __DIR__ . '/Fixtures/NewPhpFeatures.wrong.php.inc', __DIR__ . '/Fixtures/NewPhpFeatures.correct.php.inc', ], + 'PhpDoc' => [__DIR__ . '/Fixtures/PhpDoc.wrong.php.inc', __DIR__ . '/Fixtures/PhpDoc.correct.php.inc'], + 'PhpUnit' => [__DIR__ . '/Fixtures/PhpUnit.wrong.php.inc', __DIR__ . '/Fixtures/PhpUnit.correct.php.inc'], ]; } + + /** + * @test + * @requires PHP >= 8.1 + */ + public function shouldFixPhp81(): void + { + $fixedFile = $this->runEcsCheckOnFile(__DIR__ . '/Fixtures/Php81.wrong.php.inc'); + + $this->assertStringEqualsFile( + __DIR__ . '/Fixtures/Php81.correct.php.inc', + file_get_contents($fixedFile), + ); + } + + /** + * @test + * @requires PHP >= 8.2 + */ + public function shouldFixPhp82(): void + { + $fixedFile = $this->runEcsCheckOnFile(__DIR__ . '/Fixtures/Php82.wrong.php.inc'); + + $this->assertStringEqualsFile( + __DIR__ . '/Fixtures/Php82.correct.php.inc', + file_get_contents($fixedFile), + ); + } + + private function runEcsCheckOnFile(string $file): string + { + $fixtureFile = $this->initTempFixtureFile(); + + // copy source of wrongFile to a temporary file which will be modified by ECS + copy($file, $fixtureFile); + + shell_exec( + sprintf( + '%s/../../vendor/bin/ecs check --no-progress-bar --no-ansi --no-interaction --fix %s', + __DIR__, + $fixtureFile, + ), + ); + + return $fixtureFile; + } + + private function initTempFixtureFile(): string + { + // Create new file in temporary directory + $fixtureFile = tempnam(sys_get_temp_dir(), 'ecs-test'); + if ($fixtureFile === false) { + $this->fail('Could not create temporary file'); + } + $this->tempFixtureFile = $fixtureFile; // store to be able to remove it later + + return $fixtureFile; + } } diff --git a/tests/Integration/Fixtures/Basic.correct.php.inc b/tests/Integration/Fixtures/Basic.correct.php.inc index 922a41a..b2e6454 100644 --- a/tests/Integration/Fixtures/Basic.correct.php.inc +++ b/tests/Integration/Fixtures/Basic.correct.php.inc @@ -1,19 +1,45 @@ $foo + $value; + + // PhpdocToCommentFixer + /* + * Phpdoc used instead of plain comment + */ + if ($foo === 'bar') { + // NoAliasFunctionsFixer + $baz = implode(',', ['foo', 'bar']); + } + + $i = 3; + $i += 6; // LongToShorthandOperatorFixer + $i *= 2; // LongToShorthandOperatorFixer + $text = 'foo'; + $text .= 'bar'; // LongToShorthandOperatorFixer + + switch ($i) { + case 1: // NoUnneededControlParenthesesFixer + $i++; + break; + default: + break; + } + + // HeredocIndentationFixer + $heredoc = << 'bar', + 'baz' => 'bat', + ]; + + $multiLineAssociative2 = [ + 'foo' => 'bar', + 'baz' => 'bat', + 'bak' => 'baz', + ]; + + $multiLineAssociative3 = [ + 'firstKey' => 'bar', + 'thisIsSecondKey' => 'bat', + 'third' => 'bat', + ]; + } + + public function emptyFunction1(): void {} // SingleLineEmptyBodyFixer + + public function emptyFunction2( + $arg, + ): void {} // SingleLineEmptyBodyFixer + + // TrailingCommaInMultilineFixer + public function multiline( + $arg1, + $arg2, + $arg3, + ): void { + // TrailingCommaInMultilineFixer + $isSet = isset( + $arg1, + $arg2, + $arg3, + ); + + $sprintf = sprintf( + '%s%s', + 'bar', + 'baz', + ); + + foo( + 1, + 2, + ); + } + + public function withParameters( + // NullableTypeDeclarationFixer + ?string $nullableValue, + // NullableTypeDeclarationForDefaultNullValueFixer + ?string $anotherNullableValue = null, + ): void {} } diff --git a/tests/Integration/Fixtures/Basic.wrong.php.inc b/tests/Integration/Fixtures/Basic.wrong.php.inc index aa795ae..15bd67a 100644 --- a/tests/Integration/Fixtures/Basic.wrong.php.inc +++ b/tests/Integration/Fixtures/Basic.wrong.php.inc @@ -1,26 +1,169 @@ $foo + $value; + + // PhpdocToCommentFixer + /** + * Phpdoc used instead of plain comment + */ + if ($foo === 'bar') { + // NoAliasFunctionsFixer + $baz = join(',', ['foo', 'bar']); + } + + $i = 3; + $i = $i + 6; // LongToShorthandOperatorFixer + $i = $i * 2; // LongToShorthandOperatorFixer + $text = 'foo'; + $text = $text . 'bar'; // LongToShorthandOperatorFixer + + switch ($i) { + case (1): // NoUnneededControlParenthesesFixer + $i++; + break; + default: + break; + } + + // HeredocIndentationFixer + $heredoc = << 'bar', 'baz' => 'bat', + ]; + + $multiLineAssociative2 = [ + 'foo'=>'bar', + 'baz'=>'bat', + 'bak'=>'baz' + ]; + + $multiLineAssociative3 = [ + 'firstKey' => 'bar', + 'thisIsSecondKey' => 'bat', + 'third' => 'bat', + ]; + } + + public function emptyFunction1(): void { + } // SingleLineEmptyBodyFixer + + public function emptyFunction2( + $arg + ): void { + } // SingleLineEmptyBodyFixer + + // TrailingCommaInMultilineFixer + public function multiline( + $arg1, + $arg2, + $arg3 + ): void + { + // TrailingCommaInMultilineFixer + $isSet = isset( + $arg1, + $arg2, + $arg3 + ); + + $sprintf = sprintf( + '%s%s', + 'bar', + 'baz' + ); + + foo( + 1, + 2 + ); + } + + public function withParameters( + // NullableTypeDeclarationFixer + null|string $nullableValue, + // NullableTypeDeclarationForDefaultNullValueFixer + string $anotherNullableValue = null, + ): void {} + + // MagicMethodCasingFixer, OrderedClassElementsFixer + public function __ToString(): string + { + return ''; + } } diff --git a/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc b/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc index f2962dd..bff038b 100644 --- a/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc +++ b/tests/Integration/Fixtures/NewPhpFeatures.correct.php.inc @@ -4,15 +4,11 @@ namespace Lmc\CodingStandard\Integration\Fixtures; class NewPhpFeatures { - private string $someString; - - public function __construct(string $someString) - { - $this->someString = $someString; - } + public function __construct(private string $someString) // RequireConstructorPropertyPromotionSniff + {} public function php80features( - string|bool $foo, // UnionTypeHintFormatSniff + string|bool $foo, // TypesSpacesFixer int $bar, // RequireTrailingCommaInDeclarationSniff ): string|bool { $value = mt_rand( @@ -23,6 +19,9 @@ class NewPhpFeatures $dateOrNull = $this->mayReturnDateTimeOrNull(); $timestamp = $dateOrNull?->getTimestamp(); // RequireNullSafeObjectOperatorSniff + // AssignNullCoalescingToCoalesceEqualFixer + $name = $_GET['name'] ?? 'default'; + return $foo; } @@ -30,4 +29,18 @@ class NewPhpFeatures { return null; } + + // AttributeEmptyParenthesesFixer + #[SomeFunctionAttribute] + #[AnotherAttribute('bar')] + #[AnotherAttribute] + #[First] + #[Second] + public function functionWithAttributes( + // MethodArgumentSpaceFixer + #[ParamAttribute] + #[AnotherAttribute('foo')] + string $foo, + string $bar, + ): void {} } diff --git a/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc b/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc index 3435f7e..5629c7a 100644 --- a/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc +++ b/tests/Integration/Fixtures/NewPhpFeatures.wrong.php.inc @@ -6,13 +6,13 @@ class NewPhpFeatures { private string $someString; - public function __construct(string $someString) + public function __construct(string $someString) // RequireConstructorPropertyPromotionSniff { $this->someString = $someString; } public function php80features( - string | bool $foo, // UnionTypeHintFormatSniff + string | bool $foo, // TypesSpacesFixer int $bar // RequireTrailingCommaInDeclarationSniff ): string | bool { $value = mt_rand( @@ -23,6 +23,9 @@ class NewPhpFeatures $dateOrNull = $this->mayReturnDateTimeOrNull(); $timestamp = $dateOrNull !== null ? $dateOrNull->getTimestamp() : null; // RequireNullSafeObjectOperatorSniff + // AssignNullCoalescingToCoalesceEqualFixer + $name = isset($_GET['name']) ? $_GET['name'] : 'default'; + return $foo; } @@ -30,4 +33,15 @@ class NewPhpFeatures { return null; } + + // AttributeEmptyParenthesesFixer + #[SomeFunctionAttribute()] + #[AnotherAttribute('bar')]#[AnotherAttribute()] + + #[First, Second] + public function functionWithAttributes( + // MethodArgumentSpaceFixer + #[ParamAttribute] #[AnotherAttribute('foo')] string $foo, + string $bar, + ): void {} } diff --git a/tests/Integration/Fixtures/Php81.correct.php.inc b/tests/Integration/Fixtures/Php81.correct.php.inc new file mode 100644 index 0000000..f80047a --- /dev/null +++ b/tests/Integration/Fixtures/Php81.correct.php.inc @@ -0,0 +1,12 @@ + 3 ? true : false; + } + + /** + * @param string $stringParam This phpdoc should be preserved, because it contains some comment for $stringParam. + */ + public function methodWithMeaningfulParamComment(int $intParam, string $stringParam): void + { + // Do nothing. + } +} diff --git a/tests/Integration/Fixtures/PhpDoc.wrong.php.inc b/tests/Integration/Fixtures/PhpDoc.wrong.php.inc new file mode 100644 index 0000000..a13aede --- /dev/null +++ b/tests/Integration/Fixtures/PhpDoc.wrong.php.inc @@ -0,0 +1,53 @@ + 3 ? true : false; + } + + /** + * @param int $intParam + * @param string $stringParam This phpdoc should be preserved, because it contains some comment for $stringParam. + * @return void + */ + public function methodWithMeaningfulParamComment(int $intParam, string $stringParam): void + { + // Do nothing. + } +} diff --git a/tests/Integration/Fixtures/PhpUnit.correct.php.inc b/tests/Integration/Fixtures/PhpUnit.correct.php.inc new file mode 100644 index 0000000..642c49f --- /dev/null +++ b/tests/Integration/Fixtures/PhpUnit.correct.php.inc @@ -0,0 +1,39 @@ +assertTrue($data); + + // PhpUnitDedicateAssertFixer + $this->assertStringContainsString('o', 'foo'); + + $this->assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + $this->assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + $this->assertSame(1, 2); + } + + public function testMethodName(): void // PhpUnitMethodCasingFixer + { + $this->assertTrue(true); + } +} diff --git a/tests/Integration/Fixtures/PhpUnit.wrong.php.inc b/tests/Integration/Fixtures/PhpUnit.wrong.php.inc new file mode 100644 index 0000000..16f0f6a --- /dev/null +++ b/tests/Integration/Fixtures/PhpUnit.wrong.php.inc @@ -0,0 +1,39 @@ +assertSame(true, $data); + + // PhpUnitDedicateAssertFixer + $this->assertTrue(str_contains('foo', 'o')); + + $this->assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + self::assertSame(1, 2); + // PhpUnitTestCaseStaticMethodCallsFixer + static::assertSame(1, 2); + } + + public function test_method_name(): void // PhpUnitMethodCasingFixer + { + $this->assertTrue(true); + } +}