propertyNaming = $propertyNaming; $this->aliasNameResolver = $aliasNameResolver; $this->useImportsResolver = $useImportsResolver; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Type and name of catch exception should match', [new CodeSample(<<<'CODE_SAMPLE' class SomeClass { public function run() { try { // ... } catch (SomeException $typoException) { $typoException->getMessage(); } } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function run() { try { // ... } catch (SomeException $someException) { $someException->getMessage(); } } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [ClassMethod::class, Function_::class, Closure::class, FileWithoutNamespace::class, Namespace_::class]; } /** * @param ClassMethod|Function_|Closure|FileWithoutNamespace|Namespace_ $node */ public function refactor(Node $node) : ?Node { if ($node->stmts === null) { return null; } $hasChanged = \false; $uses = null; foreach ($node->stmts as $key => $stmt) { if ($this->shouldSkip($stmt)) { continue; } // variable defined first only resolvable by Scope pulled from Stmt $scope = $stmt->getAttribute(AttributeKey::SCOPE); if (!$scope instanceof Scope) { continue; } /** @var TryCatch $stmt */ $catch = $stmt->catches[0]; /** @var Variable $catchVar */ $catchVar = $catch->var; /** @var string $oldVariableName */ $oldVariableName = (string) $this->getName($catchVar); if ($uses === null) { $uses = $this->useImportsResolver->resolve(); } $type = $catch->types[0]; $typeShortName = $this->nodeNameResolver->getShortName($type); $aliasName = $this->aliasNameResolver->resolveByName($type, $uses); if (\is_string($aliasName)) { $typeShortName = $aliasName; } $newVariableName = $this->resolveNewVariableName($typeShortName); $objectType = new ObjectType($newVariableName); $newVariableName = $this->propertyNaming->fqnToVariableName($objectType); if ($oldVariableName === $newVariableName) { continue; } $isFoundInPrevious = $scope->hasVariableType($newVariableName)->yes(); if ($isFoundInPrevious) { return null; } $catch->var = new Variable($newVariableName); $this->renameVariableInStmts($catch, $oldVariableName, $newVariableName, $key, $node->stmts, $node->stmts[$key + 1] ?? null); $hasChanged = \true; } if ($hasChanged) { return $node; } return null; } private function resolveNewVariableName(string $typeShortName) : string { return Strings::replace(\lcfirst($typeShortName), self::STARTS_WITH_ABBREVIATION_REGEX, static function (array $matches) : string { $output = isset($matches[1]) ? \strtolower((string) $matches[1]) : ''; $output .= $matches[2] ?? ''; return $output . ($matches[3] ?? ''); }); } private function shouldSkip(Stmt $stmt) : bool { if (!$stmt instanceof TryCatch) { return \true; } if (\count($stmt->catches) !== 1) { return \true; } if (\count($stmt->catches[0]->types) !== 1) { return \true; } $catch = $stmt->catches[0]; return !$catch->var instanceof Variable; } /** * @param Stmt[] $stmts */ private function renameVariableInStmts(Catch_ $catch, string $oldVariableName, string $newVariableName, int $key, array $stmts, ?Stmt $stmt) : void { $this->traverseNodesWithCallable($catch->stmts, function (Node $node) use($oldVariableName, $newVariableName) { if (!$node instanceof Variable) { return null; } if (!$this->nodeNameResolver->isName($node, $oldVariableName)) { return null; } $node->name = $newVariableName; return null; }); $this->replaceNextUsageVariable($oldVariableName, $newVariableName, $key, $stmts, $stmt); } /** * @param Stmt[] $stmts */ private function replaceNextUsageVariable(string $oldVariableName, string $newVariableName, int $key, array $stmts, ?Node $nextNode) : void { if (!$nextNode instanceof Node) { return; } $nonAssignedVariables = []; $this->traverseNodesWithCallable($nextNode, function (Node $node) use($oldVariableName, &$nonAssignedVariables) : ?int { if ($node instanceof Assign && $node->var instanceof Variable) { return NodeTraverser::STOP_TRAVERSAL; } if (!$node instanceof Variable) { return null; } if (!$this->nodeNameResolver->isName($node, $oldVariableName)) { return null; } $nonAssignedVariables[] = $node; return null; }); foreach ($nonAssignedVariables as $nonAssignedVariable) { $nonAssignedVariable->name = $newVariableName; } if (!isset($stmts[$key + 1])) { return; } if (!isset($stmts[$key + 2])) { return; } $nextNode = $stmts[$key + 2]; $key += 2; $this->replaceNextUsageVariable($oldVariableName, $newVariableName, $key, $stmts, $nextNode); } }