[
  {
    "path": ".gitignore",
    "content": ".idea/\nvendor/\nbuild/\ncomposer.lock\n.phpunit.result.cache\n.phpunit.cache\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "tools:\n  external_code_coverage: true\n  php_code_sniffer:\n    config:\n      standard: PSR2\nchecks:\n  php:\n    code_rating: true\n    duplication: true\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\ninstall:\n  - composer selfupdate\n  - composer install --prefer-source\n\njobs:\n  include:\n    - stage: Unit tests\n      script:\n        - vendor/bin/phpunit\n      php: 5.6\n    - stage: Unit tests\n      script:\n        - vendor/bin/phpunit\n      php: 7.0\n    - stage: Unit tests\n      script:\n        - vendor/bin/phpunit\n      php: 7.1\n    - stage: Unit tests\n      script:\n        - vendor/bin/phpunit\n      php: 7.2\n    - stage: Unit tests\n      script:\n        - vendor/bin/phpunit\n      php: 7.3\n    - stage: Code coverage\n      script:\n        - vendor/bin/phpunit\n        - if [ \"$TRAVIS_PULL_REQUEST\" = \"false\" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi\n        - if [ \"$TRAVIS_PULL_REQUEST\" = \"false\" ]; then php ocular.phar code-coverage:upload --format=php-clover build/reports/phpunit.xml; fi\n      php: 7.3\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\nList of changes since the very first version :)\n\n## 0.9.1 - 2025-03-22\n- Support nikic/php-parser:5.4\n- Update to PHPUnit 11\n\n## 0.9.0 - 2025-03-20\n- Support nikic/php-parser:5.0\n\n## 0.8.1 - 2022-02-15\n- Fix deprecated error\n\n## 0.7.0 - 2018-03-01\n- Added an option to exclude directories\n\n## 0.5.0 - 2016-11-15\n- Add PHP 7.0 support and drop 5.5 support\n\n## 0.4.0 - 2015-08-23\n- Add metrics: \"x out of y bool expressions are assumptions (z%)\"\n\n## 0.3.0 - 2015-08-15\n- Add XML output (added -f and -o arguments)\n- Migrated PhpSpec tests to PHPUnit\n- Pretty format now outputs a table\n\n## 0.2.0 - 2015-08-10\n- Added more weak assumptions detections\n\n## 0.1.0 - 2015-08-05\n- Initial release\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Rick Kuipers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# PHP Assumptions\n[![Build Status](https://scrutinizer-ci.com/g/rskuipers/php-assumptions/badges/build.png?b=master)](https://scrutinizer-ci.com/g/rskuipers/php-assumptions/build-status/master)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/rskuipers/php-assumptions/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/rskuipers/php-assumptions/?branch=master)\n[![Code Coverage](https://scrutinizer-ci.com/g/rskuipers/php-assumptions/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/rskuipers/php-assumptions/?branch=master)\n\n## Setup\n```sh\n$ composer require --dev rskuipers/php-assumptions\n```\n\n## Introduction\nPHP Assumptions is the result of a proof of concept inspired by the \"[From assumptions to assertions](http://rskuipers.com/entry/from-assumptions-to-assertions)\" blog post.\nIt's a static code analysis tool doing checks for weak assumptions.\n\nThis is an example of an **assumption**:\n\n```php\nif ($user !== null) {\n    $user->logout();\n}\n```\n\nRunning `bin/phpa` on this file would yield the following output:\n\n```\n----------------------------------------------\n| file        | line | message               |\n==============================================\n| example.php | 3    | if ($user !== null) { |\n----------------------------------------------\n\n1 out of 1 boolean expressions are assumptions (100%)\n```\n\nThis is an example of an **assertion**:\n\n```php\nif ($user instanceof User) {\n    $user->logout();\n}\n```\n\n## Tests\nThis project is built with [PHPUnit](https://github.com/sebastianbergmann/phpunit) and [Prophecy](https://github.com/phpspec/prophecy-phpunit).\nIn order to run these tests make sure you have dev dependencies installed with composer.\n\nRunning PHPUnit:\n```sh\n$ ./vendor/bin/phpunit\n```\n"
  },
  {
    "path": "bin/phpa",
    "content": "#!/usr/bin/env php\n<?php\n\n$autoloaders = [__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'];\n\nforeach ($autoloaders as $autoloader) {\n    if (is_file($autoloader)) {\n        require_once $autoloader;\n        break;\n    }\n}\n\nini_set('xdebug.max_nesting_level', 3000);\n\n$cli = new \\PhpAssumptions\\Cli(new \\League\\CLImate\\CLImate());\nexit($cli->handle($argv));\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"rskuipers/php-assumptions\",\n    \"description\": \"Static code analysis tool to detect weak assumptions\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Rick Kuipers\",\n            \"email\": \"io@rskuipers.com\"\n        }\n    ],\n    \"require\": {\n        \"nikic/php-parser\": \"^4.0|^5.0\",\n        \"league/climate\": \"^3.1\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^11.5\",\n        \"phpspec/prophecy-phpunit\": \"^2.3\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"PhpAssumptions\\\\\": \"src/PhpAssumptions\"\n        }\n    },\n    \"bin\": [\"bin/phpa\"]\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/11.5/phpunit.xsd\" colors=\"true\" bootstrap=\"tests/bootstrap.php\" cacheDirectory=\".phpunit.cache\">\n  <testsuites>\n    <testsuite name=\"default\">\n      <directory>tests/</directory>\n    </testsuite>\n  </testsuites>\n  <source>\n    <include>\n      <directory>src/</directory>\n    </include>\n  </source>\n</phpunit>\n"
  },
  {
    "path": "src/PhpAssumptions/Analyser.php",
    "content": "<?php\n\nnamespace PhpAssumptions;\n\nuse PhpAssumptions\\Output\\Result;\nuse PhpParser\\Node;\nuse PhpParser\\NodeTraverserInterface;\nuse PhpParser\\Parser;\n\nclass Analyser\n{\n    /**\n     * @var Parser\n     */\n    private $parser;\n\n    /**\n     * @var NodeTraverserInterface\n     */\n    private $traverser;\n\n    /**\n     * @var string\n     */\n    private $currentFilePath;\n\n    /**\n     * @var array\n     */\n    private $currentFile = [];\n\n    /**\n     * @var Result\n     */\n    private $result;\n\n    /**\n     * @var array|\\string[]\n     */\n    private $excludes = [];\n\n    /**\n     * @param Parser $parser\n     * @param NodeTraverserInterface  $nodeTraverser\n     * @param string[]                $excludes\n     */\n    public function __construct(\n        Parser $parser,\n        NodeTraverserInterface $nodeTraverser,\n        $excludes = []\n    ) {\n        $this->parser = $parser;\n        $this->traverser = $nodeTraverser;\n        $this->result = new Result();\n        $this->excludes = $excludes;\n    }\n\n    /**\n     * @param array $files\n     * @return Result\n     */\n    public function analyse(array $files)\n    {\n        foreach ($files as $file) {\n            if (in_array($file, $this->excludes, true)) {\n                continue;\n            }\n            $this->currentFilePath = $file;\n            $this->currentFile = [];\n            $statements = $this->parser->parse(file_get_contents($file));\n            if (is_array($statements) || $statements instanceof Node) {\n                $this->traverser->traverse($statements);\n            }\n        }\n\n        return $this->result;\n    }\n\n    /**\n     * @param int $line\n     */\n    public function foundAssumption($line)\n    {\n        $this->result->addAssumption($this->currentFilePath, $line, $this->readLine($line));\n    }\n\n    public function foundBoolExpression()\n    {\n        $this->result->increaseBoolExpressionsCount();\n    }\n\n    private function readLine($line)\n    {\n        if (count($this->currentFile) === 0) {\n            $this->currentFile = file($this->currentFilePath);\n        }\n\n        return trim($this->currentFile[$line - 1]);\n    }\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Cli.php",
    "content": "<?php\n\nnamespace PhpAssumptions;\n\nuse League\\CLImate\\CLImate;\nuse PhpAssumptions\\Output\\PrettyOutput;\nuse PhpAssumptions\\Output\\XmlOutput;\nuse PhpAssumptions\\Parser\\NodeVisitor;\nuse PhpParser\\NodeTraverser;\nuse PhpParser\\ParserFactory;\n\nclass Cli\n{\n    const VERSION = '0.9.1';\n\n    /**\n     * @var CLImate\n     */\n    private $cli;\n\n    /**\n     * @var \\PhpParser\\Parser\n     */\n    private $parser;\n\n    private function createParser()\n    {\n        $parser = (new ParserFactory)->createForNewestSupportedVersion();\n        return $parser;\n    }\n\n    public function __construct(CLImate $cli)\n    {\n        $this->cli = $cli;\n        $this->cli->arguments->add(\n            [\n                'path' => [\n                    'description' => 'The path to analyse',\n                    'required' => false,\n                ],\n                'format' => [\n                    'prefix' => 'f',\n                    'longPrefix' => 'format',\n                    'description' => 'Format (pretty, xml)',\n                    'defaultValue' => 'pretty',\n                ],\n                'exclude' => [\n                    'prefix' => 'e',\n                    'longPrefix' => 'exclude',\n                    'description' => 'List of files/directories (separate by \",\") to exclude from the analyse',\n                    'defaultValue' => ''\n                ],\n                'output' => [\n                    'prefix' => 'o',\n                    'longPrefix' => 'output',\n                    'description' => 'Output file',\n                    'defaultValue' => 'phpa.xml',\n                ],\n                'version' => [\n                    'longPrefix' => 'version',\n                    'description' => 'Show the version'\n                ],\n            ]\n        );\n        $this->parser = self::createParser();\n    }\n\n    /**\n     * @param array $args\n     * @return int\n     */\n    public function handle(array $args)\n    {\n        try {\n            $this->cli->arguments->parse($args);\n        } catch (\\Exception $e) {\n            $this->cli->usage($args);\n            return 100;\n        }\n\n        if ($this->cli->arguments->defined('version')) {\n            $this->cli->out(Cli::VERSION);\n            return 0;\n        }\n\n        $this->cli->out(sprintf('PHPAssumptions analyser v%s by @rskuipers', Cli::VERSION))->br();\n\n        $target = $this->cli->arguments->get('path');\n\n        if (!is_string($target)) {\n            $this->cli->error('Missing target path')->br();\n            $this->cli->usage($args);\n            return 100;\n        }\n\n        switch ($this->cli->arguments->get('format')) {\n            case 'xml':\n                $output = new XmlOutput($this->cli, $this->cli->arguments->get('output'));\n                break;\n            default:\n                $output = new PrettyOutput($this->cli);\n                break;\n        }\n\n        $excludes = $this->getPathsFromList((string) $this->cli->arguments->get('exclude'));\n\n        $nodeTraverser = new NodeTraverser();\n\n        $analyser = new Analyser(\n            $this->parser,\n            $nodeTraverser,\n            $excludes\n        );\n\n        $nodeTraverser->addVisitor(new NodeVisitor($analyser, new Detector()));\n\n        $target = $this->cli->arguments->get('path');\n        $targets = $this->getPaths($target);\n\n        $result = $analyser->analyse($targets);\n\n        $output->output($result);\n\n        if ($result->getAssumptionsCount() > 0) {\n            return 110;\n        }\n\n        return 0;\n    }\n\n    /**\n     * @param string $list\n     * @return array\n     */\n    private function getPathsFromList($list)\n    {\n        $paths = [];\n        if ($list !== '') {\n            $items = explode(',', $list);\n            foreach ($items as $item) {\n                $paths = array_merge($paths, $this->getPaths($item));\n            }\n        }\n\n        return $paths;\n    }\n\n    /**\n     * @param string $fromPath\n     * @return array\n     */\n    private function getPaths($fromPath)\n    {\n        $paths = [];\n        if (is_file($fromPath)) {\n            $paths[] = $fromPath;\n        } else {\n            $directory = new \\RecursiveDirectoryIterator($fromPath);\n            $iterator = new \\RecursiveIteratorIterator($directory);\n            $regex = new \\RegexIterator($iterator, '/^.+\\.php$/i', \\RecursiveRegexIterator::GET_MATCH);\n\n            foreach ($regex as $file) {\n                $paths[] = $file[0];\n            }\n        }\n\n        return $paths;\n    }\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Detector.php",
    "content": "<?php\n\nnamespace PhpAssumptions;\n\nuse PhpParser\\Node;\nuse PhpParser\\Node\\Expr;\nuse PhpParser\\Node\\Stmt;\n\nclass Detector\n{\n    /**\n     * @param Node $node\n     * @return bool\n     */\n    public function scan(Node $node)\n    {\n        if (($node instanceof Stmt\\Expression)) {\n            $node = $node->expr;\n        }\n\n        if (($node instanceof Expr\\BinaryOp\\BooleanOr || $node instanceof Expr\\BinaryOp\\BooleanAnd)\n            && $this->bidirectionalCheck($node, Expr\\Variable::class, Expr\\BinaryOp::class)\n        ) {\n            return true;\n        }\n\n        if ($node instanceof Expr\\BinaryOp\\Equal || $node instanceof Expr\\BinaryOp\\NotEqual\n            || $node instanceof Expr\\BinaryOp\\NotIdentical\n        ) {\n            return true;\n        }\n\n        if ($this->isVariableExpression($node)) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * @param Node $node\n     * @return bool\n     */\n    public function isBoolExpression(Node $node)\n    {\n        if ($node instanceof Expr\\Ternary || $node instanceof Stmt\\If_\n            || $node instanceof Stmt\\ElseIf_ || $node instanceof Stmt\\While_\n            || $node instanceof Expr\\BinaryOp\\BooleanAnd || $node instanceof Expr\\BinaryOp\\BooleanOr\n            || $node instanceof Stmt\\For_\n        ) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * @param Node $node\n     * @return bool\n     */\n    private function isVariableExpression(Node $node)\n    {\n        if ($node instanceof Expr\\BooleanNot && $node->expr instanceof Expr\\Variable) {\n            return true;\n        }\n\n        if ($node instanceof Expr\\Ternary || $node instanceof Stmt\\If_\n            || $node instanceof Stmt\\ElseIf_ || $node instanceof Stmt\\While_\n            || $node instanceof Stmt\\For_\n        ) {\n            if ($node->cond instanceof Expr\\Variable) {\n                return true;\n            }\n\n            if (is_array($node->cond)) {\n                foreach ($node->cond as $condition) {\n                    if ($condition instanceof Expr\\Variable) {\n                        return true;\n                    }\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @param Expr   $condition\n     * @param string $left\n     * @param string $right\n     * @return bool\n     */\n    private function bidirectionalCheck(Expr $condition, $left, $right)\n    {\n        return ($this->isInstanceOf($condition->left, $left) && $this->isInstanceOf($condition->right, $right))\n            || ($this->isInstanceOf($condition->right, $left) && $this->isInstanceOf($condition->left, $right));\n    }\n\n    /**\n     * @param object $object\n     * @param string $class\n     * @return bool\n     */\n    private function isInstanceOf($object, $class)\n    {\n        return get_class($object) === $class || is_subclass_of($object, $class);\n    }\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Output/OutputInterface.php",
    "content": "<?php\n\nnamespace PhpAssumptions\\Output;\n\ninterface OutputInterface\n{\n    /**\n     * @param Result $result\n     */\n    public function output(Result $result);\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Output/PrettyOutput.php",
    "content": "<?php\n\nnamespace PhpAssumptions\\Output;\n\nuse League\\CLImate\\CLImate;\n\nclass PrettyOutput implements OutputInterface\n{\n    /**\n     * @var CLImate\n     */\n    private $cli;\n\n    /**\n     * @param CLImate $cli\n     */\n    public function __construct(CLImate $cli)\n    {\n        $this->cli = $cli;\n    }\n\n    /**\n     * @param Result $result\n     */\n    public function output(Result $result)\n    {\n        if ($result->getAssumptionsCount() > 0) {\n            $this->cli->table($result->getAssumptions())->br();\n        }\n\n        $this->cli->out(\n            sprintf(\n                '%d out of %d boolean expressions are assumptions (%d%%)',\n                $result->getAssumptionsCount(),\n                $result->getBoolExpressionsCount(),\n                $result->getPercentage()\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Output/Result.php",
    "content": "<?php\n\nnamespace PhpAssumptions\\Output;\n\nclass Result\n{\n    /**\n     * @var array\n     */\n    private $assumptions = [];\n\n    /**\n     * @var int\n     */\n    private $boolExpressionsCount = 0;\n\n    /**\n     * @param string $file\n     * @param int    $line\n     * @param string $message\n     */\n    public function addAssumption($file, $line, $message)\n    {\n        $this->assumptions[] = [\n            'file' => $file,\n            'line' => $line,\n            'message' => $message,\n        ];\n    }\n\n    public function increaseBoolExpressionsCount()\n    {\n        $this->boolExpressionsCount++;\n    }\n\n    /**\n     * @return array\n     */\n    public function getAssumptions()\n    {\n        return $this->assumptions;\n    }\n\n    /**\n     * @return int\n     */\n    public function getAssumptionsCount()\n    {\n        return count($this->assumptions);\n    }\n\n    /**\n     * @return float\n     */\n    public function getPercentage()\n    {\n        if ($this->getBoolExpressionsCount() === 0) {\n            return 0;\n        }\n\n        return round($this->getAssumptionsCount() / $this->getBoolExpressionsCount() * 100);\n    }\n\n    /**\n     * @return int\n     */\n    public function getBoolExpressionsCount()\n    {\n        return $this->boolExpressionsCount;\n    }\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Output/XmlOutput.php",
    "content": "<?php\n\nnamespace PhpAssumptions\\Output;\n\nuse League\\CLImate\\CLImate;\nuse PhpAssumptions\\Cli;\n\nclass XmlOutput implements OutputInterface\n{\n    /**\n     * @var \\DOMDocument\n     */\n    private $document;\n\n    /**\n     * @var string\n     */\n    private $file;\n\n    /**\n     * @var \\DOMXPath\n     */\n    private $xpath;\n\n    /**\n     * @var CLImate\n     */\n    private $cli;\n\n    /**\n     * @param CLImate $cli\n     * @param string  $file\n     */\n    public function __construct(CLImate $cli, $file)\n    {\n        $this->cli = $cli;\n        $this->file = $file;\n        $this->document = new \\DOMDocument();\n\n        $phpaNode = $this->document->createElement('phpa');\n        $phpaNode->setAttribute('version', Cli::VERSION);\n        $this->document->appendChild($phpaNode);\n\n        $filesNode = $this->document->createElement('files');\n        $phpaNode->appendChild($filesNode);\n\n        $this->xpath = new \\DOMXPath($this->document);\n    }\n\n    /**\n     * @param Result $result\n     */\n    public function output(Result $result)\n    {\n        $assumptions = $result->getAssumptions();\n\n        foreach ($assumptions as $assumption) {\n            $fileElements = $this->xpath->query('/phpa/files/file[@name=\"' . $assumption['file'] . '\"]');\n\n            if ($fileElements->length === 0) {\n                $files = $this->xpath->query('/phpa/files')->item(0);\n                $fileElement = $this->document->createElement('file');\n                $fileElement->setAttribute('name', $assumption['file']);\n                $files->appendChild($fileElement);\n            } else {\n                $fileElement = $fileElements->item(0);\n            }\n\n            $lineElement = $this->document->createElement('line');\n            $lineElement->setAttribute('number', $assumption['line']);\n            $lineElement->setAttribute('message', $assumption['message']);\n            $fileElement->appendChild($lineElement);\n        }\n\n        $this->document->documentElement->setAttribute('assumptions', $result->getAssumptionsCount());\n        $this->document->documentElement->setAttribute('bool-expressions', $result->getBoolExpressionsCount());\n        $this->document->documentElement->setAttribute('percentage', $result->getPercentage());\n\n        $this->document->preserveWhiteSpace = false;\n        $this->document->formatOutput = true;\n        $this->document->save($this->file);\n\n        $this->cli->out(sprintf('Written %d assumption(s) to file %s', $result->getAssumptionsCount(), $this->file));\n    }\n}\n"
  },
  {
    "path": "src/PhpAssumptions/Parser/NodeVisitor.php",
    "content": "<?php\n\nnamespace PhpAssumptions\\Parser;\n\nuse PhpAssumptions\\Analyser;\nuse PhpAssumptions\\Detector;\nuse PhpParser\\Node;\nuse PhpParser\\NodeVisitorAbstract;\n\nclass NodeVisitor extends NodeVisitorAbstract\n{\n    /**\n     * @var Analyser\n     */\n    private $analyser;\n\n    /**\n     * @var Detector\n     */\n    private $detector;\n\n    /**\n     * @param Analyser $analyser\n     * @param Detector $detector\n     */\n    public function __construct(Analyser $analyser, Detector $detector)\n    {\n        $this->analyser = $analyser;\n        $this->detector = $detector;\n    }\n\n    /**\n     * @param Node $node\n     * @return false|null|Node|\\PhpParser\\Node[]|void\n     */\n    public function enterNode(Node $node)\n    {\n        if ($this->detector->isBoolExpression($node)) {\n            $this->analyser->foundBoolExpression();\n        }\n\n        if ($this->detector->scan($node)) {\n            $this->analyser->foundAssumption($node->getLine());\n        }\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/AnalyserTest.php",
    "content": "<?php\n\nnamespace tests\\PhpAssumptions;\n\nuse PhpAssumptions\\Analyser;\nuse PhpAssumptions\\Output\\OutputInterface;\nuse PhpParser\\Parser;\nuse PhpParser\\ParserAbstract;\nuse PhpParser\\Node;\nuse PhpParser\\NodeTraverser;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Argument;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass AnalyserTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var Parser\n     */\n    private $parser;\n\n    /**\n     * @var OutputInterface\n     */\n    private $output;\n\n    /**\n     * @var NodeTraverser\n     */\n    private $nodeTraverser;\n\n    /**\n     * @var Analyser\n     */\n    private $analyser;\n\n    /**\n     * @var Node\n     */\n    private $node;\n\n    public function setUp(): void\n    {\n        $this->node = $this->prophesize(Node::class);\n        $this->parser = $this->prophesize(ParserAbstract::class);\n        $this->output = $this->prophesize(OutputInterface::class);\n        $this->nodeTraverser = $this->prophesize(NodeTraverser::class);\n        $this->analyser = new Analyser(\n            $this->parser->reveal(),\n            $this->nodeTraverser->reveal(),\n            [fixture('MyOtherClass.php')]\n        );\n    }\n\n    #[Test]\n    public function itShouldAnalyseAllFiles()\n    {\n        $files = [fixture('MyClass.php')];\n        $nodes = [$this->node];\n\n        $this->parser->parse(Argument::type('string'))->shouldBeCalled()->willReturn($nodes);\n\n        $this->nodeTraverser->traverse($nodes)->shouldBeCalled();\n\n        $this->analyser->analyse($files);\n    }\n\n    #[Test]\n    public function itShouldIgnoreExcludeFiles()\n    {\n        $files = [fixture('MyClass.php'), fixture('MyOtherClass.php')];\n        $nodes = [$this->node];\n\n        $this->parser->parse(Argument::type('string'))->shouldBeCalled()->willReturn($nodes);\n\n        $this->nodeTraverser->traverse($nodes)->shouldBeCalled();\n\n        $this->analyser->analyse($files);\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/CliTest.php",
    "content": "<?php\n\nnamespace tests\\PhpAssumptions;\n\nuse League\\CLImate\\Argument\\Manager;\nuse League\\CLImate\\CLImate;\nuse PhpAssumptions\\Cli;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Argument;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass CliTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var Cli\n     */\n    private $cli;\n\n    /**\n     * @var CLImate\n     */\n    private $climate;\n\n    /**\n     * @var Manager\n     */\n    private $argumentManager;\n\n    public function setUp(): void\n    {\n        $this->argumentManager = $this->prophesize(Manager::class);\n        $this->argumentManager->add(Argument::type('array'))->shouldBeCalled();\n        $this->argumentManager->parse(Argument::type('array'))->shouldBeCalled();\n\n        $this->climate = $this->prophesize(CLImate::class);\n\n        $this->climate->arguments = $this->argumentManager->reveal();\n\n        $this->cli = new Cli($this->climate->reveal());\n    }\n\n    #[Test]\n    public function itShouldAnalyseTargetFile()\n    {\n        $this->itShouldShowAuthor();\n\n        $path = fixture('MyClass.php');\n\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(false);\n        $this->argumentManager->get('format')->shouldBeCalled()->willReturn('pretty');\n        $this->argumentManager->get('path')->shouldBeCalled()->willReturn($path);\n        $this->argumentManager->get('exclude')->shouldBeCalled()->willReturn('');\n\n        $this->climate->table([\n            [\n                'file' => $path,\n                'line' => 9,\n                'message' => 'if ($dog !== null) {',\n            ]\n        ])->shouldBeCalled()->willReturn($this->climate);\n        $this->climate->out('1 out of 2 boolean expressions are assumptions (50%)')->shouldBeCalled();\n\n        $this->cli->handle(['phpa', $path]);\n    }\n\n    #[Test]\n    public function itShouldAnalyseTargetDirectory()\n    {\n        $this->itShouldShowAuthor();\n\n        $files = [fixture('MyClass.php'), fixture('MyOtherClass.php'), fixture('Example.php')];\n\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(false);\n        $this->argumentManager->get('format')->shouldBeCalled()->willReturn('pretty');\n        $this->argumentManager->get('path')->shouldBeCalled()->willReturn(FIXTURES_DIR);\n        $this->argumentManager->get('exclude')->shouldBeCalled()->willReturn('');\n\n        // Assert that all files show up in the table\n        $this->climate->table(Argument::that(function ($table) use ($files) {\n\n            foreach ($table as $row) {\n                unset($files[array_search($row['file'], $files)]);\n            }\n\n            return count($files) === 0;\n        }))->shouldBeCalled()->willReturn($this->climate);\n\n        $this->climate->out(Argument::containingString('boolean expressions are assumptions'))->shouldBeCalled();\n\n        $this->cli->handle(['phpa', FIXTURES_DIR]);\n    }\n\n    #[Test]\n    public function itShouldIgnoreExcludeFile()\n    {\n        $this->itShouldShowAuthor();\n\n        $path = fixture('MyClass.php');\n\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(false);\n        $this->argumentManager->get('format')->shouldBeCalled()->willReturn('pretty');\n        $this->argumentManager->get('path')->shouldBeCalled()->willReturn($path);\n        $this->argumentManager->get('exclude')->shouldBeCalled()->willReturn(fixture('MyClass.php'));\n\n        $this->climate->table()->shouldNotBeCalled();\n        $this->climate->out('0 out of 0 boolean expressions are assumptions (0%)')->shouldBeCalled();\n\n        $this->cli->handle(['phpa', $path]);\n    }\n\n    #[Test]\n    public function itShouldIgnoreExcludeFileFromDirectory()\n    {\n        $this->itShouldShowAuthor();\n\n        $path = fixture('MyClass.php');\n\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(false);\n        $this->argumentManager->get('format')->shouldBeCalled()->willReturn('pretty');\n        $this->argumentManager->get('path')->shouldBeCalled()->willReturn(FIXTURES_DIR);\n        $this->argumentManager->get('exclude')->shouldBeCalled()->willReturn(\n            fixture('MyOtherClass.php') . ',' . fixture('Example.php')\n        );\n\n        $this->climate->table([\n            [\n                'file' => $path,\n                'line' => 9,\n                'message' => 'if ($dog !== null) {',\n            ]\n        ])->shouldBeCalled()->willReturn($this->climate);\n        $this->climate->out('1 out of 2 boolean expressions are assumptions (50%)')->shouldBeCalled();\n\n        $this->cli->handle(['phpa', FIXTURES_DIR]);\n    }\n\n    #[Test]\n    public function itShouldIgnoreExcludeDirectory()\n    {\n        $this->itShouldShowAuthor();\n\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(false);\n        $this->argumentManager->get('format')->shouldBeCalled()->willReturn('pretty');\n        $this->argumentManager->get('path')->shouldBeCalled()->willReturn(FIXTURES_DIR);\n        $this->argumentManager->get('exclude')->shouldBeCalled()->willReturn(fixture(''));\n\n        // Assert that all files show up in the table\n        $this->climate->table()->shouldNotBeCalled();\n\n        $this->climate->out(Argument::containingString('boolean expressions are assumptions'))->shouldBeCalled();\n\n        $this->cli->handle(['phpa', FIXTURES_DIR]);\n    }\n\n    #[Test]\n    public function itShouldAnalyseTargetFileAndOutputXml()\n    {\n        $this->itShouldShowAuthor();\n\n        $path = fixture('MyClass.php');\n        $output = tempnam(sys_get_temp_dir(), 'xml');\n\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(false);\n        $this->argumentManager->get('format')->shouldBeCalled()->willReturn('xml');\n        $this->argumentManager->get('path')->shouldBeCalled()->willReturn($path);\n        $this->argumentManager->get('output')->shouldBeCalled()->willReturn($output);\n        $this->argumentManager->get('exclude')->shouldBeCalled()->willReturn('');\n\n        $this->climate->out('Written 1 assumption(s) to file ' . $output)->shouldBeCalled();\n\n        $this->cli->handle(['phpa', $path]);\n        $this->assertTrue(is_file($output));\n    }\n\n    #[Test]\n    public function itShouldShowUsageWithNoArgs()\n    {\n        $this->argumentManager->parse(Argument::type('array'))->willThrow(\\Exception::class);\n\n        $args = ['phpa'];\n        $this->climate->usage($args)->shouldBeCalled();\n        $this->cli->handle($args);\n    }\n\n    #[Test]\n    public function itShouldShowVersion()\n    {\n        $this->argumentManager->defined('version')->shouldBeCalled()->willReturn(true);\n\n        $args = ['phpa', '--version'];\n        $this->climate->out(Cli::VERSION)->shouldBeCalled();\n        $this->cli->handle($args);\n    }\n\n    private function itShouldShowAuthor()\n    {\n        $this->climate->out(Argument::containingString('PHPAssumptions analyser'))\n            ->shouldBeCalled()\n            ->willReturn($this->climate);\n\n        $this->climate->br()->shouldBeCalled();\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/DetectorTest.php",
    "content": "<?php\n\nnamespace tests\\PhpAssumptions;\n\nuse PhpAssumptions\\Detector;\nuse PhpParser\\Parser;\nuse PhpParser\\ParserFactory;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass DetectorTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var Parser\n     */\n    private $parser;\n\n    /**\n     * @var Detector\n     */\n    private $detector;\n\n    public function setUp(): void\n    {\n        $this->parser = (new ParserFactory)->createForNewestSupportedVersion();\n        $this->detector = new Detector();\n    }\n\n    #[Test]\n    public function itShouldDetectNotNull()\n    {\n        $node = $this->parser->parse('<?php $var !== null;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php null !== $var;')[0];\n        $this->assertTrue($this->detector->scan($node));\n    }\n\n    #[Test]\n    public function itShouldDetectEqualsNotFalse()\n    {\n        $node = $this->parser->parse('<?php $test !== false;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php false !== $test;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php false != $test;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php $test != false;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php !$test;')[0];\n        $this->assertTrue($this->detector->scan($node));\n    }\n\n    #[Test]\n    public function itShouldDetectEqualsTrue()\n    {\n        $node = $this->parser->parse('<?php $test !== true;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php true !== $test;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php true != $test;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php $test != true;')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php $test ? \"\" : \"\";')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php if ($test instanceof Test) { } elseif ($test) { }')[0]->elseifs[0];\n        $this->assertTrue($this->detector->scan($node));\n    }\n\n    #[Test]\n    public function itShouldDetectEqualsScalar()\n    {\n        $node = $this->parser->parse('<?php $test == \"test\";')[0];\n        $this->assertTrue($this->detector->scan($node));\n\n        $node = $this->parser->parse('<?php \"test\" == $test;')[0];\n        $this->assertTrue($this->detector->scan($node));\n    }\n\n    #[Test]\n    public function itShouldDetectWhileAssumptions()\n    {\n        $node = $this->parser->parse('<?php while ($test);')[0];\n        $this->assertTrue($this->detector->scan($node));\n    }\n\n    #[Test]\n    public function itShouldDetectForAssumptions()\n    {\n        $node = $this->parser->parse('<?php for ($i = 0; $i; $i++);')[0];\n        $this->assertTrue($this->detector->scan($node));\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/ExampleTest.php",
    "content": "<?php\n\nnamespace tests;\n\nuse PhpAssumptions\\Analyser;\nuse PhpAssumptions\\Detector;\nuse PhpAssumptions\\Parser\\NodeVisitor;\nuse PhpParser\\NodeTraverser;\nuse PhpParser\\ParserFactory;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass ExampleTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var Analyser\n     */\n    private $analyser;\n\n    public function setUp(): void\n    {\n        $nodeTraverser = new NodeTraverser();\n        $this->analyser = new Analyser((new ParserFactory)->createForNewestSupportedVersion(), $nodeTraverser);\n        $nodeTraverser->addVisitor(new NodeVisitor($this->analyser, new Detector()));\n    }\n\n    #[Test]\n    public function itShouldProperlyDetectAssumptions()\n    {\n        $file = fixture('Example.php');\n\n        $result = $this->analyser->analyse([$file]);\n\n        $this->assertSame([\n            [\n                'file' => $file,\n                'line' => 9,\n                'message' => '$test = $bla && $bla === \\'test\\' || $bla === \\'ha\\' ? \\'haha\\' : \\'test\\';',\n            ],\n            [\n                'file' => $file,\n                'line' => 10,\n                'message' => 'if ($test && $test > 0) {',\n            ],\n            [\n                'file' => $file,\n                'line' => 12,\n                'message' => '} elseif (!$test) {',\n            ],\n            [\n                'file' => $file,\n                'line' => 19,\n                'message' => 'while ($test) {',\n            ],\n            [\n                'file' => $file,\n                'line' => 23,\n                'message' => 'for ($i = 0; $i; $i++) {',\n            ],\n        ], $result->getAssumptions());\n    }\n\n    #[Test]\n    public function itShouldProperlyDetectBoolExpressions()\n    {\n        $file = fixture('Example.php');\n\n        $result = $this->analyser->analyse([$file]);\n        $this->assertEquals(12, $result->getBoolExpressionsCount());\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/Output/PrettyOutputTest.php",
    "content": "<?php\n\nnamespace tests\\PhpAssumptions\\Output;\n\nuse League\\CLImate\\CLImate;\nuse PhpAssumptions\\Output\\PrettyOutput;\nuse PhpAssumptions\\Output\\Result;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass PrettyOutputTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var PrettyOutput\n     */\n    private $output;\n\n    /**\n     * @var CLImate\n     */\n    private $climate;\n\n    /**\n     * @var Result\n     */\n    private $result;\n\n    public function setUp(): void\n    {\n        $this->climate = $this->prophesize(CLImate::class);\n        $this->result = $this->prophesize(Result::class);\n        $this->output = new PrettyOutput($this->climate->reveal());\n    }\n\n    #[Test]\n    public function itShouldOutputWhatIsWritten()\n    {\n        $assumptions = [\n            [\n                'file' => 'MyClass.php',\n                'line' => 120,\n                'message' => '$test',\n            ]\n        ];\n\n        $this->result->getAssumptions()->shouldBeCalled()->willReturn($assumptions);\n        $this->result->getAssumptionsCount()->shouldBeCalled()->willReturn(1);\n        $this->result->getBoolExpressionsCount()->shouldBeCalled()->willReturn(3);\n        $this->result->getPercentage()->shouldBeCalled()->willReturn(33.3333333);\n\n        $this->climate->table($assumptions)->shouldBeCalled()->willReturn($this->climate);\n        $this->climate->br()->shouldBeCalled();\n        $this->climate->out('1 out of 3 boolean expressions are assumptions (33%)')->shouldBeCalled();\n\n        $this->output->output($this->result->reveal());\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/Output/XmlOutputTest.php",
    "content": "<?php\n\nnamespace tests\\PhpAssumptions\\Output;\n\nuse League\\CLImate\\CLImate;\nuse PhpAssumptions\\Cli;\nuse PhpAssumptions\\Output\\Result;\nuse PhpAssumptions\\Output\\XmlOutput;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass XmlOutputTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var XmlOutput\n     */\n    private $xmlOutput;\n\n    /**\n     * @var CLImate\n     */\n    private $cli;\n\n    /**\n     * @var Result\n     */\n    private $result;\n\n    /**\n     * @var string\n     */\n    private $file;\n\n    public function setUp(): void\n    {\n        $this->file = tempnam(sys_get_temp_dir(), 'xml');\n        $this->cli = $this->prophesize(CLImate::class);\n        $this->result = $this->prophesize(Result::class);\n        $this->xmlOutput = new XmlOutput($this->cli->reveal(), $this->file);\n    }\n\n    #[Test]\n    public function itShouldGenerateValidXml()\n    {\n        $this->result->getAssumptions()->shouldBeCalled()->willReturn([\n            [\n                'file' => 'MyClass.php',\n                'line' => 122,\n                'message' => 'if ($test) {'\n            ],\n            [\n                'file' => 'MyClass.php',\n                'line' => 132,\n                'message' => '$test ? \"Yes\" : \"No\"'\n            ],\n            [\n                'file' => 'MyOtherClass.php',\n                'line' => 12,\n                'message' => 'if ($test !== false) {'\n            ]\n        ]);\n\n        $this->result->getAssumptionsCount()->shouldBeCalled()->willReturn(3);\n        $this->result->getPercentage()->shouldBeCalled()->willReturn(60);\n        $this->result->getBoolExpressionsCount()->shouldBeCalled()->willReturn(5);\n\n        $this->xmlOutput->output($this->result->reveal());\n\n        $version = Cli::VERSION;\n\n        $expected = <<<XML\n<?xml version=\"1.0\"?>\n<phpa version=\"{$version}\" assumptions=\"3\" bool-expressions=\"5\" percentage=\"60\">\n    <files>\n        <file name=\"MyClass.php\">\n            <line number=\"122\" message=\"if (\\$test) {\" />\n            <line number=\"132\" message=\"\\$test ? &quot;Yes&quot; : &quot;No&quot;\" />\n        </file>\n        <file name=\"MyOtherClass.php\">\n            <line number=\"12\" message=\"if (\\$test !== false) {\" />\n        </file>\n    </files>\n</phpa>\nXML;\n\n        $this->assertXmlStringEqualsXmlString($expected, file_get_contents($this->file));\n    }\n}\n"
  },
  {
    "path": "tests/PhpAssumptions/Parser/NodeVisitorTest.php",
    "content": "<?php\n\nnamespace tests\\PhpAssumptions\\Parser;\n\nuse PhpAssumptions\\Analyser;\nuse PhpAssumptions\\Detector;\nuse PhpAssumptions\\Parser\\NodeVisitor;\nuse PhpParser\\Node;\nuse PHPUnit\\Framework\\Attributes\\Test;\nuse PHPUnit\\Framework\\TestCase;\nuse Prophecy\\Argument;\nuse Prophecy\\PhpUnit\\ProphecyTrait;\n\nclass NodeVisitorTest extends TestCase\n{\n    use ProphecyTrait;\n\n    /**\n     * @var NodeVisitor\n     */\n    private $nodeVisitor;\n\n    /**\n     * @var Analyser\n     */\n    private $analyser;\n\n    /**\n     * @var Detector\n     */\n    private $detector;\n\n    /**\n     * @var Node\n     */\n    private $node;\n\n    public function setUp(): void\n    {\n        $this->analyser = $this->prophesize(Analyser::class);\n        $this->detector = $this->prophesize(Detector::class);\n        $this->node = $this->prophesize(Node::class);\n        $this->nodeVisitor = new NodeVisitor(\n            $this->analyser->reveal(),\n            $this->detector->reveal()\n        );\n    }\n\n    #[Test]\n    public function itShouldCallScanAndWriteOnSuccess()\n    {\n        $this->node->getLine()->shouldBeCalled()->willReturn(120);\n\n        $this->detector->scan($this->node)->shouldBeCalled()->willReturn(true);\n        $this->detector->isBoolExpression($this->node)->shouldBeCalled()->willReturn(true);\n\n        $this->analyser->foundAssumption(120)->shouldBeCalled();\n        $this->analyser->foundBoolExpression()->shouldBeCalled();\n\n        $this->nodeVisitor->enterNode($this->node->reveal());\n    }\n\n    #[Test]\n    public function itShouldCallScanAndNotWriteOnFailure()\n    {\n        $this->detector->scan($this->node)->shouldBeCalled()->willReturn(false);\n        $this->detector->isBoolExpression($this->node)->shouldBeCalled()->willReturn(true);\n        $this->analyser->foundAssumption(Argument::any(), Argument::any())->shouldNotBeCalled();\n        $this->analyser->foundBoolExpression()->shouldBeCalled();\n        $this->nodeVisitor->enterNode($this->node->reveal());\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\ndefine('FIXTURES_DIR', dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fixtures');\n\nfunction fixture($filename)\n{\n    return FIXTURES_DIR . DIRECTORY_SEPARATOR . $filename;\n}\n"
  },
  {
    "path": "tests/fixtures/Example.php",
    "content": "<?php\n\nnamespace Test;\n\nclass Example\n{\n    public function run($bla)\n    {\n        $test = $bla && $bla === 'test' || $bla === 'ha' ? 'haha' : 'test';\n        if ($test && $test > 0) {\n            echo '';\n        } elseif (!$test) {\n            echo '';\n        } else {\n            echo '';\n        }\n\n        $test = true;\n        while ($test) {\n            $test = false;\n        }\n\n        for ($i = 0; $i; $i++) {\n            echo '';\n        }\n\n        switch ($test) {\n            default:\n                echo '';\n                break;\n        }\n\n        if (empty($test)) {\n            echo '';\n        }\n\n        if (!is_null($test)) {\n            echo '';\n        }\n\n        if (fixture($test)) {\n            echo '';\n        }\n\n        if ($test === 'hi') {\n            echo '';\n        }\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/MyClass.php",
    "content": "<?php\n\nnamespace tests\\fixtures;\n\nclass MyClass\n{\n    public function run($dog, $cat)\n    {\n        if ($dog !== null) {\n            $dog->woof();\n        }\n\n        if ($cat instanceof MyOtherClass) {\n            $cat->run($dog);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/MyOtherClass.php",
    "content": "<?php\n\nnamespace tests\\fixtures;\n\nclass MyOtherClass\n{\n    public function run($cat)\n    {\n        if ($cat !== null) {\n            $cat->meow();\n        }\n    }\n}\n"
  }
]