diff --git a/composer.json b/composer.json index fe5c764..cad7c96 100644 --- a/composer.json +++ b/composer.json @@ -20,5 +20,8 @@ "psr-4": { "NdB\\PhpDocCheck\\": "src" } + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.4" } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..a213ca6 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,7 @@ + + + ./src + ./bin/php-doc-check + */vendor/* + + diff --git a/src/AnalysableFile.php b/src/AnalysableFile.php index 54dcbcb..5f1b578 100644 --- a/src/AnalysableFile.php +++ b/src/AnalysableFile.php @@ -1,34 +1,38 @@ file = $file; - $this->parser = $parser; - $this->arguments = $arguments; - } + public function __construct(\SplFileInfo $file, \PhpParser\Parser $parser, $arguments) + { + $this->file = $file; + $this->parser = $parser; + $this->arguments = $arguments; + } - public function analyse(){ - $statements = $this->parser->parse(file_get_contents($this->file->getRealPath())); - $traverser = new \PhpParser\NodeTraverser(); - $traverser->addVisitor(new \NdB\PhpDocCheck\NodeVisitor($this)); - $traverser->traverse($statements); - return $this->getProgressIndicator(); - } + public function analyse() + { + $statements = $this->parser->parse(file_get_contents($this->file->getRealPath())); + $traverser = new \PhpParser\NodeTraverser(); + $traverser->addVisitor(new \NdB\PhpDocCheck\NodeVisitor($this)); + $traverser->traverse($statements); + return $this->getProgressIndicator(); + } - /** - * Gives a visual progression value, based on analysis result - */ - public function getProgressIndicator() : string{ - if(!$this->has_warnings && !$this->has_errors ){ - return '.'; - }elseif(!$this->has_errors){ - return 'W'; - } - return 'E'; - } -} \ No newline at end of file + /** + * Gives a visual progression value, based on analysis result + */ + public function getProgressIndicator() : string + { + if (!$this->has_warnings && !$this->has_errors) { + return '.'; + } elseif (!$this->has_errors) { + return 'W'; + } + return 'E'; + } +} diff --git a/src/Findings/Error.php b/src/Findings/Error.php index 409430f..9a45197 100644 --- a/src/Findings/Error.php +++ b/src/Findings/Error.php @@ -1,8 +1,10 @@ message = $message; - $this->line = $line; - } +class Warning +{ + public function __construct(string $message, int $line) + { + $this->message = $message; + $this->line = $line; + } - public function getType():string{ - return 'Warning'; - } - public function getLine():int{ - return $this->line; - } - public function getMessage():string{ - return $this->message; - } -} \ No newline at end of file + public function getType():string + { + return 'Warning'; + } + public function getLine():int + { + return $this->line; + } + public function getMessage():string + { + return $this->message; + } +} diff --git a/src/NodeVisitor.php b/src/NodeVisitor.php index 0a29bac..f92d6c8 100644 --- a/src/NodeVisitor.php +++ b/src/NodeVisitor.php @@ -5,73 +5,83 @@ use \PhpParser\Node; use \PhpParser\Node\Stmt; -class NodeVisitor extends \PhpParser\NodeVisitorAbstract{ - public function __construct(AnalysableFile &$file){ - $this->file =& $file; - } +class NodeVisitor extends \PhpParser\NodeVisitorAbstract +{ + public function __construct(AnalysableFile &$file) + { + $this->file =& $file; + } - /** - * Determines if this node and it's children are of a complexity that could - * use some clarification based on Cyclomatic Complexity. - */ - public function leaveNode(\PhpParser\Node $node) { - if(is_a($node, "\PhpParser\Node\FunctionLike")){ - $methodCcn = $this->calculate_complexity($node) + 1; // each method by default is CCN 1 even if it's empty - - $name = 'Anonynous function'; - if(\property_exists($node, 'name')){ - $name = $node->name; - } - if(empty($node->getDocComment())){ - if($methodCcn >= $this->file->arguments['complexity-error-treshold']){ - $this->file->has_errors = true; - $this->file->findings[] = new \NdB\PhpDocCheck\Findings\Error(sprintf("%s has no documentation and a complexity of %d", $name, $methodCcn), $node->getStartLine()); - }elseif($methodCcn >= $this->file->arguments['complexity-warning-treshold']){ - $this->file->has_warnings = true; - $this->file->findings[] = new \NdB\PhpDocCheck\Findings\Warning(sprintf("%s has no documentation and a complexity of %d", $name, $methodCcn), $node->getStartLine()); - } - } - } - } - - /** - * Recursively calculates Cyclomatic Complexity - */ - protected function calculate_complexity($node){ - $ccn = 0; - foreach (get_object_vars($node) as $name => $member) { - foreach (is_array($member) ? $member : [$member] as $memberItem) { - if ($memberItem instanceof Node) { - $ccn += $this->calculate_complexity($memberItem); - } - } - } - switch (true) { - case $node instanceof Stmt\If_: - case $node instanceof Stmt\ElseIf_: - case $node instanceof Stmt\For_: - case $node instanceof Stmt\Foreach_: - case $node instanceof Stmt\While_: - case $node instanceof Stmt\Do_: - case $node instanceof Node\Expr\BinaryOp\LogicalAnd: - case $node instanceof Node\Expr\BinaryOp\LogicalOr: - case $node instanceof Node\Expr\BinaryOp\LogicalXor: - case $node instanceof Node\Expr\BinaryOp\BooleanAnd: - case $node instanceof Node\Expr\BinaryOp\BooleanOr: - case $node instanceof Stmt\Catch_: - case $node instanceof Node\Expr\Ternary: - case $node instanceof Node\Expr\BinaryOp\Coalesce: - $ccn++; - break; - case $node instanceof Stmt\Case_: // include default - if ($node->cond !== null) { // exclude default - $ccn++; - } - break; - case $node instanceof Node\Expr\BinaryOp\Spaceship: - $ccn += 2; - break; - } - return $ccn; - } -} \ No newline at end of file + /** + * Determines if this node and it's children are of a complexity that could + * use some clarification based on Cyclomatic Complexity. + */ + public function leaveNode(\PhpParser\Node $node) + { + if (is_a($node, "\PhpParser\Node\FunctionLike")) { + $methodCcn = $this->calculateComplexity($node) + 1; // each method by default is CCN 1 even if it's empty + + $name = 'Anonynous function'; + if (\property_exists($node, 'name')) { + $name = $node->name; + } + if (empty($node->getDocComment())) { + if ($methodCcn >= $this->file->arguments['complexity-error-treshold']) { + $this->file->has_errors = true; + $this->file->findings[] = new \NdB\PhpDocCheck\Findings\Error( + sprintf("%s has no documentation and a complexity of %d", $name, $methodCcn), + $node->getStartLine() + ); + } elseif ($methodCcn >= $this->file->arguments['complexity-warning-treshold']) { + $this->file->has_warnings = true; + $this->file->findings[] = new \NdB\PhpDocCheck\Findings\Warning( + sprintf("%s has no documentation and a complexity of %d", $name, $methodCcn), + $node->getStartLine() + ); + } + } + } + } + + /** + * Recursively calculates Cyclomatic Complexity + */ + protected function calculateComplexity($node) + { + $ccn = 0; + foreach (get_object_vars($node) as $name => $member) { + foreach (is_array($member) ? $member : [$member] as $memberItem) { + if ($memberItem instanceof Node) { + $ccn += $this->calculateComplexity($memberItem); + } + } + } + switch (true) { + case $node instanceof Stmt\If_: + case $node instanceof Stmt\ElseIf_: + case $node instanceof Stmt\For_: + case $node instanceof Stmt\Foreach_: + case $node instanceof Stmt\While_: + case $node instanceof Stmt\Do_: + case $node instanceof Node\Expr\BinaryOp\LogicalAnd: + case $node instanceof Node\Expr\BinaryOp\LogicalOr: + case $node instanceof Node\Expr\BinaryOp\LogicalXor: + case $node instanceof Node\Expr\BinaryOp\BooleanAnd: + case $node instanceof Node\Expr\BinaryOp\BooleanOr: + case $node instanceof Stmt\Catch_: + case $node instanceof Node\Expr\Ternary: + case $node instanceof Node\Expr\BinaryOp\Coalesce: + $ccn++; + break; + case $node instanceof Stmt\Case_: // include default + if ($node->cond !== null) { // exclude default + $ccn++; + } + break; + case $node instanceof Node\Expr\BinaryOp\Spaceship: + $ccn += 2; + break; + } + return $ccn; + } +} diff --git a/src/Output/AbstractOutput.php b/src/Output/AbstractOutput.php index 10cb859..bf9a303 100644 --- a/src/Output/AbstractOutput.php +++ b/src/Output/AbstractOutput.php @@ -1,25 +1,29 @@ files = $files; - } +abstract class AbstractOutput +{ + public function __construct(array $files) + { + $this->files = $files; + } - public function display(){ - echo $this->get(); - } + public function display() + { + echo $this->get(); + } - /** - * Determines if this scan has 'failed' and should be fixed. Or if it was - * flawless. CI will fail when a non zero exit code is returned. - */ - public function getExitCode(){ - foreach($this->files as $file){ - if($file->has_errors || $file->has_warnings){ - return 1; - } - } - return 0; - } -} \ No newline at end of file + /** + * Determines if this scan has 'failed' and should be fixed. Or if it was + * flawless. CI will fail when a non zero exit code is returned. + */ + public function getExitCode() + { + foreach ($this->files as $file) { + if ($file->has_errors || $file->has_warnings) { + return 1; + } + } + return 0; + } +} diff --git a/src/Output/Json.php b/src/Output/Json.php index d72df1d..0257f72 100644 --- a/src/Output/Json.php +++ b/src/Output/Json.php @@ -1,17 +1,19 @@ files as $file){ - if(!empty($file->findings)){ - $output[] = array( - 'file'=>$file->file->getRealPath(), - 'findings'=>$file->findings, - ); - } - } - return json_encode($output); - } -} \ No newline at end of file +class Json +{ + public function get() + { + $output = array(); + foreach ($this->files as $file) { + if (!empty($file->findings)) { + $output[] = array( + 'file'=>$file->file->getRealPath(), + 'findings'=>$file->findings, + ); + } + } + return json_encode($output); + } +} diff --git a/src/Output/Text.php b/src/Output/Text.php index d05aea5..3afc82f 100644 --- a/src/Output/Text.php +++ b/src/Output/Text.php @@ -1,35 +1,37 @@ files as $file){ - if(!empty($file->findings)){ - $output .= "\n"; - $output .= sprintf("Missing documentation in %s\n", $file->file->getRealPath()); - $header = array( - 'Severity', - 'Message', - 'Line' - ); - $rows = array(); - foreach($file->findings as $finding){ - $rows[] = array( - $finding->getType(), - $finding->getMessage(), - $finding->getLine() - ); - } - $lines = (new \cli\Table($header, $rows))->getDisplayLines(); - foreach($lines as $line){ - $output .= $line. "\n"; - } - } - } - return $output; - } -} \ No newline at end of file +class Text extends AbstractOutput +{ + /** + * Outputs findings for each file analysed in a table form + */ + public function get() + { + $output = ''; + foreach ($this->files as $file) { + if (!empty($file->findings)) { + $output .= "\n"; + $output .= sprintf("Missing documentation in %s\n", $file->file->getRealPath()); + $header = array( + 'Severity', + 'Message', + 'Line' + ); + $rows = array(); + foreach ($file->findings as $finding) { + $rows[] = array( + $finding->getType(), + $finding->getMessage(), + $finding->getLine() + ); + } + $lines = (new \cli\Table($header, $rows))->getDisplayLines(); + foreach ($lines as $line) { + $output .= $line. "\n"; + } + } + } + return $output; + } +}