|  | 
|  | 1 | +<?php | 
|  | 2 | + | 
|  | 3 | +namespace ReturnListRule; | 
|  | 4 | + | 
|  | 5 | +use PhpParser\Node; | 
|  | 6 | +use PhpParser\Node\Expr; | 
|  | 7 | +use PhpParser\Node\Expr\BinaryOp; | 
|  | 8 | +use PHPStan\Analyser\Scope; | 
|  | 9 | +use PHPStan\Rules\Rule; | 
|  | 10 | +use PHPStan\Rules\RuleError; | 
|  | 11 | +use PHPStan\Rules\RuleErrorBuilder; | 
|  | 12 | +use PHPStan\Type\Type; | 
|  | 13 | +use PHPStan\Type\VerbosityLevel; | 
|  | 14 | +/** | 
|  | 15 | + * @implements Rule<BinaryOp> | 
|  | 16 | + */ | 
|  | 17 | +class BinaryOpEnumValueRule implements Rule | 
|  | 18 | +{ | 
|  | 19 | + | 
|  | 20 | +/** @var class-string<BinaryOp> */ | 
|  | 21 | +private string $className; | 
|  | 22 | + | 
|  | 23 | +/** | 
|  | 24 | + * @param class-string $operator | 
|  | 25 | + */ | 
|  | 26 | +public function __construct(string $operator, ?string $okMessage = null) | 
|  | 27 | +{ | 
|  | 28 | +$this->className = $operator; | 
|  | 29 | +} | 
|  | 30 | + | 
|  | 31 | +public function getNodeType(): string | 
|  | 32 | +{ | 
|  | 33 | +return $this->className; | 
|  | 34 | +} | 
|  | 35 | + | 
|  | 36 | +/** | 
|  | 37 | + * @param BinaryOp $node | 
|  | 38 | + * @return list<RuleError> | 
|  | 39 | + */ | 
|  | 40 | +public function processNode(Node $node, Scope $scope): array | 
|  | 41 | +{ | 
|  | 42 | +$leftType = $scope->getType($node->left); | 
|  | 43 | +$rightType = $scope->getType($node->right); | 
|  | 44 | +$isDirectCompareType = true; | 
|  | 45 | + | 
|  | 46 | +if (!$this->isEnumWithValue($leftType) || !$this->isEnumWithValue($rightType)) { | 
|  | 47 | +$isDirectCompareType = false; | 
|  | 48 | +} | 
|  | 49 | + | 
|  | 50 | +$errors = []; | 
|  | 51 | +$leftError = $this->processOpExpression($node->left, $leftType, $node->getOperatorSigil()); | 
|  | 52 | +$rightError = $this->processOpExpression($node->right, $rightType, $node->getOperatorSigil()); | 
|  | 53 | + | 
|  | 54 | +if ($leftError !== null) { | 
|  | 55 | +$errors[] = $leftError; | 
|  | 56 | +} | 
|  | 57 | + | 
|  | 58 | +if ($rightError !== null && $rightError !== $leftError) { | 
|  | 59 | +$errors[] = $rightError; | 
|  | 60 | +} | 
|  | 61 | + | 
|  | 62 | +if (!$isDirectCompareType && $errors === []) { | 
|  | 63 | +return []; | 
|  | 64 | +} | 
|  | 65 | + | 
|  | 66 | +if ($isDirectCompareType && $errors === []) { | 
|  | 67 | +$errors[] = sprintf( | 
|  | 68 | +'Cannot compare %s to %s', | 
|  | 69 | +$leftType->describe(VerbosityLevel::typeOnly()), | 
|  | 70 | +$rightType->describe(VerbosityLevel::typeOnly()), | 
|  | 71 | +); | 
|  | 72 | +} | 
|  | 73 | + | 
|  | 74 | +return array_map(static fn (string $message) => RuleErrorBuilder::message($message)->build(), $errors); | 
|  | 75 | +} | 
|  | 76 | + | 
|  | 77 | +private function processOpExpression(Expr $expression, Type $expressionType, string $sigil): ?string | 
|  | 78 | +{ | 
|  | 79 | +return null; | 
|  | 80 | +} | 
|  | 81 | + | 
|  | 82 | +private function isEnumWithValue(Type $type): bool | 
|  | 83 | +{ | 
|  | 84 | +return false; | 
|  | 85 | +} | 
|  | 86 | + | 
|  | 87 | +} | 
0 commit comments