|
5 | 5 | namespace Shopsys\CodingStandards\CsFixer; |
6 | 6 |
|
7 | 7 | use Override; |
8 | | -use PhpCsFixer\Fixer\ClassNotation\FinalClassFixer; |
9 | | -use PhpCsFixer\Fixer\FixerInterface; |
10 | | -use PhpCsFixer\FixerDefinition\FixerDefinition; |
11 | | -use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; |
12 | | -use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis; |
13 | | -use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer; |
14 | | -use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; |
15 | | -use PhpCsFixer\Tokenizer\Token; |
16 | | -use PhpCsFixer\Tokenizer\Tokens; |
17 | | -use SplFileInfo; |
18 | 8 |
|
19 | | -class FinalFormTypeFixer implements FixerInterface |
| 9 | +class FinalFormTypeFixer extends AbstractFinalClassByParentFixer |
20 | 10 | { |
21 | 11 | #[Override] |
22 | | - public function getDefinition(): FixerDefinitionInterface |
| 12 | + protected function getDescription(): string |
23 | 13 | { |
24 | | - return new FixerDefinition( |
25 | | - 'Form types extending AbstractType or AbstractTypeExtension must be final.', |
26 | | - [], |
27 | | - ); |
| 14 | + return 'Form types extending AbstractType or AbstractTypeExtension must be final.'; |
28 | 15 | } |
29 | 16 |
|
30 | 17 | #[Override] |
31 | | - public function isCandidate(Tokens $tokens): bool |
| 18 | + protected function getMatchingParentClasses(): array |
32 | 19 | { |
33 | | - return $tokens->isTokenKindFound(T_EXTENDS); |
34 | | - } |
35 | | - |
36 | | - #[Override] |
37 | | - public function isRisky(): bool |
38 | | - { |
39 | | - return false; |
40 | | - } |
41 | | - |
42 | | - #[Override] |
43 | | - public function fix(SplFileInfo $file, Tokens $tokens): void |
44 | | - { |
45 | | - $namespacesAnalyzer = new NamespacesAnalyzer(); |
46 | | - $namespaces = $namespacesAnalyzer->getDeclarations($tokens); |
47 | | - |
48 | | - // Process each namespace (including global namespace) |
49 | | - foreach ($namespaces as $namespace) { |
50 | | - $this->fixNamespace($tokens, $namespace); |
51 | | - } |
52 | | - } |
53 | | - |
54 | | - private function fixNamespace(Tokens $tokens, NamespaceAnalysis $namespace): void |
55 | | - { |
56 | | - $usesAnalyzer = new NamespaceUsesAnalyzer(); |
57 | | - $uses = $usesAnalyzer->getDeclarationsInNamespace($tokens, $namespace); |
58 | | - |
59 | | - // Process tokens in reverse order to avoid index shifting |
60 | | - for ($index = $namespace->getScopeEndIndex(); $index >= $namespace->getScopeStartIndex(); --$index) { |
61 | | - $token = $tokens[$index]; |
62 | | - |
63 | | - if (!$token->isGivenKind(T_CLASS)) { |
64 | | - continue; |
65 | | - } |
66 | | - |
67 | | - // Check if this is an abstract class or already final |
68 | | - $prevMeaningfulIndex = $tokens->getPrevMeaningfulToken($index); |
69 | | - |
70 | | - if ($prevMeaningfulIndex !== null) { |
71 | | - $prevToken = $tokens[$prevMeaningfulIndex]; |
72 | | - |
73 | | - if ($prevToken->isGivenKind([T_ABSTRACT, T_FINAL])) { |
74 | | - continue; |
75 | | - } |
76 | | - } |
77 | | - |
78 | | - // Find extends keyword after class declaration |
79 | | - $extendsIndex = $tokens->getNextTokenOfKind($index, [[T_EXTENDS]]); |
80 | | - |
81 | | - if ($extendsIndex === null) { |
82 | | - continue; |
83 | | - } |
84 | | - |
85 | | - // Get the parent class name |
86 | | - $parentClassIndex = $tokens->getNextMeaningfulToken($extendsIndex); |
87 | | - |
88 | | - if ($parentClassIndex === null) { |
89 | | - continue; |
90 | | - } |
91 | | - |
92 | | - $parentName = $this->getClassNameFromTokens($tokens, $parentClassIndex); |
93 | | - $resolvedParentName = $this->resolveClassNameWithUses($parentName, $uses); |
94 | | - |
95 | | - if (!$this->isFormTypeClass($resolvedParentName)) { |
96 | | - continue; |
97 | | - } |
98 | | - |
99 | | - // Insert "final" before class keyword |
100 | | - $tokens->insertAt($index, new Token([T_FINAL, 'final'])); |
101 | | - $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' '])); |
102 | | - } |
| 20 | + return [ |
| 21 | + 'Symfony\Component\Form\AbstractType', |
| 22 | + 'Symfony\Component\Form\AbstractTypeExtension', |
| 23 | + ]; |
103 | 24 | } |
104 | 25 |
|
105 | 26 | #[Override] |
106 | 27 | public function getName(): string |
107 | 28 | { |
108 | 29 | return 'Shopsys/final_form_type'; |
109 | 30 | } |
110 | | - |
111 | | - #[Override] |
112 | | - public function getPriority(): int |
113 | | - { |
114 | | - // Before native FinalClassFixer |
115 | | - return (new FinalClassFixer())->getPriority() + 1; |
116 | | - } |
117 | | - |
118 | | - #[Override] |
119 | | - public function supports(SplFileInfo $file): bool |
120 | | - { |
121 | | - return true; |
122 | | - } |
123 | | - |
124 | | - private function getClassNameFromTokens(Tokens $tokens, int $startIndex): string |
125 | | - { |
126 | | - $className = ''; |
127 | | - $index = $startIndex; |
128 | | - |
129 | | - // Collect all parts of the class name (including namespace separators) |
130 | | - while ($index !== null && $tokens[$index]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { |
131 | | - $className .= $tokens[$index]->getContent(); |
132 | | - $index = $tokens->getNextMeaningfulToken($index); |
133 | | - } |
134 | | - |
135 | | - return $className; |
136 | | - } |
137 | | - |
138 | | - /** |
139 | | - * Resolve class name using use statements |
140 | | - * |
141 | | - * @param \PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis[] $uses |
142 | | - */ |
143 | | - private function resolveClassNameWithUses(string $className, array $uses): string |
144 | | - { |
145 | | - // Already fully qualified - return as is |
146 | | - if (str_starts_with($className, '\\')) { |
147 | | - return $className; |
148 | | - } |
149 | | - |
150 | | - // Check for exact match in use statements |
151 | | - foreach ($uses as $use) { |
152 | | - if ($className === $use->getShortName()) { |
153 | | - return $use->getFullName(); |
154 | | - } |
155 | | - } |
156 | | - |
157 | | - // Not found in imports - return as is (could be relative to current namespace) |
158 | | - return $className; |
159 | | - } |
160 | | - |
161 | | - private function isFormTypeClass(string $className): bool |
162 | | - { |
163 | | - return in_array($className, [ |
164 | | - 'AbstractType', |
165 | | - 'AbstractTypeExtension', |
166 | | - 'Symfony\Component\Form\AbstractType', |
167 | | - 'Symfony\Component\Form\AbstractTypeExtension', |
168 | | - '\Symfony\Component\Form\AbstractType', |
169 | | - '\Symfony\Component\Form\AbstractTypeExtension', |
170 | | - ], true); |
171 | | - } |
172 | 31 | } |
0 commit comments