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