dependencies-upgrade

This commit is contained in:
RafficMohammed
2023-01-08 02:20:59 +05:30
parent 7870479b18
commit 49021a4497
1711 changed files with 74994 additions and 70803 deletions

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Laravel;
@@ -21,11 +14,9 @@ use NunoMaduro\Collision\SolutionsRepositories\NullSolutionsRepository;
use NunoMaduro\Collision\Writer;
/**
* This is an Collision Laravel Adapter Service Provider implementation.
* @internal
*
* Registers the Error Handler on Laravel.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @final
*/
class CollisionServiceProvider extends ServiceProvider
{

View File

@@ -1,15 +1,25 @@
<?php
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Laravel\Commands;
use Dotenv\Dotenv;
use Dotenv\Repository\RepositoryBuilder;
use Dotenv\Exception\InvalidPathException;
use Dotenv\Parser\Parser;
use Dotenv\Store\StoreBuilder;
use Illuminate\Console\Command;
use Illuminate\Support\Env;
use Illuminate\Support\Str;
use NunoMaduro\Collision\Adapters\Laravel\Exceptions\RequirementsException;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
/**
* @internal
*
* @final
*/
class TestCommand extends Command
{
/**
@@ -17,7 +27,11 @@ class TestCommand extends Command
*
* @var string
*/
protected $signature = 'test {--without-tty : Disable output to TTY}';
protected $signature = 'test
{--without-tty : Disable output to TTY}
{--p|parallel : Indicates if the tests should run in parallel}
{--recreate-databases : Indicates if the test databases should be re-created}
';
/**
* The console command description.
@@ -26,16 +40,6 @@ class TestCommand extends Command
*/
protected $description = 'Run the application tests';
/**
* The arguments to be used while calling phpunit.
*
* @var array
*/
protected $arguments = [
'--printer',
'NunoMaduro\Collision\Adapters\Phpunit\Printer',
];
/**
* Create a new command instance.
*
@@ -55,17 +59,39 @@ class TestCommand extends Command
*/
public function handle()
{
if ((int) \PHPUnit\Runner\Version::id()[0] < 9) {
throw new RequirementsException('Running Collision ^5.0 artisan test command requires at least PHPUnit ^9.0.');
}
// @phpstan-ignore-next-line
if ((int) \Illuminate\Foundation\Application::VERSION[0] < 8) {
throw new RequirementsException('Running Collision ^5.0 artisan test command requires at least Laravel ^8.0.');
}
if ($this->option('parallel') && !$this->isParallelDependenciesInstalled()) {
if (!$this->confirm('Running tests in parallel requires "brianium/paratest". Do you wish to install it as a dev dependency?')) {
return 1;
}
$this->installParallelDependencies();
}
$options = array_slice($_SERVER['argv'], $this->option('without-tty') ? 3 : 2);
$this->clearEnv();
$parallel = $this->option('parallel');
$process = (new Process(array_merge(
$this->binary(),
array_merge(
$this->arguments,
$this->phpunitArguments($options)
)
)))->setTimeout(null);
// Binary ...
$this->binary(),
// Arguments ...
$parallel ? $this->paratestArguments($options) : $this->phpunitArguments($options)
),
null,
// Envs ...
$parallel ? $this->paratestEnvironmentVariables() : $this->phpunitEnvironmentVariables(),
))->setTimeout(null);
try {
$process->setTty(!$this->option('without-tty'));
@@ -91,11 +117,17 @@ class TestCommand extends Command
*/
protected function binary()
{
if ('phpdbg' === PHP_SAPI) {
return [PHP_BINARY, '-qrr', 'vendor/phpunit/phpunit/phpunit'];
if (class_exists(\Pest\Laravel\PestServiceProvider::class)) {
$command = $this->option('parallel') ? ['vendor/pestphp/pest/bin/pest', '--parallel'] : ['vendor/pestphp/pest/bin/pest'];
} else {
$command = $this->option('parallel') ? ['vendor/brianium/paratest/bin/paratest'] : ['vendor/phpunit/phpunit/phpunit'];
}
return [PHP_BINARY, 'vendor/phpunit/phpunit/phpunit'];
if ('phpdbg' === PHP_SAPI) {
return array_merge([PHP_BINARY, '-qrr'], $command);
}
return array_merge([PHP_BINARY], $command);
}
/**
@@ -107,6 +139,8 @@ class TestCommand extends Command
*/
protected function phpunitArguments($options)
{
$options = array_merge(['--printer=NunoMaduro\\Collision\\Adapters\\Phpunit\\Printer'], $options);
$options = array_values(array_filter($options, function ($option) {
return !Str::startsWith($option, '--env=');
}));
@@ -115,7 +149,56 @@ class TestCommand extends Command
$file = base_path('phpunit.xml.dist');
}
return array_merge(['-c', $file], $options);
return array_merge(["--configuration=$file"], $options);
}
/**
* Get the array of arguments for running Paratest.
*
* @param array $options
*
* @return array
*/
protected function paratestArguments($options)
{
$options = array_values(array_filter($options, function ($option) {
return !Str::startsWith($option, '--env=')
&& !Str::startsWith($option, '-p')
&& !Str::startsWith($option, '--parallel')
&& !Str::startsWith($option, '--recreate-databases');
}));
if (!file_exists($file = base_path('phpunit.xml'))) {
$file = base_path('phpunit.xml.dist');
}
return array_merge([
"--configuration=$file",
"--runner=\Illuminate\Testing\ParallelRunner",
], $options);
}
/**
* Get the array of environment variables for running PHPUnit.
*
* @return array
*/
protected function phpunitEnvironmentVariables()
{
return [];
}
/**
* Get the array of environment variables for running Paratest.
*
* @return array
*/
protected function paratestEnvironmentVariables()
{
return [
'LARAVEL_PARALLEL_TESTING' => 1,
'LARAVEL_PARALLEL_TESTING_RECREATE_DATABASES' => $this->option('recreate-databases'),
];
}
/**
@@ -126,18 +209,101 @@ class TestCommand extends Command
protected function clearEnv()
{
if (!$this->option('env')) {
$repositories = RepositoryBuilder::create()
->make();
$envs = Dotenv::create(
$repositories,
$vars = self::getEnvironmentVariables(
// @phpstan-ignore-next-line
$this->laravel->environmentPath(),
// @phpstan-ignore-next-line
$this->laravel->environmentFile()
)->safeLoad();
);
foreach ($envs as $name => $value) {
$repositories->clear($name);
$repository = Env::getRepository();
foreach ($vars as $name) {
$repository->clear($name);
}
}
}
/**
* @param string $path
* @param string $file
*
* @return array
*/
protected static function getEnvironmentVariables($path, $file)
{
try {
$content = StoreBuilder::createWithNoNames()
->addPath($path)
->addName($file)
->make()
->read();
} catch (InvalidPathException $e) {
return [];
}
$vars = [];
foreach ((new Parser())->parse($content) as $entry) {
$vars[] = $entry->getName();
}
return $vars;
}
/**
* Check if the parallel dependencies are installed.
*
* @return bool
*/
protected function isParallelDependenciesInstalled()
{
return class_exists(\ParaTest\Console\Commands\ParaTestCommand::class);
}
/**
* Install parallel testing needed dependencies.
*
* @return void
*/
protected function installParallelDependencies()
{
$command = $this->findComposer() . ' require brianium/paratest --dev';
$process = Process::fromShellCommandline($command, null, null, null, null);
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) {
try {
$process->setTty(true);
} catch (RuntimeException $e) {
$this->output->writeln('Warning: ' . $e->getMessage());
}
}
try {
$process->run(function ($type, $line) {
$this->output->write($line);
});
} catch (ProcessSignaledException $e) {
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
}
}
/**
* Get the composer command for the environment.
*
* @return string
*/
protected function findComposer()
{
$composerPath = getcwd() . '/composer.phar';
if (file_exists($composerPath)) {
return '"' . PHP_BINARY . '" ' . $composerPath;
}
return 'composer';
}
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Laravel;
@@ -18,13 +11,9 @@ use Symfony\Component\Console\Exception\ExceptionInterface as SymfonyConsoleExce
use Throwable;
/**
* This is an Collision Laravel Adapter ExceptionHandler implementation.
*
* Registers the Error Handler on Laravel.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
class ExceptionHandler implements ExceptionHandlerContract
final class ExceptionHandler implements ExceptionHandlerContract
{
/**
* Holds an instance of the application exception handler.

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Laravel\Exceptions;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use RuntimeException;
/**
* @internal
*/
final class RequirementsException extends RuntimeException implements RenderlessEditor, RenderlessTrace
{
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Laravel;
@@ -16,13 +9,9 @@ use NunoMaduro\Collision\Contracts\SolutionsRepository;
use Throwable;
/**
* This is an Collision Laravel Adapter Solutions Provider implementation.
*
* Registers the Error Handler on Laravel.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
class IgnitionSolutionsRepository implements SolutionsRepository
final class IgnitionSolutionsRepository implements SolutionsRepository
{
/**
* Holds an instance of ignition solutions provider repository.

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* This file is part of Collision.
*
@@ -14,11 +16,9 @@ namespace NunoMaduro\Collision\Adapters\Laravel;
use Whoops\Exception\Inspector as BaseInspector;
/**
* This is an Collision Laravel Adapter Inspector implementation.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
class Inspector extends BaseInspector
final class Inspector extends BaseInspector
{
/**
* {@inheritdoc}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* This file is part of Collision.
*

View File

@@ -1,56 +1,245 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Phpunit;
/*
* This `if` condition exists because phpunit
* is not a direct dependency of Collision.
*
* This code bellow it's for phpunit@8
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use ReflectionObject;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Throwable;
/**
* @internal
*/
if (class_exists(\PHPUnit\Runner\Version::class) && intval(substr(\PHPUnit\Runner\Version::id(), 0, 1)) === 8) {
final class Printer implements \PHPUnit\TextUI\ResultPrinter
{
/**
* This is an Collision Phpunit Adapter implementation.
* Holds an instance of the style.
*
* @internal
* Style is a class we use to interact with output.
*
* @var Style
*/
final class Printer extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener
private $style;
/**
* Holds the duration time of the test suite.
*
* @var Timer
*/
private $timer;
/**
* Holds the state of the test
* suite. The number of tests, etc.
*
* @var State
*/
private $state;
/**
* If the test suite has failed.
*
* @var bool
*/
private $failed = false;
/**
* Creates a new instance of the listener.
*
* @param ConsoleOutput $output
*
* @throws \ReflectionException
*/
public function __construct(\Symfony\Component\Console\Output\ConsoleOutputInterface $output = null, bool $verbose = false, string $colors = 'always')
{
use PrinterContents;
$this->timer = Timer::start();
$decorated = $colors === 'always' || $colors === 'auto';
$output = $output ?? new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $decorated);
ConfigureIO::of(new ArgvInput(), $output);
$this->style = new Style($output);
$dummyTest = new class() extends TestCase {
};
$this->state = State::from($dummyTest);
}
}
/*
* This `if` condition exists because phpunit
* is not a direct dependency of Collision.
*
* This code bellow it's for phpunit@9
*/
if (class_exists(\PHPUnit\Runner\Version::class) && intval(substr(\PHPUnit\Runner\Version::id(), 0, 1)) === 9) {
/**
* This is an Collision Phpunit Adapter implementation.
*
* @internal
* {@inheritdoc}
*/
final class Printer implements \PHPUnit\TextUI\ResultPrinter
public function addError(Test $testCase, Throwable $throwable, float $time): void
{
use PrinterContents;
$this->failed = true;
/**
* Intentionally left blank as we output things on events of the listener.
*/
public function printResult(\PHPUnit\Framework\TestResult $result): void
{
// ..
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::FAIL, $throwable));
}
/**
* {@inheritdoc}
*/
public function addWarning(Test $testCase, Warning $warning, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::WARN, $warning));
}
/**
* {@inheritdoc}
*/
public function addFailure(Test $testCase, AssertionFailedError $error, float $time): void
{
$this->failed = true;
$testCase = $this->testCaseFromTest($testCase);
$reflector = new ReflectionObject($error);
if ($reflector->hasProperty('message')) {
$message = trim((string) preg_replace("/\r|\n/", "\n ", $error->getMessage()));
$property = $reflector->getProperty('message');
$property->setAccessible(true);
$property->setValue($error, $message);
}
$this->state->add(TestResult::fromTestCase($testCase, TestResult::FAIL, $error));
}
/**
* {@inheritdoc}
*/
public function addIncompleteTest(Test $testCase, Throwable $throwable, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::INCOMPLETE, $throwable));
}
/**
* {@inheritdoc}
*/
public function addRiskyTest(Test $testCase, Throwable $throwable, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::RISKY, $throwable));
}
/**
* {@inheritdoc}
*/
public function addSkippedTest(Test $testCase, Throwable $throwable, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::SKIPPED, $throwable));
}
/**
* {@inheritdoc}
*/
public function startTestSuite(TestSuite $suite): void
{
if ($this->state->suiteTotalTests === null) {
$this->state->suiteTotalTests = $suite->count();
}
}
/**
* {@inheritdoc}
*/
public function endTestSuite(TestSuite $suite): void
{
// ..
}
/**
* {@inheritdoc}
*/
public function startTest(Test $testCase): void
{
$testCase = $this->testCaseFromTest($testCase);
// Let's check first if the testCase is over.
if ($this->state->testCaseHasChanged($testCase)) {
$this->style->writeCurrentTestCaseSummary($this->state);
$this->state->moveTo($testCase);
}
}
/**
* {@inheritdoc}
*/
public function endTest(Test $testCase, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
if (!$this->state->existsInTestCase($testCase)) {
$this->state->add(TestResult::fromTestCase($testCase, TestResult::PASS));
}
if ($testCase instanceof TestCase
&& $testCase->getTestResultObject() instanceof \PHPUnit\Framework\TestResult
&& !$testCase->getTestResultObject()->isStrictAboutOutputDuringTests()
&& !$testCase->hasExpectationOnOutput()) {
$this->style->write($testCase->getActualOutput());
}
}
/**
* Intentionally left blank as we output things on events of the listener.
*/
public function write(string $content): void
{
// ..
}
/**
* Returns a test case from the given test.
*
* Note: This printer is do not work with normal Test classes - only
* with Test Case classes. Please report an issue if you think
* this should work any other way.
*/
private function testCaseFromTest(Test $test): TestCase
{
if (!$test instanceof TestCase) {
throw new ShouldNotHappen();
}
return $test;
}
/**
* Intentionally left blank as we output things on events of the listener.
*/
public function printResult(\PHPUnit\Framework\TestResult $result): void
{
if ($result->count() === 0) {
$this->style->writeWarning('No tests executed!');
}
$this->style->writeCurrentTestCaseSummary($this->state);
if ($this->failed) {
$onFailure = $this->state->suiteTotalTests !== $this->state->testSuiteTestsCount();
$this->style->writeErrorsSummary($this->state, $onFailure);
}
$this->style->writeRecap($this->state, $this->timer);
}
}

View File

@@ -1,224 +0,0 @@
<?php
namespace NunoMaduro\Collision\Adapters\Phpunit;
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use ReflectionObject;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Throwable;
trait PrinterContents
{
/**
* Holds an instance of the style.
*
* Style is a class we use to interact with output.
*
* @var Style
*/
private $style;
/**
* Holds the duration time of the test suite.
*
* @var Timer
*/
private $timer;
/**
* Holds the state of the test
* suite. The number of tests, etc.
*
* @var State
*/
private $state;
/**
* If the test suite has ended before.
*
* @var bool
*/
private $ended = false;
/**
* Creates a new instance of the listener.
*
* @param ConsoleOutput $output
*
* @throws \ReflectionException
*/
public function __construct(ConsoleOutput $output = null)
{
if (intval(substr(\PHPUnit\Runner\Version::id(), 0, 1)) === 8) {
parent::__construct();
}
$this->timer = Timer::start();
$output = $output ?? new ConsoleOutput();
ConfigureIO::of(new ArgvInput(), $output);
$this->style = new Style($output);
$dummyTest = new class() extends TestCase {
};
$this->state = State::from($dummyTest);
}
/**
* {@inheritdoc}
*/
public function addError(Test $testCase, Throwable $throwable, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::FAIL));
$this->style->writeError($this->state, $throwable);
}
/**
* {@inheritdoc}
*/
public function addWarning(Test $testCase, Warning $warning, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::WARN, $warning->getMessage()));
}
/**
* {@inheritdoc}
*/
public function addFailure(Test $testCase, AssertionFailedError $error, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::FAIL));
$reflector = new ReflectionObject($error);
if ($reflector->hasProperty('message')) {
$message = trim((string) preg_replace("/\r|\n/", ' ', $error->getMessage()));
$property = $reflector->getProperty('message');
$property->setAccessible(true);
$property->setValue($error, $message);
}
$this->style->writeError($this->state, $error);
}
/**
* {@inheritdoc}
*/
public function addIncompleteTest(Test $testCase, Throwable $t, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::INCOMPLETE));
}
/**
* {@inheritdoc}
*/
public function addRiskyTest(Test $testCase, Throwable $t, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::RISKY, $t->getMessage()));
}
/**
* {@inheritdoc}
*/
public function addSkippedTest(Test $testCase, Throwable $t, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
$this->state->add(TestResult::fromTestCase($testCase, TestResult::SKIPPED, $t->getMessage()));
}
/**
* {@inheritdoc}
*/
public function startTestSuite(TestSuite $suite): void
{
if ($this->state->suiteTotalTests === null) {
$this->state->suiteTotalTests = $suite->count();
}
}
/**
* {@inheritdoc}
*/
public function endTestSuite(TestSuite $suite): void
{
if (!$this->ended && $this->state->suiteTotalTests === $this->state->testSuiteTestsCount()) {
$this->ended = true;
$this->style->writeCurrentRecap($this->state);
$this->style->updateFooter($this->state);
$this->style->writeRecap($this->timer);
}
}
/**
* {@inheritdoc}
*/
public function startTest(Test $testCase): void
{
$testCase = $this->testCaseFromTest($testCase);
// Let's check first if the testCase is over.
if ($this->state->testCaseHasChanged($testCase)) {
$this->style->writeCurrentRecap($this->state);
$this->state->moveTo($testCase);
}
$this->style->updateFooter($this->state, $testCase);
}
/**
* {@inheritdoc}
*/
public function endTest(Test $testCase, float $time): void
{
$testCase = $this->testCaseFromTest($testCase);
if (!$this->state->existsInTestCase($testCase)) {
$this->state->add(TestResult::fromTestCase($testCase, TestResult::PASS));
}
}
/**
* Intentionally left blank as we output things on events of the listener.
*/
public function write(string $content): void
{
// ..
}
/**
* Returns a test case from the given test.
*
* Note: This printer is do not work with normal Test classes - only
* with Test Case classes. Please report an issue if you think
* this should work any other way.
*/
private function testCaseFromTest(Test $test): TestCase
{
if (!$test instanceof TestCase) {
throw new ShouldNotHappen();
}
return $test;
}
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Phpunit;
@@ -47,6 +40,20 @@ final class State
*/
public $testCaseTests = [];
/**
* The current test case tests.
*
* @var array<int, TestResult>
*/
public $toBePrintedCaseTests = [];
/**
* Header printed.
*
* @var bool
*/
public $headerPrinted = false;
/**
* The state constructor.
*/
@@ -68,7 +75,8 @@ final class State
*/
public function add(TestResult $test): void
{
$this->testCaseTests[] = $test;
$this->testCaseTests[] = $test;
$this->toBePrintedCaseTests[] = $test;
$this->suiteTests[] = $test;
}
@@ -145,6 +153,8 @@ final class State
$this->testCaseName = self::getPrintableTestCaseName($testCase);
$this->testCaseTests = [];
$this->headerPrinted = false;
}
/**
@@ -152,9 +162,11 @@ final class State
*/
public function eachTestCaseTests(callable $callback): void
{
foreach ($this->testCaseTests as $test) {
foreach ($this->toBePrintedCaseTests as $test) {
$callback($test);
}
$this->toBePrintedCaseTests = [];
}
public function countTestsInTestSuiteBy(string $type): int
@@ -181,14 +193,10 @@ final class State
/**
* Returns the printable test case name from the given `TestCase`.
*/
private static function getPrintableTestCaseName(TestCase $test): string
public static function getPrintableTestCaseName(TestCase $test): string
{
if ($test instanceof HasPrintableTestCaseName) {
$name = $test->getPrintableTestCaseName();
} else {
$name = get_class($test);
}
return $name;
return $test instanceof HasPrintableTestCaseName
? $test->getPrintableTestCaseName()
: get_class($test);
}
}

View File

@@ -1,23 +1,16 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Phpunit;
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
use NunoMaduro\Collision\Writer;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Throwable;
use Whoops\Exception\Inspector;
@@ -31,19 +24,24 @@ final class Style
*/
private $output;
/**
* @var ConsoleSectionOutput
*/
private $footer;
/**
* Style constructor.
*/
public function __construct(ConsoleOutput $output)
public function __construct(ConsoleOutputInterface $output)
{
$this->output = $output;
if (!$output instanceof ConsoleOutput) {
throw new ShouldNotHappen();
}
$this->footer = $output->section();
$this->output = $output;
}
/**
* Prints the content.
*/
public function write(string $content): void
{
$this->output->write($content);
}
/**
@@ -54,20 +52,21 @@ final class Style
* ✓ basic test
* ```
*/
public function writeCurrentRecap(State $state): void
public function writeCurrentTestCaseSummary(State $state): void
{
if (!$state->testCaseTestsCount()) {
if ($state->testCaseTestsCount() === 0) {
return;
}
$this->footer->clear();
$this->output->writeln($this->titleLineFrom(
$state->getTestCaseTitle() === 'FAIL' ? 'white' : 'black',
$state->getTestCaseTitleColor(),
$state->getTestCaseTitle(),
$state->testCaseName
));
if (!$state->headerPrinted) {
$this->output->writeln($this->titleLineFrom(
$state->getTestCaseTitle() === 'FAIL' ? 'white' : 'black',
$state->getTestCaseTitleColor(),
$state->getTestCaseTitle(),
$state->testCaseName
));
$state->headerPrinted = true;
}
$state->eachTestCaseTests(function (TestResult $testResult) {
$this->output->writeln($this->testLineFrom(
@@ -80,85 +79,97 @@ final class Style
}
/**
* Prints the content similar too on the footer. Where
* we are updating the current test.
* Prints the content similar too:.
*
* ```
* Runs Unit\ExampleTest
* basic test
* PASS Unit\ExampleTest
* basic test
* ```
*/
public function updateFooter(State $state, TestCase $testCase = null): void
public function writeErrorsSummary(State $state, bool $onFailure): void
{
$runs = [];
$errors = array_filter($state->suiteTests, function (TestResult $testResult) {
return $testResult->type === TestResult::FAIL;
});
if ($testCase) {
$runs[] = $this->titleLineFrom(
'black',
'yellow',
'RUNS',
get_class($testCase)
);
$testResult = TestResult::fromTestCase($testCase, TestResult::RUNS);
$runs[] = $this->testLineFrom(
$testResult->color,
$testResult->icon,
$testResult->description
);
if (!$onFailure) {
$this->output->writeln(['', " \e[2m---\e[22m", '']);
}
$types = [TestResult::FAIL, TestResult::WARN, TestResult::RISKY, TestResult::INCOMPLETE, TestResult::SKIPPED, TestResult::PASS];
array_map(function (TestResult $testResult) use ($onFailure) {
if (!$onFailure) {
$this->output->write(sprintf(
' <fg=red;options=bold>• %s </>> <fg=red;options=bold>%s</>',
$testResult->testCaseName,
$testResult->description
));
}
if (!$testResult->throwable instanceof Throwable) {
throw new ShouldNotHappen();
}
$this->writeError($testResult->throwable);
}, $errors);
}
/**
* Writes the final recap.
*/
public function writeRecap(State $state, Timer $timer = null): void
{
$types = [TestResult::FAIL, TestResult::WARN, TestResult::RISKY, TestResult::INCOMPLETE, TestResult::SKIPPED, TestResult::PASS];
foreach ($types as $type) {
if ($countTests = $state->countTestsInTestSuiteBy($type)) {
if (($countTests = $state->countTestsInTestSuiteBy($type)) !== 0) {
$color = TestResult::makeColor($type);
$tests[] = "<fg=$color;options=bold>$countTests $type</>";
}
}
$pending = $state->suiteTotalTests - $state->testSuiteTestsCount();
if ($pending) {
if ($pending !== 0) {
$tests[] = "\e[2m$pending pending\e[22m";
}
if (!empty($tests)) {
$this->footer->overwrite(array_merge($runs, [
'',
$this->output->write([
"\n",
sprintf(
' <fg=white;options=bold>Tests: </><fg=default>%s</>',
implode(', ', $tests)
),
]));
]);
}
if ($timer !== null) {
$timeElapsed = number_format($timer->result(), 2, '.', '');
$this->output->writeln([
'',
sprintf(
' <fg=white;options=bold>Time: </><fg=default>%ss</>',
$timeElapsed
),
]
);
}
$this->output->writeln('');
}
/**
* Writes the final recap.
* Displays a warning message.
*/
public function writeRecap(Timer $timer): void
public function writeWarning(string $message): void
{
$timeElapsed = number_format($timer->result(), 2, '.', '');
$this->footer->writeln(
sprintf(
' <fg=white;options=bold>Time: </><fg=default>%ss</>',
$timeElapsed
)
);
$this->output->writeln($this->testLineFrom('yellow', $message, ''));
}
/**
* Displays the error using Collision's writer
* and terminates with exit code === 1.
*
* @return void
*/
public function writeError(State $state, Throwable $throwable)
public function writeError(Throwable $throwable): void
{
$this->writeCurrentRecap($state);
$this->updateFooter($state);
$writer = (new Writer())->setOutput($this->output);
if ($throwable instanceof AssertionFailedError) {
@@ -167,10 +178,24 @@ final class Style
}
$writer->ignoreFilesIn([
'/vendor\/pestphp\/pest/',
'/vendor\/phpspec\/prophecy-phpunit/',
'/vendor\/phpunit\/phpunit\/src/',
'/vendor\/mockery\/mockery/',
'/vendor\/laravel\/dusk/',
'/vendor\/laravel\/framework\/src\/Illuminate\/Testing/',
'/vendor\/laravel\/framework\/src\/Illuminate\/Foundation\/Testing/',
'/vendor\/symfony\/framework-bundle\/Test/',
'/vendor\/symfony\/phpunit-bridge/',
'/vendor\/symfony\/dom-crawler/',
'/vendor\/symfony\/browser-kit/',
'/vendor\/symfony\/css-selector/',
'/vendor\/bin\/.phpunit/',
'/bin\/.phpunit/',
'/vendor\/bin\/simple-phpunit/',
'/bin\/phpunit/',
'/vendor\/coduo\/php-matcher\/src\/PHPUnit/',
'/vendor\/sulu\/sulu\/src\/Sulu\/Bundle\/TestBundle\/Testing/',
]);
if ($throwable instanceof ExceptionWrapper && $throwable->getOriginalException() !== null) {
@@ -182,10 +207,25 @@ final class Style
$writer->write($inspector);
if ($throwable instanceof ExpectationFailedException && $comparisionFailure = $throwable->getComparisonFailure()) {
$this->output->write($comparisionFailure->getDiff());
$diff = $comparisionFailure->getDiff();
$lines = explode(PHP_EOL, $diff);
$diff = '';
foreach ($lines as $line) {
if (0 === strpos($line, '-')) {
$line = '<fg=red>' . $line . '</>';
} elseif (0 === strpos($line, '+')) {
$line = '<fg=green>' . $line . '</>';
}
$diff .= $line . PHP_EOL;
}
$diff = trim((string) preg_replace("/\r|\n/", "\n ", $diff));
$this->output->write(" $diff");
}
exit(1);
$this->output->writeln('');
}
/**
@@ -193,20 +233,6 @@ final class Style
*/
private function titleLineFrom(string $fg, string $bg, string $title, string $testCaseName): string
{
if (class_exists($testCaseName)) {
$nameParts = explode('\\', $testCaseName);
$highlightedPart = array_pop($nameParts);
$nonHighlightedPart = implode('\\', $nameParts);
$testCaseName = sprintf("\e[2m%s\e[22m<fg=white;options=bold>%s</>", "$nonHighlightedPart\\", $highlightedPart);
} elseif (file_exists($testCaseName)) {
$testCaseName = substr($testCaseName, strlen((string) getcwd()) + 1);
$nameParts = explode(DIRECTORY_SEPARATOR, $testCaseName);
$highlightedPart = (string) array_pop($nameParts);
$highlightedPart = substr($highlightedPart, 0, (int) strrpos($highlightedPart, '.'));
$nonHighlightedPart = implode('\\', $nameParts);
$testCaseName = sprintf("\e[2m%s\e[22m<fg=white;options=bold>%s</>", "$nonHighlightedPart\\", $highlightedPart);
}
return sprintf(
"\n <fg=%s;bg=%s;options=bold> %s </><fg=default> %s</>",
$fg,

View File

@@ -1,17 +1,12 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Phpunit;
use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName;
use PHPUnit\Framework\TestCase;
use Throwable;
/**
* @internal
@@ -26,6 +21,13 @@ final class TestResult
public const RUNS = 'pending';
public const PASS = 'passed';
/**
* @readonly
*
* @var string
*/
public $testCaseName;
/**
* @readonly
*
@@ -57,36 +59,53 @@ final class TestResult
/**
* @readonly
*
* @var string|null
* @var Throwable|null
*/
public $warning;
public $throwable;
/**
* @readonly
*
* @var string
*/
public $warning = '';
/**
* Test constructor.
*
* @param string $warning
*/
private function __construct(string $description, string $type, string $icon, string $color, string $warning = null)
private function __construct(string $testCaseName, string $description, string $type, string $icon, string $color, Throwable $throwable = null)
{
$this->description = $description;
$this->type = $type;
$this->icon = $icon;
$this->color = $color;
$this->warning = trim((string) preg_replace("/\r|\n/", ' ', (string) $warning));
$this->testCaseName = $testCaseName;
$this->description = $description;
$this->type = $type;
$this->icon = $icon;
$this->color = $color;
$this->throwable = $throwable;
$asWarning = $this->type === TestResult::WARN
|| $this->type === TestResult::RISKY
|| $this->type === TestResult::SKIPPED
|| $this->type === TestResult::INCOMPLETE;
if ($throwable instanceof Throwable && $asWarning) {
$this->warning = trim((string) preg_replace("/\r|\n/", ' ', $throwable->getMessage()));
}
}
/**
* Creates a new test from the given test case.
*/
public static function fromTestCase(TestCase $testCase, string $type, string $warning = null): self
public static function fromTestCase(TestCase $testCase, string $type, Throwable $throwable = null): self
{
$testCaseName = State::getPrintableTestCaseName($testCase);
$description = self::makeDescription($testCase);
$icon = self::makeIcon($type);
$color = self::makeColor($type);
return new self($description, $type, $icon, $color, $warning);
return new self($testCaseName, $description, $type, $icon, $color, $throwable);
}
/**
@@ -94,7 +113,11 @@ final class TestResult
*/
public static function makeDescription(TestCase $testCase): string
{
$name = $testCase->getName(true);
$name = $testCase->getName(false);
if ($testCase instanceof HasPrintableTestCaseName) {
return $name;
}
// First, lets replace underscore by spaces.
$name = str_replace('_', ' ', $name);
@@ -106,10 +129,21 @@ final class TestResult
$name = (string) preg_replace('/^test/', '', $name);
// Removes spaces
$name = (string) trim($name);
$name = trim($name);
// Finally, lower case everything
return (string) mb_strtolower($name);
// Lower case everything
$name = mb_strtolower($name);
// Add the dataset name if it has one
if ($dataName = $testCase->dataName()) {
if (is_int($dataName)) {
$name .= sprintf(' with data set #%d', $dataName);
} else {
$name .= sprintf(' with data set "%s"', $dataName);
}
}
return $name;
}
/**
@@ -119,15 +153,15 @@ final class TestResult
{
switch ($type) {
case self::FAIL:
return '';
return '';
case self::SKIPPED:
return 's';
return '-';
case self::RISKY:
return 'r';
return '!';
case self::INCOMPLETE:
return 'i';
return '';
case self::WARN:
return 'w';
return '!';
case self::RUNS:
return '•';
default:

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Phpunit;

View File

@@ -1,25 +1,20 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision;
use NunoMaduro\Collision\Contracts\ArgumentFormatter as ArgumentFormatterContract;
/**
* This is an Collision Argument Formatter implementation.
* @internal
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @see \Tests\Unit\ArgumentFormatterTest
*/
class ArgumentFormatter implements ArgumentFormatterContract
final class ArgumentFormatter implements ArgumentFormatterContract
{
private const MAX_STRING_LENGTH = 1000;
/**
* {@inheritdoc}
*/
@@ -30,7 +25,7 @@ class ArgumentFormatter implements ArgumentFormatterContract
foreach ($arguments as $argument) {
switch (true) {
case is_string($argument):
$result[] = '"' . $argument . '"';
$result[] = '"' . (mb_strlen($argument) > self::MAX_STRING_LENGTH ? mb_substr($argument, 0, self::MAX_STRING_LENGTH) . '...' : $argument) . '"';
break;
case is_array($argument):
$associative = array_keys($argument) !== range(0, count($argument) - 1);

View File

@@ -1,35 +1,23 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision;
use NunoMaduro\Collision\Exceptions\InvalidStyleException;
use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
/**
* This is an Collision Console Color implementation.
*
* Code originally from { JakubOnderka\\PhpConsoleColor }. But the package got deprecated.
*
* @internal
*
* @final
*/
class ConsoleColor
final class ConsoleColor
{
const FOREGROUND = 38;
const BACKGROUND = 48;
public const FOREGROUND = 38;
public const BACKGROUND = 48;
const COLOR256_REGEXP = '~^(bg_)?color_([0-9]{1,3})$~';
public const COLOR256_REGEXP = '~^(bg_)?color_(\d{1,3})$~';
const RESET_STYLE = 0;
public const RESET_STYLE = 0;
/** @var bool */
private $isSupported;
@@ -38,7 +26,7 @@ class ConsoleColor
private $forceStyle = false;
/** @var array */
private $styles = [
private const STYLES = [
'none' => null,
'bold' => '1',
'dark' => '2',
@@ -145,7 +133,7 @@ class ConsoleColor
*/
public function setForceStyle($forceStyle)
{
$this->forceStyle = (bool) $forceStyle;
$this->forceStyle = $forceStyle;
}
/**
@@ -217,17 +205,16 @@ class ConsoleColor
*/
public function isSupported()
{
if (DIRECTORY_SEPARATOR === '\\') {
if (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT)) {
return true;
} elseif (getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON') {
return true;
}
return false;
} else {
return function_exists('posix_isatty') && @posix_isatty(STDOUT);
// The COLLISION_FORCE_COLORS variable is for internal purposes only
if (getenv('COLLISION_FORCE_COLORS') !== false) {
return true;
}
if (DIRECTORY_SEPARATOR === '\\') {
return getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON';
}
return function_exists('posix_isatty') && @posix_isatty(STDOUT);
}
/**
@@ -237,9 +224,9 @@ class ConsoleColor
{
if (DIRECTORY_SEPARATOR === '\\') {
return function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT);
} else {
return strpos(getenv('TERM'), '256color') !== false;
}
return strpos(getenv('TERM'), '256color') !== false;
}
/**
@@ -247,7 +234,7 @@ class ConsoleColor
*/
public function getPossibleStyles()
{
return array_keys($this->styles);
return array_keys(self::STYLES);
}
/**
@@ -272,8 +259,8 @@ class ConsoleColor
*/
private function styleSequence($style)
{
if (array_key_exists($style, $this->styles)) {
return $this->styles[$style];
if (array_key_exists($style, self::STYLES)) {
return self::STYLES[$style];
}
if (!$this->are256ColorsSupported()) {
@@ -295,7 +282,7 @@ class ConsoleColor
*/
private function isValidStyle($style)
{
return array_key_exists($style, $this->styles) || preg_match(self::COLOR256_REGEXP, $style);
return array_key_exists($style, self::STYLES) || preg_match(self::COLOR256_REGEXP, $style);
}
/**

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts\Adapters\Phpunit;

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts\Adapters\Phpunit;
@@ -15,9 +8,7 @@ use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestListener;
/**
* This is an Collision Phpunit Adapter contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface Listener extends TestListener
{

View File

@@ -1,20 +1,11 @@
<?php
/*
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
/**
* This is an Collision Argument Formatter contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface ArgumentFormatter
{

View File

@@ -1,13 +1,6 @@
<?php
/*
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
@@ -15,9 +8,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Whoops\Handler\HandlerInterface;
/**
* This is an Collision Handler contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface Handler extends HandlerInterface
{

View File

@@ -1,20 +1,11 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
/**
* This is the Collision Highlighter contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface Highlighter
{

View File

@@ -1,20 +1,11 @@
<?php
/*
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
/**
* This is an Collision Provider contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface Provider
{

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
/**
* @internal
*/
interface RenderlessEditor
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
/**
* @internal
*/
interface RenderlessTrace
{
}

View File

@@ -1,13 +1,6 @@
<?php
/*
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
@@ -15,9 +8,7 @@ use Facade\IgnitionContracts\Solution;
use Throwable;
/**
* This is an Collision Solutions Repository contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface SolutionsRepository
{

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/**
* This file is part of Collision.
*
@@ -15,9 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Whoops\Exception\Inspector;
/**
* This is the Collision Writer contract.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
interface Writer
{

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace NunoMaduro\Collision\Exceptions;
use RuntimeException;
/**
* @internal
*/
final class InvalidStyleException extends RuntimeException
{
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\Exceptions;
@@ -18,10 +11,13 @@ use RuntimeException;
*/
final class ShouldNotHappen extends RuntimeException
{
/**
* @var string
*/
private const MESSAGE = 'This should not happen, please open an issue on collision repository: %s';
public function __construct()
{
$message = 'This should not happen, please open an issue on collision repository: %s';
parent::__construct(sprintf($message, 'https://github.com/nunomaduro/collision/issues/new'));
parent::__construct(sprintf(self::MESSAGE, 'https://github.com/nunomaduro/collision/issues/new'));
}
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision;
@@ -17,11 +10,11 @@ use Symfony\Component\Console\Output\OutputInterface;
use Whoops\Handler\Handler as AbstractHandler;
/**
* This is an Collision Handler implementation.
* @internal
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @see \Tests\Unit\HandlerTest
*/
class Handler extends AbstractHandler implements HandlerContract
final class Handler extends AbstractHandler implements HandlerContract
{
/**
* Holds an instance of the writer.

View File

@@ -1,84 +1,94 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision;
use NunoMaduro\Collision\Contracts\Highlighter as HighlighterContract;
/**
* This is an Collision Highlighter implementation.
*
* Code originally from { JakubOnderka\\PhpConsoleColor }. But the package got deprecated.
*
* @internal
*
* @final
*/
class Highlighter implements HighlighterContract
final class Highlighter implements HighlighterContract
{
public const TOKEN_DEFAULT = 'token_default';
public const TOKEN_COMMENT = 'token_comment';
public const TOKEN_STRING = 'token_string';
public const TOKEN_HTML = 'token_html';
public const TOKEN_KEYWORD = 'token_keyword';
public const ACTUAL_LINE_MARK = 'actual_line_mark';
public const LINE_NUMBER = 'line_number';
private const ARROW_SYMBOL = '>';
private const DELIMITER = '|';
private const ARROW_SYMBOL_UTF8 = '➜';
private const DELIMITER_UTF8 = '▕'; // '▶';
private const LINE_NUMBER_DIVIDER = 'line_divider';
private const MARKED_LINE_NUMBER = 'marked_line';
private const WIDTH = 3;
/**
* Holds the theme.
*
* @var array
*/
private $theme = [
self::TOKEN_STRING => ['light_gray'],
self::TOKEN_COMMENT => ['dark_gray', 'italic'],
self::TOKEN_KEYWORD => ['magenta', 'bold'],
self::TOKEN_DEFAULT => ['default', 'bold'],
self::TOKEN_HTML => ['blue', 'bold'],
self::ACTUAL_LINE_MARK => ['red', 'bold'],
self::LINE_NUMBER => ['dark_gray'],
private const THEME = [
self::TOKEN_STRING => ['light_gray'],
self::TOKEN_COMMENT => ['dark_gray', 'italic'],
self::TOKEN_KEYWORD => ['magenta', 'bold'],
self::TOKEN_DEFAULT => ['default', 'bold'],
self::TOKEN_HTML => ['blue', 'bold'],
self::ACTUAL_LINE_MARK => ['red', 'bold'],
self::LINE_NUMBER => ['dark_gray'],
self::MARKED_LINE_NUMBER => ['italic', 'bold'],
self::LINE_NUMBER_DIVIDER => ['dark_gray'],
];
const TOKEN_DEFAULT = 'token_default';
const TOKEN_COMMENT = 'token_comment';
const TOKEN_STRING = 'token_string';
const TOKEN_HTML = 'token_html';
const TOKEN_KEYWORD = 'token_keyword';
const ACTUAL_LINE_MARK = 'actual_line_mark';
const LINE_NUMBER = 'line_number';
/** @var ConsoleColor */
private $color;
/** @var array */
private $defaultTheme = [
private const DEFAULT_THEME = [
self::TOKEN_STRING => 'red',
self::TOKEN_COMMENT => 'yellow',
self::TOKEN_KEYWORD => 'green',
self::TOKEN_DEFAULT => 'default',
self::TOKEN_HTML => 'cyan',
self::ACTUAL_LINE_MARK => 'red',
self::LINE_NUMBER => 'dark_gray',
self::ACTUAL_LINE_MARK => 'dark_gray',
self::LINE_NUMBER => 'dark_gray',
self::MARKED_LINE_NUMBER => 'dark_gray',
self::LINE_NUMBER_DIVIDER => 'dark_gray',
];
/** @var string */
private $delimiter = self::DELIMITER_UTF8;
/** @var string */
private $arrow = self::ARROW_SYMBOL_UTF8;
/**
* @var string
*/
private const NO_MARK = ' ';
/**
* Creates an instance of the Highlighter.
*/
public function __construct(ConsoleColor $color = null)
public function __construct(ConsoleColor $color = null, bool $UTF8 = true)
{
$this->color = $color ?: new ConsoleColor();
foreach ($this->defaultTheme as $name => $styles) {
foreach (self::DEFAULT_THEME as $name => $styles) {
if (!$this->color->hasTheme($name)) {
$this->color->addTheme($name, $styles);
}
}
foreach ($this->theme as $name => $styles) {
$this->color->addTheme((string) $name, $styles);
foreach (self::THEME as $name => $styles) {
$this->color->addTheme($name, $styles);
}
if (!$UTF8) {
$this->delimiter = self::DELIMITER;
$this->arrow = self::ARROW_SYMBOL;
}
$this->delimiter .= ' ';
}
/**
@@ -94,10 +104,8 @@ class Highlighter implements HighlighterContract
* @param int $lineNumber
* @param int $linesBefore
* @param int $linesAfter
*
* @return string
*/
public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2)
public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2): string
{
$tokenLines = $this->getHighlightedLines($source);
@@ -113,10 +121,8 @@ class Highlighter implements HighlighterContract
/**
* @param string $source
*
* @return array
*/
private function getHighlightedLines($source)
private function getHighlightedLines($source): array
{
$source = str_replace(["\r\n", "\r"], "\n", $source);
$tokens = $this->tokenize($source);
@@ -126,10 +132,8 @@ class Highlighter implements HighlighterContract
/**
* @param string $source
*
* @return array
*/
private function tokenize($source)
private function tokenize($source): array
{
$tokens = token_get_all($source);
@@ -148,7 +152,6 @@ class Highlighter implements HighlighterContract
case T_CLOSE_TAG:
case T_STRING:
case T_VARIABLE:
// Constants
case T_DIR:
case T_FILE:
@@ -204,10 +207,7 @@ class Highlighter implements HighlighterContract
return $output;
}
/**
* @return array
*/
private function splitToLines(array $tokens)
private function splitToLines(array $tokens): array
{
$lines = [];
@@ -232,10 +232,7 @@ class Highlighter implements HighlighterContract
return $lines;
}
/**
* @return array
*/
private function colorLines(array $tokenLines)
private function colorLines(array $tokenLines): array
{
$lines = [];
foreach ($tokenLines as $lineCount => $tokenLine) {
@@ -256,24 +253,47 @@ class Highlighter implements HighlighterContract
/**
* @param int|null $markLine
*
* @return string
*/
private function lineNumbers(array $lines, $markLine = null)
private function lineNumbers(array $lines, $markLine = null): string
{
end($lines);
$lineStrlen = strlen(key($lines) + 1);
$snippet = '';
$lineStrlen = strlen((string) (array_key_last($lines) + 1));
$lineStrlen = $lineStrlen < self::WIDTH ? self::WIDTH : $lineStrlen;
$snippet = '';
$mark = ' ' . $this->arrow . ' ';
foreach ($lines as $i => $line) {
if ($markLine !== null) {
$snippet .= ($markLine === $i + 1 ? $this->color->apply(self::ACTUAL_LINE_MARK, ' > ') : ' ');
}
$coloredLineNumber = $this->coloredLineNumber(self::LINE_NUMBER, $i, $lineStrlen);
if (null !== $markLine) {
$snippet .=
($markLine === $i + 1
? $this->color->apply(self::ACTUAL_LINE_MARK, $mark)
: self::NO_MARK
);
$coloredLineNumber =
($markLine === $i + 1 ?
$this->coloredLineNumber(self::MARKED_LINE_NUMBER, $i, $lineStrlen) :
$coloredLineNumber
);
}
$snippet .= $coloredLineNumber;
$snippet .=
$this->color->apply(self::LINE_NUMBER_DIVIDER, $this->delimiter);
$snippet .= $this->color->apply(self::LINE_NUMBER, str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ');
$snippet .= $line . PHP_EOL;
}
return $snippet;
}
/**
* @param string $style
* @param int $i
* @param int $lineStrlen
*/
private function coloredLineNumber($style, $i, $lineStrlen): string
{
return $this->color->apply($style, str_pad((string) ($i + 1), $lineStrlen, ' ', STR_PAD_LEFT));
}
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision;
@@ -17,11 +10,11 @@ use Whoops\Run;
use Whoops\RunInterface;
/**
* This is an Collision Provider implementation.
* @internal
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @see \Tests\Unit\ProviderTest
*/
class Provider implements ProviderContract
final class Provider implements ProviderContract
{
/**
* Holds an instance of the Run.

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision\SolutionsRepositories;
@@ -15,11 +8,9 @@ use NunoMaduro\Collision\Contracts\SolutionsRepository;
use Throwable;
/**
* This is an Collision Null Solutions Provider implementation.
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @internal
*/
class NullSolutionsRepository implements SolutionsRepository
final class NullSolutionsRepository implements SolutionsRepository
{
/**
* {@inheritdoc}

View File

@@ -1,18 +1,13 @@
<?php
/**
* This file is part of Collision.
*
* (c) Nuno Maduro <enunomaduro@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace NunoMaduro\Collision;
use NunoMaduro\Collision\Contracts\ArgumentFormatter as ArgumentFormatterContract;
use NunoMaduro\Collision\Contracts\Highlighter as HighlighterContract;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use NunoMaduro\Collision\Contracts\SolutionsRepository;
use NunoMaduro\Collision\Contracts\Writer as WriterContract;
use NunoMaduro\Collision\SolutionsRepositories\NullSolutionsRepository;
@@ -22,16 +17,16 @@ use Whoops\Exception\Frame;
use Whoops\Exception\Inspector;
/**
* This is an Collision Writer implementation.
* @internal
*
* @author Nuno Maduro <enunomaduro@gmail.com>
* @see \Tests\Unit\WriterTest
*/
class Writer implements WriterContract
final class Writer implements WriterContract
{
/**
* The number of frames if no verbosity is specified.
*/
const VERBOSITY_NORMAL_FRAMES = 1;
public const VERBOSITY_NORMAL_FRAMES = 1;
/**
* Holds an instance of the solutions repository.
@@ -116,15 +111,20 @@ class Writer implements WriterContract
$editorFrame = array_shift($frames);
if ($this->showEditor && $editorFrame !== null) {
$exception = $inspector->getException();
if ($this->showEditor
&& $editorFrame !== null
&& !$exception instanceof RenderlessEditor
) {
$this->renderEditor($editorFrame);
}
$this->renderSolution($inspector);
if ($this->showTrace && !empty($frames)) {
if ($this->showTrace && !empty($frames) && !$exception instanceof RenderlessTrace) {
$this->renderTrace($frames);
} else {
} elseif (!$exception instanceof RenderlessEditor) {
$this->output->writeln('');
}
}
@@ -202,7 +202,10 @@ class Writer implements WriterContract
}
foreach ($this->ignore as $ignore) {
if (preg_match($ignore, $frame->getFile())) {
// Ensure paths are linux-style (like the ones on $this->ignore)
// @phpstan-ignore-next-line
$sanitizedPath = (string) str_replace('\\', '/', $frame->getFile());
if (preg_match($ignore, $sanitizedPath)) {
return false;
}
}
@@ -267,15 +270,17 @@ class Writer implements WriterContract
*/
protected function renderEditor(Frame $frame): WriterContract
{
$file = $this->getFileRelativePath((string) $frame->getFile());
if ($frame->getFile() !== 'Unknown') {
$file = $this->getFileRelativePath((string) $frame->getFile());
// getLine() might return null so cast to int to get 0 instead
$line = (int) $frame->getLine();
$this->render('at <fg=green>' . $file . '</>' . ':<fg=green>' . $line . '</>');
// getLine() might return null so cast to int to get 0 instead
$line = (int) $frame->getLine();
$this->render('at <fg=green>' . $file . '</>' . ':<fg=green>' . $line . '</>');
$content = $this->highlighter->highlight((string) $frame->getFileContents(), (int) $frame->getLine());
$content = $this->highlighter->highlight((string) $frame->getFileContents(), (int) $frame->getLine());
$this->output->writeln($content);
$this->output->writeln($content);
}
return $this;
}
@@ -317,15 +322,6 @@ class Writer implements WriterContract
$this->render("<fg=white> $class$function($args)</>", false);
}
/* Let's consider add this later...
* if ($vendorFrames > 0) {
* $this->output->write(
* sprintf("\n \e[2m+%s vendor frames \e[22m\n", $vendorFrames)
* );
* $vendorFrames = 0;
* }.
*/
return $this;
}