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