Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 4a1cef8

Browse files
committed
feat: add pluralizer rule
1 parent 193b163 commit 4a1cef8

4 files changed

Lines changed: 120 additions & 2 deletions

File tree

src/Config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function __construct()
1818

1919
$this->registerCustomFixers([
2020
new \LiveIntent\PhpCsFixer\Fixer\Naming\ClassSuffixFixer(),
21+
new \LiveIntent\PhpCsFixer\Fixer\Naming\ClassNumberFixer(),
2122
]);
2223

2324
$this->setRules(
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
namespace LiveIntent\PhpCsFixer\Fixer\Naming;
4+
5+
use SplFileInfo;
6+
use Illuminate\Support\Str;
7+
use PhpCsFixer\Tokenizer\Token;
8+
use PhpCsFixer\Tokenizer\Tokens;
9+
use LiveIntent\PhpCsFixer\AbstractFixer;
10+
use PhpCsFixer\FixerDefinition\FixerDefinition;
11+
use LiveIntent\PhpCsFixer\Util\LaravelIdentifier;
12+
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
13+
14+
class ClassNumberFixer extends AbstractFixer
15+
{
16+
/**
17+
* Mapping of laravel components to their expected class number.
18+
*
19+
* @var array
20+
*/
21+
public const NUMBERS = [
22+
'command' => 'singular',
23+
'controller' => 'singular',
24+
'factory' => 'singular',
25+
'form_request' => 'singular',
26+
'model' => 'singular',
27+
'resource' => 'singular',
28+
'table' => 'plural',
29+
];
30+
31+
/**
32+
* Check if the fixer is a candidate for given Tokens collection.
33+
*
34+
* Fixer is a candidate when the collection contains tokens that may be fixed
35+
* during fixer work. This could be considered as some kind of bloom filter.
36+
* When this method returns true then to the Tokens collection may or may not
37+
* need a fixing, but when this method returns false then the Tokens collection
38+
* need no fixing for sure.
39+
*/
40+
public function isCandidate(Tokens $tokens): bool
41+
{
42+
return $tokens->isAnyTokenKindsFound([T_CLASS]);
43+
}
44+
45+
/**
46+
* Check if fixer is risky or not.
47+
*
48+
* Risky fixer could change code behavior!
49+
*/
50+
public function isRisky(): bool
51+
{
52+
return true;
53+
}
54+
55+
/**
56+
* Fixes a file.
57+
*/
58+
public function applyFix(SplFileInfo $file, Tokens $tokens): void
59+
{
60+
if (! $component = LaravelIdentifier::identify($file)) {
61+
return;
62+
}
63+
64+
if (! $number = static::NUMBERS[$component] ?? null) {
65+
return;
66+
}
67+
68+
$suffix = ClassSuffixFixer::SUFFIXES[$component] ?? '';
69+
[$classNameToken, $classNameTokenIndex] = $this->findClassNameToken($tokens);
70+
71+
$subject = Str::beforeLast($classNameToken->getContent(), $suffix);
72+
73+
$adjusted = $number === 'singular' ? Str::singular($subject) : Str::plural($subject);
74+
75+
$tokens[$classNameTokenIndex] = new Token($adjusted.$suffix);
76+
}
77+
78+
/**
79+
* Returns the definition of the fixer.
80+
*/
81+
public function getDefinition(): FixerDefinitionInterface
82+
{
83+
return new FixerDefinition(
84+
'Enforce class names use the proper grammatical number.',
85+
[],
86+
null,
87+
'Renames classes and cannot rename the files or references. You might need to do additional manual fixing.'
88+
);
89+
}
90+
91+
/**
92+
* Returns true if the file is supported by this fixer.
93+
*
94+
* @return bool true if the file is supported by this fixer, false otherwise
95+
*/
96+
public function supports(SplFileInfo $file): bool
97+
{
98+
return ! Str::contains($file->getPath(), ['migrations']);
99+
}
100+
101+
/**
102+
* Get the token representing the name of the class.
103+
*/
104+
protected function findClassNameToken(Tokens $tokens): array|null
105+
{
106+
foreach ($tokens as $index => $token) {
107+
if ($token->isGivenKind(T_CLASS)) {
108+
$classNameTokenIndex = $index + 2;
109+
110+
return [$tokens[$classNameTokenIndex], $classNameTokenIndex];
111+
}
112+
}
113+
114+
return null;
115+
}
116+
}

src/Fixer/Naming/ClassSuffixFixer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ClassSuffixFixer extends AbstractFixer
1919
*
2020
* @var array
2121
*/
22-
private static $suffixes = [
22+
public const SUFFIXES = [
2323
'command' => 'Command',
2424
'controller' => 'Controller',
2525
'event' => null,
@@ -69,7 +69,7 @@ public function applyFix(SplFileInfo $file, Tokens $tokens): void
6969
return;
7070
}
7171

72-
$suffix = static::$suffixes[$component];
72+
$suffix = static::SUFFIXES[$component];
7373
[$classNameToken, $classNameTokenIndex] = $this->findClassNameToken($tokens);
7474

7575
$adjusted = $suffix

src/RuleSet/Set/LiveIntentSet.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function getRules(): array
4444
* Custom rules...
4545
*/
4646
'LiveIntent/class_suffix' => true,
47+
'LiveIntent/class_number' => true,
4748
];
4849
}
4950
}

0 commit comments

Comments
 (0)