-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit for an @throws check. Small modifications for retrievi…
…ng a list of fully exception types (with their namespaces expanded) thrown by a function or method from the Symbol table.
- Loading branch information
1 parent
caccd65
commit 1f4b30f
Showing
1 changed file
with
83 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
namespace BambooHR\Guardrail\Checks; | ||
|
||
use BambooHR\Guardrail\Checks\BaseCheck; | ||
use BambooHR\Guardrail\NodeVisitors\ForEachNode; | ||
use BambooHR\Guardrail\Scope; | ||
use BambooHR\Guardrail\TypeComparer; | ||
use BambooHR\Guardrail\Util; | ||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\ClassLike; | ||
|
||
class ThrowsCheck extends BaseCheck { | ||
|
||
function getCheckNodeTypes() { | ||
return [Node\Expr\Throw_::class, Node\Stmt\Throw_::class, | ||
Node\Expr\MethodCall::class, | ||
Node\Expr\FuncCall::class, | ||
Node\Expr\StaticCall::class | ||
]; | ||
} | ||
|
||
public function run($fileName, Node $node, ClassLike $inside = null, Scope $scope = null) { | ||
|
||
if ($node instanceof Node\Expr\Throw_ || $node instanceof Node\Stmt\Throw_) { | ||
$throws = $node->expr->getAttribute(TypeComparer::INFERRED_TYPE_ATTR); | ||
if ($throws instanceof Node\Name) { | ||
if (!$this->parentCatches($scope->getParentNodes(), $throws) && | ||
!$this->isDocumentedThrow($scope->getInsideFunction(), $throws) | ||
) { | ||
$this->emitError($fileName, $node, ErrorConstants::TYPE_UNDOCUMENTED_EXCEPTION,"Undocumented exception ($throws) thrown"); | ||
} | ||
} | ||
} else if ($node instanceof Node\Expr\MethodCall && $node->name instanceof Node\Identifier) { | ||
$type=$node->getAttribute(TypeComparer::INFERRED_TYPE_ATTR); | ||
if ($type) { | ||
TypeComparer::forEachType($type, function($typeNode) use ($node,$scope, $fileName) { | ||
$method = Util::findAbstractedMethod($typeNode, $node->name, $this->symbolTable ); | ||
if ($method) { | ||
$throws=$method->getThrowsList(); | ||
foreach($throws as $throw) { | ||
if (!$this->parentCatches($scope->getParentNodes(), $throw) && | ||
!$this->isDocumentedThrow($scope->getInsideFunction(), $throw)) | ||
{ | ||
$this->emitError($fileName, $node, ErrorConstants::TYPE_UNDOCUMENTED_EXCEPTION,"Undocumented exception ($throw) thrown by ".$typeNode."::".$node->name); | ||
} | ||
} | ||
|
||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
function isDocumentedThrow(?Node $node, string $throw) { | ||
if ($node) { | ||
$documentedThrows = $node->getAttribute('throws', []); | ||
foreach ($documentedThrows as $documentedThrow) { | ||
if (strcasecmp($documentedThrow, $throw) == 0) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function parentCatches($parents, $throws):bool { | ||
foreach (array_reverse($parents) as $parent) { | ||
if ($parent instanceof Node\Stmt\TryCatch) { | ||
foreach ($parent->catches as $catch) { | ||
foreach ($catch->types as $type) { | ||
if ($this->symbolTable->isParentClassOrInterface($type,$throws)) { | ||
return true; | ||
} | ||
} | ||
} | ||
} else if($parent instanceof Node\FunctionLike) { | ||
return false; | ||
} | ||
} | ||
return false; | ||
} | ||
} |