8889841cREADME.md000066600000002034150443406370006034 0ustar00# sebastian/complexity Library for calculating the complexity of PHP code units. [![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/complexity.svg?style=flat-square)](https://packagist.org/packages/sebastian/complexity) [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.3-8892BF.svg?style=flat-square)](https://php.net/) [![CI Status](https://github.com/sebastianbergmann/complexity/workflows/CI/badge.svg?branch=master&event=push)](https://phpunit.de/build-status.html) [![Type Coverage](https://shepherd.dev/github/sebastianbergmann/complexity/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/complexity) ## Installation You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): ``` composer require sebastian/complexity ``` If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: ``` composer require --dev sebastian/complexity ``` src/Complexity/Complexity.php000066600000001531150443406370012350 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; /** * @psalm-immutable */ final class Complexity { /** * @var string */ private $name; /** * @var int */ private $cyclomaticComplexity; public function __construct(string $name, int $cyclomaticComplexity) { $this->name = $name; $this->cyclomaticComplexity = $cyclomaticComplexity; } public function name(): string { return $this->name; } public function cyclomaticComplexity(): int { return $this->cyclomaticComplexity; } } src/Complexity/ComplexityCollectionIterator.php000066600000002045150443406370016077 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; use Iterator; final class ComplexityCollectionIterator implements Iterator { /** * @psalm-var list */ private $items; /** * @var int */ private $position = 0; public function __construct(ComplexityCollection $items) { $this->items = $items->asArray(); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return isset($this->items[$this->position]); } public function key(): int { return $this->position; } public function current(): Complexity { return $this->items[$this->position]; } public function next(): void { $this->position++; } } src/Complexity/ComplexityCollection.php000066600000002722150443406370014367 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; use function count; use Countable; use IteratorAggregate; /** * @psalm-immutable */ final class ComplexityCollection implements Countable, IteratorAggregate { /** * @psalm-var list */ private $items = []; public static function fromList(Complexity ...$items): self { return new self($items); } /** * @psalm-param list $items */ private function __construct(array $items) { $this->items = $items; } /** * @psalm-return list */ public function asArray(): array { return $this->items; } public function getIterator(): ComplexityCollectionIterator { return new ComplexityCollectionIterator($this); } public function count(): int { return count($this->items); } public function isEmpty(): bool { return empty($this->items); } public function cyclomaticComplexity(): int { $cyclomaticComplexity = 0; foreach ($this as $item) { $cyclomaticComplexity += $item->cyclomaticComplexity(); } return $cyclomaticComplexity; } } src/Calculator.php000066600000004643150443406370010156 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; use PhpParser\Error; use PhpParser\Lexer; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\ParentConnectingVisitor; use PhpParser\Parser; use PhpParser\ParserFactory; final class Calculator { /** * @throws RuntimeException */ public function calculateForSourceFile(string $sourceFile): ComplexityCollection { return $this->calculateForSourceString(file_get_contents($sourceFile)); } /** * @throws RuntimeException */ public function calculateForSourceString(string $source): ComplexityCollection { try { $nodes = $this->parser()->parse($source); assert($nodes !== null); return $this->calculateForAbstractSyntaxTree($nodes); // @codeCoverageIgnoreStart } catch (Error $error) { throw new RuntimeException( $error->getMessage(), (int) $error->getCode(), $error ); } // @codeCoverageIgnoreEnd } /** * @param Node[] $nodes * * @throws RuntimeException */ public function calculateForAbstractSyntaxTree(array $nodes): ComplexityCollection { $traverser = new NodeTraverser; $complexityCalculatingVisitor = new ComplexityCalculatingVisitor(true); $traverser->addVisitor(new NameResolver); $traverser->addVisitor(new ParentConnectingVisitor); $traverser->addVisitor($complexityCalculatingVisitor); try { /* @noinspection UnusedFunctionResultInspection */ $traverser->traverse($nodes); // @codeCoverageIgnoreStart } catch (Error $error) { throw new RuntimeException( $error->getMessage(), (int) $error->getCode(), $error ); } // @codeCoverageIgnoreEnd return $complexityCalculatingVisitor->result(); } private function parser(): Parser { return (new ParserFactory)->create(ParserFactory::PREFER_PHP7, new Lexer); } } src/Visitor/ComplexityCalculatingVisitor.php000066600000005561150443406370015410 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; use function assert; use function is_array; use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Trait_; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; final class ComplexityCalculatingVisitor extends NodeVisitorAbstract { /** * @psalm-var list */ private $result = []; /** * @var bool */ private $shortCircuitTraversal; public function __construct(bool $shortCircuitTraversal) { $this->shortCircuitTraversal = $shortCircuitTraversal; } public function enterNode(Node $node): ?int { if (!$node instanceof ClassMethod && !$node instanceof Function_) { return null; } if ($node instanceof ClassMethod) { $name = $this->classMethodName($node); } else { $name = $this->functionName($node); } $statements = $node->getStmts(); assert(is_array($statements)); $this->result[] = new Complexity( $name, $this->cyclomaticComplexity($statements) ); if ($this->shortCircuitTraversal) { return NodeTraverser::DONT_TRAVERSE_CHILDREN; } return null; } public function result(): ComplexityCollection { return ComplexityCollection::fromList(...$this->result); } /** * @param Stmt[] $statements */ private function cyclomaticComplexity(array $statements): int { $traverser = new NodeTraverser; $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor; $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); /* @noinspection UnusedFunctionResultInspection */ $traverser->traverse($statements); return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); } private function classMethodName(ClassMethod $node): string { $parent = $node->getAttribute('parent'); assert($parent instanceof Class_ || $parent instanceof Trait_); assert(isset($parent->namespacedName)); assert($parent->namespacedName instanceof Name); return $parent->namespacedName->toString() . '::' . $node->name->toString(); } private function functionName(Function_ $node): string { assert(isset($node->namespacedName)); assert($node->namespacedName instanceof Name); return $node->namespacedName->toString(); } } src/Visitor/CyclomaticComplexityCalculatingVisitor.php000066600000003222150443406370017410 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; use function get_class; use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\BooleanAnd; use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PhpParser\Node\Expr\BinaryOp\LogicalAnd; use PhpParser\Node\Expr\BinaryOp\LogicalOr; use PhpParser\Node\Expr\Ternary; use PhpParser\Node\Stmt\Case_; use PhpParser\Node\Stmt\Catch_; use PhpParser\Node\Stmt\ElseIf_; use PhpParser\Node\Stmt\For_; use PhpParser\Node\Stmt\Foreach_; use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\While_; use PhpParser\NodeVisitorAbstract; final class CyclomaticComplexityCalculatingVisitor extends NodeVisitorAbstract { /** * @var int */ private $cyclomaticComplexity = 1; public function enterNode(Node $node): void { /* @noinspection GetClassMissUseInspection */ switch (get_class($node)) { case BooleanAnd::class: case BooleanOr::class: case Case_::class: case Catch_::class: case ElseIf_::class: case For_::class: case Foreach_::class: case If_::class: case LogicalAnd::class: case LogicalOr::class: case Ternary::class: case While_::class: $this->cyclomaticComplexity++; } } public function cyclomaticComplexity(): int { return $this->cyclomaticComplexity; } } src/Exception/RuntimeException.php000066600000000603150443406370013315 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; final class RuntimeException extends \RuntimeException implements Exception { } src/Exception/Exception.php000066600000000555150443406370011757 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Complexity; use Throwable; interface Exception extends Throwable { } ChangeLog.md000066600000001600150443406370006724 0ustar00# ChangeLog All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. ## [2.0.2] - 2020-10-26 ### Fixed * `SebastianBergmann\Complexity\Exception` now correctly extends `\Throwable` ## [2.0.1] - 2020-09-28 ### Changed * Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` ## [2.0.0] - 2020-07-25 ### Removed * The `ParentConnectingVisitor` has been removed (it should have been marked as `@internal`) ## [1.0.0] - 2020-07-22 * Initial release [2.0.2]: https://github.com/sebastianbergmann/complexity/compare/2.0.1...2.0.2 [2.0.1]: https://github.com/sebastianbergmann/complexity/compare/2.0.0...2.0.1 [2.0.0]: https://github.com/sebastianbergmann/complexity/compare/1.0.0...2.0.0 [1.0.0]: https://github.com/sebastianbergmann/complexity/compare/70ee0ad32d9e2be3f85beffa3e2eb474193f2487...1.0.0 .psalm/baseline.xml000066600000000157150443406370010257 0ustar00 .psalm/config.xml000066600000000752150443406370007743 0ustar00 composer.json000066600000001701150443406370007277 0ustar00{ "name": "sebastian/complexity", "description": "Library for calculating the complexity of PHP code units", "type": "library", "homepage": "https://github.com/sebastianbergmann/complexity", "license": "BSD-3-Clause", "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", "role": "lead" } ], "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues" }, "require": { "php": ">=7.3", "nikic/php-parser": "^4.7" }, "require-dev": { "phpunit/phpunit": "^9.3" }, "config": { "platform": { "php": "7.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "autoload": { "classmap": [ "src/" ] }, "extra": { "branch-alias": { "dev-master": "2.0-dev" } } } LICENSE000066600000003016150443406370005563 0ustar00sebastian/complexity Copyright (c) 2020, Sebastian Bergmann . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sebastian Bergmann nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.