Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

## [Unreleased]
### Added
- Added `SlevomatCodingStandard.Classes.ClassKeywordOrder` rule to enforce correct order of class modifiers (e.g., `final readonly class`)

### Changed
- Updated Slevomat Coding Standard to 8.27.*

## [0.4.2] - 2025-11-28
### Changed
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
],
"require": {
"php": ">=7.4",
"slevomat/coding-standard": "8.25.*",
"slevomat/coding-standard": "8.27.*",
"squizlabs/php_codesniffer": "4.0.*"
},
"require-dev": {
Expand Down
1 change: 1 addition & 0 deletions ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
<rule ref="SlevomatCodingStandard.Classes.DisallowMultiPropertyDefinition"/>
<rule ref="SlevomatCodingStandard.Classes.UselessLateStaticBinding"/>
<rule ref="SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch"/>
<rule ref="SlevomatCodingStandard.Classes.ClassKeywordOrder"/>
<rule ref="SlevomatCodingStandard.Classes.ConstantSpacing"/>
<!-- <rule ref="SlevomatCodingStandard.Classes.RequireSingleLineMethodSignature"/>-->
<rule ref="SlevomatCodingStandard.Classes.RequireMultiLineMethodSignature"/>
Expand Down
45 changes: 45 additions & 0 deletions tests/Integration/SimplifiedRulesetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,51 @@ public function useArrowFunction(): array
);
}

public function testClassKeywordOrderCompliant(): void
{
$testFile = __DIR__ . '/../fixtures/compliant/class-keyword-order-test.php';
$this->assertFileExists($testFile);

$command = sprintf(
'%s --standard=%s %s',
escapeshellarg($this->phpcsPath),
escapeshellarg($this->rulesetPath),
escapeshellarg($testFile),
);

exec($command, $output, $exitCode);

$this->assertEquals(
0,
$exitCode,
'Compliant class keyword order should pass without violations. Output: ' . implode("\n", $output),
);
}

public function testClassKeywordOrderViolations(): void
{
$testFile = __DIR__ . '/../fixtures/violations/class-keyword-order-test.php';
$this->assertFileExists($testFile);

$command = sprintf(
'%s --standard=%s %s',
escapeshellarg($this->phpcsPath),
escapeshellarg($this->rulesetPath),
escapeshellarg($testFile),
);

exec($command, $output, $exitCode);
$outputString = implode("\n", $output);

$this->assertGreaterThan(0, $exitCode, 'Class keyword order violations should be detected');
$this->assertTrue(
strpos($outputString, 'ClassKeywordOrder') !== false ||
strpos($outputString, 'keyword') !== false ||
strpos($outputString, 'order') !== false,
'Should detect class keyword order violations. Output: ' . $outputString,
);
}

protected function setUp(): void
{
$this->phpcsPath = __DIR__ . '/../../vendor/bin/phpcs';
Expand Down
14 changes: 14 additions & 0 deletions tests/fixtures/compliant/class-keyword-order-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace DigitalCz\CodingStandard\Tests\Fixtures\Compliant;

// Correct: final readonly class (final before readonly)
final readonly class FinalReadonlyClass
{
public function __construct(
public string $property,
) {
}
}
14 changes: 14 additions & 0 deletions tests/fixtures/violations/class-keyword-order-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace DigitalCz\CodingStandard\Tests\Fixtures\Violations;

// Wrong: readonly before final (should be: final readonly class)
readonly final class WrongFinalReadonlyClass
{
public function __construct(
public string $property,
) {
}
}