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\Constants;
15:
16: use PhpParser\Node;
17: use PHPStan\Analyser\Scope;
18: use PHPStan\Reflection\ClassConstantReflection;
19: use PHPStan\Reflection\ClassReflection;
20: use PHPStan\Rules\IdentifierRuleError;
21: use PHPStan\Rules\Rule;
22: use PHPStan\Rules\RuleErrorBuilder;
23: use PHPStan\ShouldNotHappenException;
24:
25: /**
26: * @implements Rule<Node\Stmt\ClassConst>
27: */
28: final class ClassConstantNamingRule implements Rule
29: {
30: #[\Override]
31: public function getNodeType(): string
32: {
33: return Node\Stmt\ClassConst::class;
34: }
35:
36: #[\Override]
37: public function processNode(Node $node, Scope $scope): array
38: {
39: if (! $scope->isInClass()) {
40: throw new ShouldNotHappenException(); // @codeCoverageIgnore
41: }
42:
43: $errors = [];
44:
45: foreach ($node->consts as $const) {
46: $errors = [
47: ...$errors,
48: ...self::processSingleConstant($scope->getClassReflection(), $const),
49: ];
50: }
51:
52: return $errors;
53: }
54:
55: /**
56: * @return list<IdentifierRuleError>
57: */
58: private static function processSingleConstant(ClassReflection $classReflection, Node\Const_ $const): array
59: {
60: $constantName = $const->name->toString();
61: $prototype = self::findPrototype($classReflection, $constantName);
62:
63: if (
64: null !== $prototype
65: && ! str_starts_with($prototype->getDeclaringClass()->getDisplayName(), 'Nexus\\')
66: ) {
67: return [];
68: }
69:
70: if (preg_match('/^[A-Z][A-Z0-9_]+$/', $constantName) !== 1) {
71: return [
72: RuleErrorBuilder::message(\sprintf(
73: 'Constant %s::%s should be in UPPER_SNAKE_CASE format.',
74: $classReflection->getDisplayName(),
75: $constantName,
76: ))
77: ->identifier('nexus.constantCasing')
78: ->line($const->getStartLine())
79: ->build(),
80: ];
81: }
82:
83: return [];
84: }
85:
86: private static function findPrototype(ClassReflection $classReflection, string $constantName): ?ClassConstantReflection
87: {
88: foreach ($classReflection->getImmediateInterfaces() as $immediateInterface) {
89: if ($immediateInterface->hasConstant($constantName)) {
90: return $immediateInterface->getConstant($constantName); // @codeCoverageIgnore
91: }
92: }
93:
94: $parentClass = $classReflection->getParentClass();
95:
96: if (null === $parentClass) {
97: return null; // @codeCoverageIgnore
98: }
99:
100: if (! $parentClass->hasConstant($constantName)) {
101: return null;
102: }
103:
104: $constant = $parentClass->getConstant($constantName);
105:
106: if ($constant->isPrivate()) {
107: return null; // @codeCoverageIgnore
108: }
109:
110: return $constant;
111: }
112: }
113: