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: public function getNodeType(): string
30: {
31: return ClassPropertyNode::class;
32: }
33:
34: public function processNode(Node $node, Scope $scope): array
35: {
36: $classReflection = $node->getClassReflection();
37: $propertyName = $node->getName();
38: $propertyPrototype = $this->findPrototype($classReflection, $propertyName);
39:
40: if (
41: null !== $propertyPrototype
42: && ! str_starts_with($propertyPrototype->getDeclaringClass()->getDisplayName(), 'Nexus\\')
43: ) {
44: return [];
45: }
46:
47: if (str_starts_with($propertyName, '_')) {
48: return [
49: RuleErrorBuilder::message(\sprintf(
50: 'Property %s::$%s should not start with an underscore.',
51: $classReflection->getDisplayName(),
52: $propertyName,
53: ))->identifier('nexus.propertyUnderscore')->build(),
54: ];
55: }
56:
57: if (preg_match('/^[a-z][a-zA-Z0-9]+$/', $propertyName) !== 1) {
58: return [
59: RuleErrorBuilder::message(\sprintf(
60: 'Property %s::$%s should be in camelCase format.',
61: $classReflection->getDisplayName(),
62: $propertyName,
63: ))->identifier('nexus.propertyCasing')->build(),
64: ];
65: }
66:
67: return [];
68: }
69:
70: private function findPrototype(ClassReflection $classReflection, string $propertyName): ?PhpPropertyReflection
71: {
72: $parentClass = $classReflection->getParentClass();
73:
74: if (null === $parentClass) {
75: return null;
76: }
77:
78: if (! $parentClass->hasNativeProperty($propertyName)) {
79: return null; // @codeCoverageIgnore
80: }
81:
82: $property = $parentClass->getNativeProperty($propertyName);
83:
84: if ($property->isPrivate()) {
85: return null; // @codeCoverageIgnore
86: }
87:
88: return $property;
89: }
90: }
91: