Skip to content

Commit 949b94c

Browse files
authored
Merge pull request #20 from Kocal/homogeneize-codebase
2 parents d614f11 + ad87307 commit 949b94c

File tree

13 files changed

+84
-29
lines changed

13 files changed

+84
-29
lines changed

AGENTS.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,55 @@ This command will:
210210
### Other available commands
211211
Check the `composer.json` file to see all available commands.
212212

213+
## Code Style Conventions
214+
215+
### Variable Naming
216+
217+
- **Reflection variables**: Use `$reflClass` for `ClassReflection` objects (not `$classReflection`)
218+
- **Method reflection**: Use `$reflMethod` for method reflections
219+
- **Property reflection**: Use descriptive names like `$propertyRefl`
220+
- **Attribute nodes**: Use `$attribute` for single attributes, `$livePropAttribute` for specific ones
221+
- **Other nodes**: Use descriptive names like `$method`, `$property`, `$node`
222+
223+
### PHPDoc Comments
224+
225+
- **Public methods**: Always include `@implements Rule<Class_>` or similar on rule classes
226+
- **Private methods**: Always add a brief description comment explaining the method's purpose
227+
- **Return types**: Document complex return types with `@return` (e.g., `@return array{name: string, custom: bool}|null`)
228+
- **Parameters**: Document array parameters with `@param string[]` or similar when applicable
229+
230+
### Error Messages
231+
232+
- **Modal verbs**: Use "must" for requirements (e.g., "Method must be public"), not "should"
233+
- **Consistency**: Be consistent with message structure across similar rules
234+
- **Tips**: Always provide actionable tips with `->tip()` to guide users toward solutions
235+
- **Formatting**: Use double quotes for strings in sprintf, backticks in markdown for code
236+
237+
### Code Structure
238+
239+
- **Early returns**: Check for attribute presence first, return empty array if not found
240+
- **Null checks**: Check for null/undefined values before proceeding
241+
- **Error collection**: Use `$errors = []` array and collect all errors before returning
242+
- **Method order**:
243+
1. `getNodeType()`
244+
2. `processNode()`
245+
3. Private helper methods (alphabetically sorted if multiple)
246+
247+
### Validation Order in `processNode()`
248+
249+
1. Check if node has required attribute (return `[]` if not)
250+
2. Check if `namespacedName` exists (when needed)
251+
3. Initialize error array: `$errors = []`
252+
4. Get reflection class if needed: `$reflClass = $this->reflectionProvider->getClass(...)`
253+
5. Iterate over relevant elements (methods, properties, etc.)
254+
6. Perform validations and collect errors
255+
7. Return `$errors`
256+
213257
## Best Practices
214258

215259
1. **Naming**: Rules should have a descriptive name and end with `Rule`
216-
2. **Identifiers**: Use the format `symfonyUX.twigComponent.descriptiveName` for error identifiers
217-
3. **Clear messages**: Error messages should be explicit and include a `tip()` with a suggestion
260+
2. **Identifiers**: Use the format `symfonyUX.twigComponent.descriptiveName` or `symfonyUX.liveComponent.descriptiveName` for error identifiers
261+
3. **Clear messages**: Error messages must be explicit and include a `tip()` with a suggestion
218262
4. **Complete tests**: Always test valid cases, invalid cases, and non-components
219263
5. **Documentation**: Document each rule in the README with concrete examples
220264
6. **Validation**: Always run `symfony composer qa-fix` before committing

src/Rules/LiveComponent/LiveActionMethodsShouldBePublicRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function processNode(Node $node, Scope $scope): array
4040
$methodName = $method->name->toString();
4141

4242
$errors[] = RuleErrorBuilder::message(
43-
sprintf('LiveAction method "%s()" should be public.', $methodName)
43+
sprintf('LiveAction method "%s()" must be public.', $methodName)
4444
)
4545
->identifier('symfonyUX.liveComponent.liveActionMethodsShouldBePublic')
4646
->line($method->getLine())

src/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function processNode(Node $node, Scope $scope): array
3838

3939
if (! $method->isPublic()) {
4040
$errors[] = RuleErrorBuilder::message(sprintf(
41-
'LiveListener method "%s()" should be public.',
41+
'LiveListener method "%s()" must be public.',
4242
$method->name->toString()
4343
))
4444
->identifier('symfonyUX.liveComponent.liveListenerMethodShouldBePublic')

src/Rules/TwigComponent/ClassNameShouldNotEndWithComponentRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array
3737
$classNameString = $className->toString();
3838
if (str_ends_with($classNameString, 'Component')) {
3939
return [
40-
RuleErrorBuilder::message(sprintf('Twig component class "%s" should not end with "Component".', $classNameString))
40+
RuleErrorBuilder::message(sprintf('Twig component class "%s" must not end with "Component".', $classNameString))
4141
->identifier('symfonyUX.twigComponent.classNameShouldNotEndWithComponent')
4242
->line($className->getLine())
4343
->tip('Remove the "Component" suffix from the class name.')

src/Rules/TwigComponent/ExposePublicPropsShouldBeFalseRule.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public function processNode(Node $node, Scope $scope): array
5050
return [];
5151
}
5252

53+
/**
54+
* Extract the exposePublicProps value from the component attribute.
55+
*/
5356
private function getExposePublicPropsValue(Node\Attribute $attribute): ?bool
5457
{
5558
foreach ($attribute->args as $arg) {

src/Rules/TwigComponent/ForbiddenAttributesPropertyRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public function processNode(Node $node, Scope $scope): array
6868
}
6969

7070
/**
71+
* Extract the attributes variable name from the component attribute.
72+
*
7173
* @return array{name: string, custom: bool}|null
7274
*/
7375
private function getAttributesVarName(Node\Attribute $attribute): ?array

src/Rules/TwigComponent/MethodsShouldBePublicOrPrivateRule.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public function processNode(Node $node, Scope $scope): array
4646
}
4747

4848
$errors = [];
49-
$classReflection = $this->reflectionProvider->getClass($className);
50-
$abstractTraitMethods = $this->getAbstractTraitMethods($classReflection);
49+
$reflClass = $this->reflectionProvider->getClass($className);
50+
$abstractTraitMethods = $this->getAbstractTraitMethods($reflClass);
5151

5252
foreach ($node->getMethods() as $method) {
5353
if (! $method->isProtected()) {
@@ -62,7 +62,7 @@ public function processNode(Node $node, Scope $scope): array
6262
}
6363

6464
$errors[] = RuleErrorBuilder::message(
65-
sprintf('Method "%s()" in a Twig component should not be protected.', $methodName)
65+
sprintf('Method "%s()" in a Twig component must not be protected.', $methodName)
6666
)
6767
->identifier('symfonyUX.twigComponent.methodsShouldBePublicOrPrivate')
6868
->line($method->getLine())
@@ -77,7 +77,7 @@ public function processNode(Node $node, Scope $scope): array
7777
}
7878

7979
$reportedTraitMethods = [];
80-
foreach ($classReflection->getTraits() as $traitReflection) {
80+
foreach ($reflClass->getTraits() as $traitReflection) {
8181
// Use native reflection to get trait methods
8282
foreach ($traitReflection->getNativeReflection()->getMethods() as $traitMethod) {
8383
if (! $traitMethod->isProtected()) {
@@ -111,7 +111,7 @@ public function processNode(Node $node, Scope $scope): array
111111
}
112112

113113
$errors[] = RuleErrorBuilder::message(
114-
sprintf('Method "%s()" in a Twig component should not be protected.', $methodName)
114+
sprintf('Method "%s()" in a Twig component must not be protected.', $methodName)
115115
)
116116
->identifier('symfonyUX.twigComponent.methodsShouldBePublicOrPrivate')
117117
->line($lineNumber)
@@ -128,11 +128,11 @@ public function processNode(Node $node, Scope $scope): array
128128
*
129129
* @return array<string, true>
130130
*/
131-
private function getAbstractTraitMethods(ClassReflection $classReflection): array
131+
private function getAbstractTraitMethods(ClassReflection $reflClass): array
132132
{
133133
$abstractTraitMethods = [];
134134

135-
foreach ($classReflection->getTraits() as $traitReflection) {
135+
foreach ($reflClass->getTraits() as $traitReflection) {
136136
foreach ($traitReflection->getNativeReflection()->getMethods() as $method) {
137137
if ($method->isAbstract()) {
138138
$abstractTraitMethods[$method->getName()] = true;

src/Rules/TwigComponent/PublicPropertiesShouldBeCamelCaseRule.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function processNode(Node $node, Scope $scope): array
4141

4242
if (! $this->isCamelCase($propertyName)) {
4343
$errors[] = RuleErrorBuilder::message(
44-
sprintf('Public property "%s" in a Twig component should be in camelCase.', $propertyName)
44+
sprintf('Public property "%s" in a Twig component must be in camelCase.', $propertyName)
4545
)
4646
->identifier('symfonyUX.twigComponent.publicPropertiesShouldBeCamelCase')
4747
->line($property->getLine())
@@ -54,12 +54,18 @@ public function processNode(Node $node, Scope $scope): array
5454
return $errors;
5555
}
5656

57+
/**
58+
* Check if a property name is in camelCase format.
59+
*/
5760
private function isCamelCase(string $name): bool
5861
{
5962
// Property should start with a lowercase letter and contain no underscores
6063
return preg_match('/^[a-z][a-zA-Z0-9]*$/', $name) === 1;
6164
}
6265

66+
/**
67+
* Convert a string to camelCase format.
68+
*/
6369
private function toCamelCase(string $name): string
6470
{
6571
// Convert snake_case or PascalCase to camelCase

tests/Rules/LiveComponent/LiveActionMethodsShouldBePublicRule/LiveActionMethodsShouldBePublicRuleTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function testViolations(): void
1919
[__DIR__ . '/Fixture/LiveComponentWithPrivateLiveAction.php'],
2020
[
2121
[
22-
'LiveAction method "save()" should be public.',
22+
'LiveAction method "save()" must be public.',
2323
15,
2424
'Methods annotated with #[LiveAction] must be public to be accessible as component actions.',
2525
],
@@ -30,7 +30,7 @@ public function testViolations(): void
3030
[__DIR__ . '/Fixture/LiveComponentWithProtectedLiveAction.php'],
3131
[
3232
[
33-
'LiveAction method "delete()" should be public.',
33+
'LiveAction method "delete()" must be public.',
3434
15,
3535
'Methods annotated with #[LiveAction] must be public to be accessible as component actions.',
3636
],

tests/Rules/LiveComponent/LiveListenerMethodsShouldBePublicRule/LiveListenerMethodsShouldBePublicRuleTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ public function testViolations(): void
1919
[__DIR__ . '/Fixture/PrivateLiveListener.php'],
2020
[
2121
[
22-
'LiveListener method "onAnotherEvent()" should be public.',
22+
'LiveListener method "onAnotherEvent()" must be public.',
2323
13,
2424
'Change the method visibility to public.',
2525
],
2626
[
27-
'LiveListener method "onSomeEvent()" should be public.',
27+
'LiveListener method "onSomeEvent()" must be public.',
2828
18,
2929
'Change the method visibility to public.',
3030
],

0 commit comments

Comments
 (0)