1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus framework.
7: *
8: * (c) John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\PHPStan\Rules\Properties;
15:
16: use PhpParser\Node;
17: use PHPStan\Analyser\Scope;
18: use PHPStan\Node\ClassPropertyNode;
19: use PHPStan\Reflection\ClassReflection;
20: use PHPStan\Reflection\Php\PhpPropertyReflection;
21: use PHPStan\Rules\Rule;
22: use PHPStan\Rules\RuleErrorBuilder;
23:
24: /**
25: * @implements Rule<ClassPropertyNode>
26: */
27: final class PropertyNamingRule implements Rule
28: {
29: #[\Override]
30: public function getNodeType(): string
31: {
32: return ClassPropertyNode::class;
33: }
34:
35: #[\Override]
36: public function processNode(Node $node, Scope $scope): array
37: {
38: $classReflection = $node->getClassReflection();
39: $propertyName = $node->getName();
40: $propertyPrototype = self::findPrototype($classReflection, $propertyName);
41:
42: if (
43: null !== $propertyPrototype
44: && ! str_starts_with($propertyPrototype->getDeclaringClass()->getDisplayName(), 'Nexus\\')
45: ) {
46: return [];
47: }
48:
49: if (str_starts_with($propertyName, '_')) {
50: return [
51: RuleErrorBuilder::message(\sprintf(
52: 'Property %s::$%s should not start with an underscore.',
53: $classReflection->getDisplayName(),
54: $propertyName,
55: ))->identifier('nexus.propertyUnderscore')->build(),
56: ];
57: }
58:
59: if (preg_match('/^[a-z][a-zA-Z0-9]+$/', $propertyName) !== 1) {
60: return [
61: RuleErrorBuilder::message(\sprintf(
62: 'Property %s::$%s should be in camelCase format.',
63: $classReflection->getDisplayName(),
64: $propertyName,
65: ))->identifier('nexus.propertyCasing')->build(),
66: ];
67: }
68:
69: return [];
70: }
71:
72: private static function findPrototype(ClassReflection $classReflection, string $propertyName): ?PhpPropertyReflection
73: {
74: $parentClass = $classReflection->getParentClass();
75:
76: if (null === $parentClass) {
77: return null;
78: }
79:
80: if (! $parentClass->hasNativeProperty($propertyName)) {
81: return null; // @codeCoverageIgnore
82: }
83:
84: $property = $parentClass->getNativeProperty($propertyName);
85:
86: if ($property->isPrivate()) {
87: return null; // @codeCoverageIgnore
88: }
89:
90: return $property;
91: }
92: }
93: