package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -0,0 +1 @@
.gitkeep

View File

@@ -1,3 +1,7 @@
<a href="https://supportukrainenow.org/"><img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg" width="100%"></a>
------
<p align="center">
<img src="https://raw.githubusercontent.com/nunomaduro/collision/stable/docs/logo.png" alt="Collision logo" width="480">
<br>
@@ -22,7 +26,7 @@ Collision was created by, and is maintained by **[Nuno Maduro](https://github.co
## Installation & Usage
> **Requires [PHP 7.3+](https://php.net/releases/)**
> **Requires [PHP 8.0+](https://php.net/releases/)**
Require Collision using [Composer](https://getcomposer.org):
@@ -37,6 +41,7 @@ composer require nunomaduro/collision --dev
6.x | 3.x
7.x | 4.x
8.x | 5.x
9.x | 6.x
As an example, here is how to require Collision on Laravel 6.x:
@@ -69,12 +74,6 @@ Thank you for considering to contribute to Collision. All the contribution guide
You can have a look at the [CHANGELOG](CHANGELOG.md) for constant updates & detailed information about the changes. You can also follow the twitter account for latest announcements or just come say hi!: [@enunomaduro](https://twitter.com/enunomaduro)
## Support the development
**Do you like this project? Support it by donating**
- PayPal: [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L)
- Patreon: [Donate](https://www.patreon.com/nunomaduro)
## License
Collision is an open-sourced software licensed under the [MIT license](LICENSE.md).

View File

@@ -14,21 +14,19 @@
}
],
"require": {
"php": "^7.3 || ^8.0",
"facade/ignition-contracts": "^1.0",
"filp/whoops": "^2.14.3",
"symfony/console": "^5.0"
"php": "^8.0.0",
"filp/whoops": "^2.14.5",
"symfony/console": "^6.0.2"
},
"require-dev": {
"brianium/paratest": "^6.1",
"fideloper/proxy": "^4.4.1",
"fruitcake/laravel-cors": "^2.0.3",
"laravel/framework": "8.x-dev",
"nunomaduro/larastan": "^0.6.2",
"nunomaduro/mock-final-classes": "^1.0",
"orchestra/testbench": "^6.0",
"phpstan/phpstan": "^0.12.64",
"phpunit/phpunit": "^9.5.0"
"brianium/paratest": "^6.4.1",
"laravel/framework": "^9.26.1",
"laravel/pint": "^1.1.1",
"nunomaduro/larastan": "^1.0.3",
"nunomaduro/mock-final-classes": "^1.1.0",
"orchestra/testbench": "^7.7",
"phpunit/phpunit": "^9.5.23",
"spatie/ignition": "^1.4.1"
},
"autoload-dev": {
"psr-4": {
@@ -47,9 +45,15 @@
},
"config": {
"preferred-install": "dist",
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"extra": {
"branch-alias": {
"dev-develop": "6.x-dev"
},
"laravel": {
"providers": [
"NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
@@ -57,9 +61,12 @@
}
},
"scripts": {
"lint": "pint -v",
"test:lint": "pint --test -v",
"test:types": "phpstan analyse --ansi",
"test:unit": "phpunit --colors=always",
"test": [
"@test:lint",
"@test:types",
"@test:unit"
]

View File

@@ -44,12 +44,14 @@ class CollisionServiceProvider extends ServiceProvider
*/
public function register()
{
if ($this->app->runningInConsole() && !$this->app->runningUnitTests()) {
if ($this->app->runningInConsole() && ! $this->app->runningUnitTests()) {
$this->app->bind(ProviderContract::class, function () {
if ($this->app->has(\Facade\IgnitionContracts\SolutionProviderRepository::class)) {
$solutionsRepository = new IgnitionSolutionsRepository(
$this->app->get(\Facade\IgnitionContracts\SolutionProviderRepository::class)
);
// @phpstan-ignore-next-line
if ($this->app->has(\Spatie\Ignition\Contracts\SolutionProviderRepository::class)) {
/** @var \Spatie\Ignition\Contracts\SolutionProviderRepository $solutionProviderRepository */
$solutionProviderRepository = $this->app->get(\Spatie\Ignition\Contracts\SolutionProviderRepository::class);
$solutionsRepository = new IgnitionSolutionsRepository($solutionProviderRepository);
} else {
$solutionsRepository = new NullSolutionsRepository();
}
@@ -60,6 +62,7 @@ class CollisionServiceProvider extends ServiceProvider
return new Provider(null, $handler);
});
/** @var \Illuminate\Contracts\Debug\ExceptionHandler $appExceptionHandler */
$appExceptionHandler = $this->app->make(ExceptionHandlerContract::class);
$this->app->singleton(

View File

@@ -11,6 +11,7 @@ use Illuminate\Console\Command;
use Illuminate\Support\Env;
use Illuminate\Support\Str;
use NunoMaduro\Collision\Adapters\Laravel\Exceptions\RequirementsException;
use NunoMaduro\Collision\Coverage;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
@@ -29,8 +30,11 @@ class TestCommand extends Command
*/
protected $signature = 'test
{--without-tty : Disable output to TTY}
{--coverage : Indicates whether code coverage information should be collected}
{--min= : Indicates the minimum threshold enforcement for code coverage}
{--p|parallel : Indicates if the tests should run in parallel}
{--recreate-databases : Indicates if the test databases should be re-created}
{--drop-databases : Indicates if the test databases should be dropped}
';
/**
@@ -59,17 +63,38 @@ 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.');
$phpunitVersion = \PHPUnit\Runner\Version::id();
if ((int) $phpunitVersion[0] === 1) {
throw new RequirementsException('Running PHPUnit 10.x or Pest 2.x requires Collision 7.x.');
}
if ((int) $phpunitVersion[0] < 9) {
throw new RequirementsException('Running Collision 6.x artisan test command requires at least PHPUnit 9.x.');
}
$laravelVersion = (int) \Illuminate\Foundation\Application::VERSION;
// @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 ($laravelVersion < 9) {
throw new RequirementsException('Running Collision 6.x artisan test command requires at least Laravel 9.x.');
}
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?')) {
if ($this->option('coverage') && ! Coverage::isAvailable()) {
$this->output->writeln(sprintf(
"\n <fg=white;bg=red;options=bold> ERROR </> Code coverage driver not available.%s</>",
Coverage::usingXdebug()
? " Did you set <href=https://xdebug.org/docs/code_coverage#mode>Xdebug's coverage mode</>?"
: ''
));
$this->newLine();
return 1;
}
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;
}
@@ -83,24 +108,26 @@ class TestCommand extends Command
$parallel = $this->option('parallel');
$process = (new Process(array_merge(
// Binary ...
$this->binary(),
// Arguments ...
$parallel ? $this->paratestArguments($options) : $this->phpunitArguments($options)
),
// 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'));
$process->setTty(! $this->option('without-tty'));
} catch (RuntimeException $e) {
$this->output->writeln('Warning: ' . $e->getMessage());
$this->output->writeln('Warning: '.$e->getMessage());
}
$exitCode = 1;
try {
return $process->run(function ($type, $line) {
$exitCode = $process->run(function ($type, $line) {
$this->output->write($line);
});
} catch (ProcessSignaledException $e) {
@@ -108,6 +135,28 @@ class TestCommand extends Command
throw $e;
}
}
if ($exitCode === 0 && $this->option('coverage')) {
if (! $this->usingPest() && $this->option('parallel')) {
$this->newLine();
}
$coverage = Coverage::report($this->output);
$exitCode = (int) ($coverage < $this->option('min'));
if ($exitCode === 1) {
$this->output->writeln(sprintf(
"\n <fg=white;bg=red;options=bold> FAIL </> Code coverage below expected:<fg=red;options=bold> %s %%</>. Minimum:<fg=white;options=bold> %s %%</>.",
number_format($coverage, 1),
number_format((float) $this->option('min'), 1)
));
}
}
$this->newLine();
return $exitCode;
}
/**
@@ -117,7 +166,7 @@ class TestCommand extends Command
*/
protected function binary()
{
if (class_exists(\Pest\Laravel\PestServiceProvider::class)) {
if ($this->usingPest()) {
$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'];
@@ -130,11 +179,37 @@ class TestCommand extends Command
return array_merge([PHP_BINARY], $command);
}
/**
* Gets the common arguments of PHPUnit and Pest.
*
* @return array
*/
protected function commonArguments()
{
$arguments = [];
if ($this->option('coverage')) {
$arguments[] = '--coverage-php';
$arguments[] = Coverage::getPath();
}
return $arguments;
}
/**
* Determines if Pest is being used.
*
* @return bool
*/
protected function usingPest()
{
return class_exists(\Pest\Laravel\PestServiceProvider::class);
}
/**
* Get the array of arguments for running PHPUnit.
*
* @param array $options
*
* @param array $options
* @return array
*/
protected function phpunitArguments($options)
@@ -142,37 +217,45 @@ class TestCommand extends Command
$options = array_merge(['--printer=NunoMaduro\\Collision\\Adapters\\Phpunit\\Printer'], $options);
$options = array_values(array_filter($options, function ($option) {
return !Str::startsWith($option, '--env=');
return ! Str::startsWith($option, '--env=')
&& $option != '-q'
&& $option != '--quiet'
&& $option != '--coverage'
&& ! Str::startsWith($option, '--min');
}));
if (!file_exists($file = base_path('phpunit.xml'))) {
if (! file_exists($file = base_path('phpunit.xml'))) {
$file = base_path('phpunit.xml.dist');
}
return array_merge(["--configuration=$file"], $options);
return array_merge($this->commonArguments(), ["--configuration=$file"], $options);
}
/**
* Get the array of arguments for running Paratest.
*
* @param array $options
*
* @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');
return ! Str::startsWith($option, '--env=')
&& $option != '--coverage'
&& $option != '-q'
&& $option != '--quiet'
&& ! Str::startsWith($option, '--min')
&& ! Str::startsWith($option, '-p')
&& ! Str::startsWith($option, '--parallel')
&& ! Str::startsWith($option, '--recreate-databases')
&& ! Str::startsWith($option, '--drop-databases');
}));
if (!file_exists($file = base_path('phpunit.xml'))) {
if (! file_exists($file = base_path('phpunit.xml'))) {
$file = base_path('phpunit.xml.dist');
}
return array_merge([
return array_merge($this->commonArguments(), [
"--configuration=$file",
"--runner=\Illuminate\Testing\ParallelRunner",
], $options);
@@ -196,8 +279,9 @@ class TestCommand extends Command
protected function paratestEnvironmentVariables()
{
return [
'LARAVEL_PARALLEL_TESTING' => 1,
'LARAVEL_PARALLEL_TESTING' => 1,
'LARAVEL_PARALLEL_TESTING_RECREATE_DATABASES' => $this->option('recreate-databases'),
'LARAVEL_PARALLEL_TESTING_DROP_DATABASES' => $this->option('drop-databases'),
];
}
@@ -208,7 +292,7 @@ class TestCommand extends Command
*/
protected function clearEnv()
{
if (!$this->option('env')) {
if (! $this->option('env')) {
$vars = self::getEnvironmentVariables(
// @phpstan-ignore-next-line
$this->laravel->environmentPath(),
@@ -225,9 +309,8 @@ class TestCommand extends Command
}
/**
* @param string $path
* @param string $file
*
* @param string $path
* @param string $file
* @return array
*/
protected static function getEnvironmentVariables($path, $file)
@@ -268,7 +351,7 @@ class TestCommand extends Command
*/
protected function installParallelDependencies()
{
$command = $this->findComposer() . ' require brianium/paratest --dev';
$command = $this->findComposer().' require brianium/paratest --dev';
$process = Process::fromShellCommandline($command, null, null, null, null);
@@ -276,7 +359,7 @@ class TestCommand extends Command
try {
$process->setTty(true);
} catch (RuntimeException $e) {
$this->output->writeln('Warning: ' . $e->getMessage());
$this->output->writeln('Warning: '.$e->getMessage());
}
}
@@ -298,10 +381,10 @@ class TestCommand extends Command
*/
protected function findComposer()
{
$composerPath = getcwd() . '/composer.phar';
$composerPath = getcwd().'/composer.phar';
if (file_exists($composerPath)) {
return '"' . PHP_BINARY . '" ' . $composerPath;
return '"'.PHP_BINARY.'" '.$composerPath;
}
return 'composer';

View File

@@ -34,7 +34,7 @@ final class ExceptionHandler implements ExceptionHandlerContract
*/
public function __construct(Container $container, ExceptionHandlerContract $appExceptionHandler)
{
$this->container = $container;
$this->container = $container;
$this->appExceptionHandler = $appExceptionHandler;
}
@@ -62,8 +62,10 @@ final class ExceptionHandler implements ExceptionHandlerContract
if ($e instanceof SymfonyConsoleExceptionInterface) {
$this->appExceptionHandler->renderForConsole($output, $e);
} else {
$handler = $this->container->make(ProviderContract::class)
->register()
/** @var \NunoMaduro\Collision\Contracts\Provider $provider */
$provider = $this->container->make(ProviderContract::class);
$handler = $provider->register()
->getHandler()
->setOutput($output);

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace NunoMaduro\Collision\Adapters\Laravel;
use Facade\IgnitionContracts\SolutionProviderRepository;
use NunoMaduro\Collision\Contracts\SolutionsRepository;
use Spatie\Ignition\Contracts\SolutionProviderRepository;
use Throwable;
/**
@@ -16,7 +16,7 @@ final class IgnitionSolutionsRepository implements SolutionsRepository
/**
* Holds an instance of ignition solutions provider repository.
*
* @var \Facade\IgnitionContracts\SolutionProviderRepository
* @var \Spatie\Ignition\Contracts\SolutionProviderRepository
*/
protected $solutionProviderRepository;

View File

@@ -25,15 +25,15 @@ final class ConfigureIO
{
/**
* Configures both given input and output with
* options from the enviroment.
* options from the environment.
*
* @throws \ReflectionException
*/
public static function of(InputInterface $input, Output $output): void
{
$application = new Application();
$reflector = new ReflectionObject($application);
$method = $reflector->getMethod('configureIO');
$reflector = new ReflectionObject($application);
$method = $reflector->getMethod('configureIO');
$method->setAccessible(true);
$method->invoke($application, $input, $output);
}

View File

@@ -54,7 +54,7 @@ final class Printer implements \PHPUnit\TextUI\ResultPrinter
/**
* Creates a new instance of the listener.
*
* @param ConsoleOutput $output
* @param ConsoleOutput $output
*
* @throws \ReflectionException
*/
@@ -69,7 +69,8 @@ final class Printer implements \PHPUnit\TextUI\ResultPrinter
ConfigureIO::of(new ArgvInput(), $output);
$this->style = new Style($output);
$dummyTest = new class() extends TestCase {
$dummyTest = new class() extends TestCase
{
};
$this->state = State::from($dummyTest);
@@ -109,7 +110,7 @@ final class Printer implements \PHPUnit\TextUI\ResultPrinter
$reflector = new ReflectionObject($error);
if ($reflector->hasProperty('message')) {
$message = trim((string) preg_replace("/\r|\n/", "\n ", $error->getMessage()));
$message = trim((string) preg_replace("/\r|\n/", "\n ", $error->getMessage()));
$property = $reflector->getProperty('message');
$property->setAccessible(true);
$property->setValue($error, $message);
@@ -188,14 +189,14 @@ final class Printer implements \PHPUnit\TextUI\ResultPrinter
{
$testCase = $this->testCaseFromTest($testCase);
if (!$this->state->existsInTestCase($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()) {
&& ! $testCase->getTestResultObject()->isStrictAboutOutputDuringTests()
&& ! $testCase->hasExpectationOnOutput()) {
$this->style->write($testCase->getActualOutput());
}
}
@@ -217,7 +218,7 @@ final class Printer implements \PHPUnit\TextUI\ResultPrinter
*/
private function testCaseFromTest(Test $test): TestCase
{
if (!$test instanceof TestCase) {
if (! $test instanceof TestCase) {
throw new ShouldNotHappen();
}

View File

@@ -75,7 +75,7 @@ final class State
*/
public function add(TestResult $test): void
{
$this->testCaseTests[] = $test;
$this->testCaseTests[] = $test;
$this->toBePrintedCaseTests[] = $test;
$this->suiteTests[] = $test;

View File

@@ -29,7 +29,7 @@ final class Style
*/
public function __construct(ConsoleOutputInterface $output)
{
if (!$output instanceof ConsoleOutput) {
if (! $output instanceof ConsoleOutput) {
throw new ShouldNotHappen();
}
@@ -58,7 +58,7 @@ final class Style
return;
}
if (!$state->headerPrinted) {
if (! $state->headerPrinted) {
$this->output->writeln($this->titleLineFrom(
$state->getTestCaseTitle() === 'FAIL' ? 'white' : 'black',
$state->getTestCaseTitleColor(),
@@ -92,12 +92,12 @@ final class Style
return $testResult->type === TestResult::FAIL;
});
if (!$onFailure) {
if (! $onFailure) {
$this->output->writeln(['', " \e[2m---\e[22m", '']);
}
array_map(function (TestResult $testResult) use ($onFailure) {
if (!$onFailure) {
if (! $onFailure) {
$this->output->write(sprintf(
' <fg=red;options=bold>• %s </>> <fg=red;options=bold>%s</>',
$testResult->testCaseName,
@@ -105,7 +105,7 @@ final class Style
));
}
if (!$testResult->throwable instanceof Throwable) {
if (! $testResult->throwable instanceof Throwable) {
throw new ShouldNotHappen();
}
@@ -121,7 +121,7 @@ final class Style
$types = [TestResult::FAIL, TestResult::WARN, TestResult::RISKY, TestResult::INCOMPLETE, TestResult::SKIPPED, TestResult::PASS];
foreach ($types as $type) {
if (($countTests = $state->countTestsInTestSuiteBy($type)) !== 0) {
$color = TestResult::makeColor($type);
$color = TestResult::makeColor($type);
$tests[] = "<fg=$color;options=bold>$countTests $type</>";
}
}
@@ -131,7 +131,7 @@ final class Style
$tests[] = "\e[2m$pending pending\e[22m";
}
if (!empty($tests)) {
if (! empty($tests)) {
$this->output->write([
"\n",
sprintf(
@@ -144,12 +144,12 @@ final class Style
if ($timer !== null) {
$timeElapsed = number_format($timer->result(), 2, '.', '');
$this->output->writeln([
'',
sprintf(
' <fg=white;options=bold>Time: </><fg=default>%ss</>',
$timeElapsed
),
]
'',
sprintf(
' <fg=white;options=bold>Time: </><fg=default>%ss</>',
$timeElapsed
),
]
);
}
@@ -178,8 +178,11 @@ final class Style
}
$writer->ignoreFilesIn([
'/vendor\/bin\/pest/',
'/bin\/pest/',
'/vendor\/pestphp\/pest/',
'/vendor\/phpspec\/prophecy-phpunit/',
'/vendor\/phpspec\/prophecy/',
'/vendor\/phpunit\/phpunit\/src/',
'/vendor\/mockery\/mockery/',
'/vendor\/laravel\/dusk/',
@@ -196,6 +199,7 @@ final class Style
'/bin\/phpunit/',
'/vendor\/coduo\/php-matcher\/src\/PHPUnit/',
'/vendor\/sulu\/sulu\/src\/Sulu\/Bundle\/TestBundle\/Testing/',
'/vendor\/webmozart\/assert/',
]);
if ($throwable instanceof ExceptionWrapper && $throwable->getOriginalException() !== null) {
@@ -207,20 +211,20 @@ final class Style
$writer->write($inspector);
if ($throwable instanceof ExpectationFailedException && $comparisionFailure = $throwable->getComparisonFailure()) {
$diff = $comparisionFailure->getDiff();
$diff = $comparisionFailure->getDiff();
$lines = explode(PHP_EOL, $diff);
$diff = '';
$diff = '';
foreach ($lines as $line) {
if (0 === strpos($line, '-')) {
$line = '<fg=red>' . $line . '</>';
$line = '<fg=red>'.$line.'</>';
} elseif (0 === strpos($line, '+')) {
$line = '<fg=green>' . $line . '</>';
$line = '<fg=green>'.$line.'</>';
}
$diff .= $line . PHP_EOL;
$diff .= $line.PHP_EOL;
}
$diff = trim((string) preg_replace("/\r|\n/", "\n ", $diff));
$diff = trim((string) preg_replace("/\r|\n/", "\n ", $diff));
$this->output->write(" $diff");
}
@@ -247,7 +251,7 @@ final class Style
*/
private function testLineFrom(string $fg, string $icon, string $description, string $warning = null): string
{
if (!empty($warning)) {
if (! empty($warning)) {
$warning = sprintf(
' → %s',
$warning

View File

@@ -13,13 +13,19 @@ use Throwable;
*/
final class TestResult
{
public const FAIL = 'failed';
public const SKIPPED = 'skipped';
public const INCOMPLETE = 'incompleted';
public const RISKY = 'risked';
public const WARN = 'warnings';
public const RUNS = 'pending';
public const PASS = 'passed';
public const FAIL = 'failed';
public const SKIPPED = 'skipped';
public const INCOMPLETE = 'incomplete';
public const RISKY = 'risky';
public const WARN = 'warnings';
public const RUNS = 'pending';
public const PASS = 'passed';
/**
* @readonly
@@ -76,11 +82,11 @@ final class TestResult
private function __construct(string $testCaseName, string $description, string $type, string $icon, string $color, Throwable $throwable = null)
{
$this->testCaseName = $testCaseName;
$this->description = $description;
$this->type = $type;
$this->icon = $icon;
$this->color = $color;
$this->throwable = $throwable;
$this->description = $description;
$this->type = $type;
$this->icon = $icon;
$this->color = $color;
$this->throwable = $throwable;
$asWarning = $this->type === TestResult::WARN
|| $this->type === TestResult::RISKY
@@ -88,7 +94,7 @@ final class TestResult
|| $this->type === TestResult::INCOMPLETE;
if ($throwable instanceof Throwable && $asWarning) {
$this->warning = trim((string) preg_replace("/\r|\n/", ' ', $throwable->getMessage()));
$this->warning = trim((string) preg_replace("/\r|\n/", ' ', $throwable->getMessage()));
}
}

View File

@@ -25,16 +25,16 @@ final class ArgumentFormatter implements ArgumentFormatterContract
foreach ($arguments as $argument) {
switch (true) {
case is_string($argument):
$result[] = '"' . (mb_strlen($argument) > self::MAX_STRING_LENGTH ? mb_substr($argument, 0, self::MAX_STRING_LENGTH) . '...' : $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);
if ($recursive && $associative && count($argument) <= 5) {
$result[] = '[' . $this->format($argument, false) . ']';
$result[] = '['.$this->format($argument, false).']';
}
break;
case is_object($argument):
$class = get_class($argument);
$class = get_class($argument);
$result[] = "Object($class)";
break;
}

View File

@@ -13,6 +13,7 @@ use NunoMaduro\Collision\Exceptions\ShouldNotHappen;
final class ConsoleColor
{
public const FOREGROUND = 38;
public const BACKGROUND = 48;
public const COLOR256_REGEXP = '~^(bg_)?color_(\d{1,3})$~';
@@ -27,52 +28,52 @@ final class ConsoleColor
/** @var array */
private const STYLES = [
'none' => null,
'bold' => '1',
'dark' => '2',
'italic' => '3',
'none' => null,
'bold' => '1',
'dark' => '2',
'italic' => '3',
'underline' => '4',
'blink' => '5',
'reverse' => '7',
'blink' => '5',
'reverse' => '7',
'concealed' => '8',
'default' => '39',
'black' => '30',
'red' => '31',
'green' => '32',
'yellow' => '33',
'blue' => '34',
'magenta' => '35',
'cyan' => '36',
'default' => '39',
'black' => '30',
'red' => '31',
'green' => '32',
'yellow' => '33',
'blue' => '34',
'magenta' => '35',
'cyan' => '36',
'light_gray' => '37',
'dark_gray' => '90',
'light_red' => '91',
'light_green' => '92',
'light_yellow' => '93',
'light_blue' => '94',
'dark_gray' => '90',
'light_red' => '91',
'light_green' => '92',
'light_yellow' => '93',
'light_blue' => '94',
'light_magenta' => '95',
'light_cyan' => '96',
'white' => '97',
'light_cyan' => '96',
'white' => '97',
'bg_default' => '49',
'bg_black' => '40',
'bg_red' => '41',
'bg_green' => '42',
'bg_yellow' => '43',
'bg_blue' => '44',
'bg_magenta' => '45',
'bg_cyan' => '46',
'bg_default' => '49',
'bg_black' => '40',
'bg_red' => '41',
'bg_green' => '42',
'bg_yellow' => '43',
'bg_blue' => '44',
'bg_magenta' => '45',
'bg_cyan' => '46',
'bg_light_gray' => '47',
'bg_dark_gray' => '100',
'bg_light_red' => '101',
'bg_light_green' => '102',
'bg_light_yellow' => '103',
'bg_light_blue' => '104',
'bg_dark_gray' => '100',
'bg_light_red' => '101',
'bg_light_green' => '102',
'bg_light_yellow' => '103',
'bg_light_blue' => '104',
'bg_light_magenta' => '105',
'bg_light_cyan' => '106',
'bg_white' => '107',
'bg_light_cyan' => '106',
'bg_white' => '107',
];
/** @var array */
@@ -84,9 +85,8 @@ final class ConsoleColor
}
/**
* @param string|array $style
* @param string $text
*
* @param string|array $style
* @param string $text
* @return string
*
* @throws InvalidStyleException
@@ -94,14 +94,14 @@ final class ConsoleColor
*/
public function apply($style, $text)
{
if (!$this->isStyleForced() && !$this->isSupported()) {
if (! $this->isStyleForced() && ! $this->isSupported()) {
return $text;
}
if (is_string($style)) {
$style = [$style];
}
if (!is_array($style)) {
if (! is_array($style)) {
throw new \InvalidArgumentException('Style must be string or array.');
}
@@ -125,11 +125,11 @@ final class ConsoleColor
return $text;
}
return $this->escSequence(implode(';', $sequences)) . $text . $this->escSequence(self::RESET_STYLE);
return $this->escSequence(implode(';', $sequences)).$text.$this->escSequence(self::RESET_STYLE);
}
/**
* @param bool $forceStyle
* @param bool $forceStyle
*/
public function setForceStyle($forceStyle)
{
@@ -153,20 +153,20 @@ final class ConsoleColor
}
/**
* @param string $name
* @param array|string $styles
* @param string $name
* @param array|string $styles
*/
public function addTheme($name, $styles)
{
if (is_string($styles)) {
$styles = [$styles];
}
if (!is_array($styles)) {
if (! is_array($styles)) {
throw new \InvalidArgumentException('Style must be string or array.');
}
foreach ($styles as $style) {
if (!$this->isValidStyle($style)) {
if (! $this->isValidStyle($style)) {
throw new InvalidStyleException($style);
}
}
@@ -183,8 +183,7 @@ final class ConsoleColor
}
/**
* @param string $name
*
* @param string $name
* @return bool
*/
public function hasTheme($name)
@@ -193,7 +192,7 @@ final class ConsoleColor
}
/**
* @param string $name
* @param string $name
*/
public function removeTheme($name)
{
@@ -238,8 +237,7 @@ final class ConsoleColor
}
/**
* @param string $name
*
* @param string $name
* @return string[]
*/
private function themeSequence($name)
@@ -253,8 +251,7 @@ final class ConsoleColor
}
/**
* @param string $style
*
* @param string $style
* @return string
*/
private function styleSequence($style)
@@ -263,21 +260,20 @@ final class ConsoleColor
return self::STYLES[$style];
}
if (!$this->are256ColorsSupported()) {
if (! $this->are256ColorsSupported()) {
return null;
}
preg_match(self::COLOR256_REGEXP, $style, $matches);
$type = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND;
$type = $matches[1] === 'bg_' ? self::BACKGROUND : self::FOREGROUND;
$value = $matches[2];
return "$type;5;$value";
}
/**
* @param string $style
*
* @param string $style
* @return bool
*/
private function isValidStyle($style)
@@ -286,8 +282,7 @@ final class ConsoleColor
}
/**
* @param string|int $value
*
* @param string|int $value
* @return string
*/
private function escSequence($value)

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace NunoMaduro\Collision\Contracts;
use Facade\IgnitionContracts\Solution;
use Spatie\Ignition\Contracts\Solution;
use Throwable;
/**

View File

@@ -25,8 +25,7 @@ interface Writer
* Ignores traces where the file string matches one
* of the provided regex expressions.
*
* @param string[] $ignore the regex expressions
*
* @param string[] $ignore the regex expressions
* @return \NunoMaduro\Collision\Contracts\Writer
*/
public function ignoreFilesIn(array $ignore): Writer;

View File

@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace NunoMaduro\Collision;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\Directory;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\Environment\Runtime;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;
/**
* @internal
*/
final class Coverage
{
/**
* Returns the coverage path.
*/
public static function getPath(): string
{
return implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__),
'.temp',
'coverage',
]);
}
/**
* Runs true there is any code coverage driver available.
*/
public static function isAvailable(): bool
{
if (! (new Runtime())->canCollectCodeCoverage()) {
return false;
}
if (static::usingXdebug()) {
$mode = getenv('XDEBUG_MODE') ?: ini_get('xdebug.mode');
return $mode && in_array('coverage', explode(',', $mode), true);
}
return true;
}
/**
* If the user is using Xdebug.
*/
public static function usingXdebug(): bool
{
return (new Runtime())->hasXdebug();
}
/**
* Reports the code coverage report to the
* console and returns the result in float.
*/
public static function report(OutputInterface $output): float
{
if (! file_exists($reportPath = self::getPath())) {
if (self::usingXdebug()) {
$output->writeln(
" <fg=black;bg=yellow;options=bold> WARN </> Unable to get coverage using Xdebug. Did you set <href=https://xdebug.org/docs/code_coverage#mode>Xdebug's coverage mode</>?</>",
);
return 0.0;
}
$output->writeln(
' <fg=black;bg=yellow;options=bold> WARN </> No coverage driver detected.</>',
);
return 0.0;
}
/** @var CodeCoverage $codeCoverage */
$codeCoverage = require $reportPath;
unlink($reportPath);
$totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines();
$totalWidth = (new Terminal())->getWidth();
$dottedLineLength = $totalWidth;
/** @var Directory<File|Directory> $report */
$report = $codeCoverage->getReport();
foreach ($report->getIterator() as $file) {
if (! $file instanceof File) {
continue;
}
$dirname = dirname($file->id());
$basename = basename($file->id(), '.php');
$name = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
$dirname,
$basename,
]);
$rawName = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
$dirname,
$basename,
]);
$linesExecutedTakenSize = 0;
if ($file->percentageOfExecutedLines()->asString() != '0.00%') {
$linesExecutedTakenSize = strlen($uncoveredLines = trim(implode(', ', self::getMissingCoverage($file)))) + 1;
$name .= sprintf(' <fg=red>%s</>', $uncoveredLines);
}
$percentage = $file->numberOfExecutableLines() === 0
? '100.0'
: number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', '');
$takenSize = strlen($rawName.$percentage) + 8 + $linesExecutedTakenSize; // adding 3 space and percent sign
$percentage = sprintf(
'<fg=%s%s>%s</>',
$percentage === '100.0' ? 'green' : ($percentage === '0.0' ? 'red' : 'yellow'),
$percentage === '100.0' ? ';options=bold' : '',
$percentage
);
$output->writeln(sprintf(
' <fg=white>%s</> <fg=#6C7280>%s</> %s <fg=#6C7280>%%</>',
$name,
str_repeat('.', max($dottedLineLength - $takenSize, 1)),
$percentage
));
}
$output->writeln('');
$rawName = 'Total Coverage';
$takenSize = strlen($rawName.$totalCoverage->asString()) + 6;
$output->writeln(sprintf(
' <fg=white;options=bold>%s</> <fg=#6C7280>%s</> %s <fg=#6C7280>%%</>',
$rawName,
str_repeat('.', max($dottedLineLength - $takenSize, 1)),
number_format($totalCoverage->asFloat(), 1, '.', '')
));
return $totalCoverage->asFloat();
}
/**
* Generates an array of missing coverage on the following format:.
*
* ```
* ['11', '20..25', '50', '60..80'];
* ```
*
* @param File $file
* @return array<int, string>
*/
public static function getMissingCoverage($file): array
{
$shouldBeNewLine = true;
$eachLine = function (array $array, array $tests, int $line) use (&$shouldBeNewLine): array {
if (count($tests) > 0) {
$shouldBeNewLine = true;
return $array;
}
if ($shouldBeNewLine) {
$array[] = (string) $line;
$shouldBeNewLine = false;
return $array;
}
$lastKey = count($array) - 1;
if (array_key_exists($lastKey, $array) && str_contains($array[$lastKey], '..')) {
[$from] = explode('..', $array[$lastKey]);
$array[$lastKey] = $line > $from ? sprintf('%s..%s', $from, $line) : sprintf('%s..%s', $line, $from);
return $array;
}
$array[$lastKey] = sprintf('%s..%s', $array[$lastKey], $line);
return $array;
};
$array = [];
foreach (array_filter($file->lineCoverageData(), 'is_array') as $line => $tests) {
$array = $eachLine($array, $tests, $line);
}
return $array;
}
}

View File

@@ -11,58 +11,75 @@ use NunoMaduro\Collision\Contracts\Highlighter as 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';
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 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;
private const MARKED_LINE_NUMBER = 'marked_line';
private const WIDTH = 3;
/**
* Holds the theme.
*
* @var array
*/
private const THEME = [
self::TOKEN_STRING => ['light_gray'],
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::TOKEN_HTML => ['blue', 'bold'],
self::ACTUAL_LINE_MARK => ['red', 'bold'],
self::LINE_NUMBER => ['dark_gray'],
self::MARKED_LINE_NUMBER => ['italic', 'bold'],
self::ACTUAL_LINE_MARK => ['red', 'bold'],
self::LINE_NUMBER => ['dark_gray'],
self::MARKED_LINE_NUMBER => ['italic', 'bold'],
self::LINE_NUMBER_DIVIDER => ['dark_gray'],
];
/** @var ConsoleColor */
private $color;
/** @var array */
private const DEFAULT_THEME = [
self::TOKEN_STRING => 'red',
self::TOKEN_STRING => 'red',
self::TOKEN_COMMENT => 'yellow',
self::TOKEN_KEYWORD => 'green',
self::TOKEN_DEFAULT => 'default',
self::TOKEN_HTML => 'cyan',
self::TOKEN_HTML => 'cyan',
self::ACTUAL_LINE_MARK => 'dark_gray',
self::LINE_NUMBER => 'dark_gray',
self::MARKED_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
*/
@@ -76,7 +93,7 @@ final class Highlighter implements HighlighterContract
$this->color = $color ?: new ConsoleColor();
foreach (self::DEFAULT_THEME as $name => $styles) {
if (!$this->color->hasTheme($name)) {
if (! $this->color->hasTheme($name)) {
$this->color->addTheme($name, $styles);
}
}
@@ -84,9 +101,9 @@ final class Highlighter implements HighlighterContract
foreach (self::THEME as $name => $styles) {
$this->color->addTheme($name, $styles);
}
if (!$UTF8) {
if (! $UTF8) {
$this->delimiter = self::DELIMITER;
$this->arrow = self::ARROW_SYMBOL;
$this->arrow = self::ARROW_SYMBOL;
}
$this->delimiter .= ' ';
}
@@ -100,18 +117,18 @@ final class Highlighter implements HighlighterContract
}
/**
* @param string $source
* @param int $lineNumber
* @param int $linesBefore
* @param int $linesAfter
* @param string $source
* @param int $lineNumber
* @param int $linesBefore
* @param int $linesAfter
*/
public function getCodeSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2): string
{
$tokenLines = $this->getHighlightedLines($source);
$offset = $lineNumber - $linesBefore - 1;
$offset = max($offset, 0);
$length = $linesAfter + $linesBefore + 1;
$offset = $lineNumber - $linesBefore - 1;
$offset = max($offset, 0);
$length = $linesAfter + $linesBefore + 1;
$tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true);
$lines = $this->colorLines($tokenLines);
@@ -120,7 +137,7 @@ final class Highlighter implements HighlighterContract
}
/**
* @param string $source
* @param string $source
*/
private function getHighlightedLines($source): array
{
@@ -131,15 +148,15 @@ final class Highlighter implements HighlighterContract
}
/**
* @param string $source
* @param string $source
*/
private function tokenize($source): array
{
$tokens = token_get_all($source);
$output = [];
$output = [];
$currentType = null;
$buffer = '';
$buffer = '';
foreach ($tokens as $token) {
if (is_array($token)) {
@@ -192,8 +209,8 @@ final class Highlighter implements HighlighterContract
}
if ($currentType !== $newType) {
$output[] = [$currentType, $buffer];
$buffer = '';
$output[] = [$currentType, $buffer];
$buffer = '';
$currentType = $newType;
}
@@ -216,7 +233,7 @@ final class Highlighter implements HighlighterContract
foreach (explode("\n", $token[1]) as $count => $tokenLine) {
if ($count > 0) {
$lines[] = $line;
$line = [];
$line = [];
}
if ($tokenLine === '') {
@@ -252,14 +269,14 @@ final class Highlighter implements HighlighterContract
}
/**
* @param int|null $markLine
* @param int|null $markLine
*/
private function lineNumbers(array $lines, $markLine = null): string
{
$lineStrlen = strlen((string) (array_key_last($lines) + 1));
$lineStrlen = $lineStrlen < self::WIDTH ? self::WIDTH : $lineStrlen;
$snippet = '';
$mark = ' ' . $this->arrow . ' ';
$snippet = '';
$mark = ' '.$this->arrow.' ';
foreach ($lines as $i => $line) {
$coloredLineNumber = $this->coloredLineNumber(self::LINE_NUMBER, $i, $lineStrlen);
@@ -281,16 +298,16 @@ final class Highlighter implements HighlighterContract
$snippet .=
$this->color->apply(self::LINE_NUMBER_DIVIDER, $this->delimiter);
$snippet .= $line . PHP_EOL;
$snippet .= $line.PHP_EOL;
}
return $snippet;
}
/**
* @param string $style
* @param int $i
* @param int $lineStrlen
* @param string $style
* @param int $i
* @param int $lineStrlen
*/
private function coloredLineNumber($style, $i, $lineStrlen): string
{

View File

@@ -35,7 +35,7 @@ final class Provider implements ProviderContract
*/
public function __construct(RunInterface $run = null, HandlerContract $handler = null)
{
$this->run = $run ?: new Run();
$this->run = $run ?: new Run();
$this->handler = $handler ?: new Handler();
}

View File

@@ -95,9 +95,9 @@ final class Writer implements WriterContract
HighlighterContract $highlighter = null
) {
$this->solutionsRepository = $solutionsRepository ?: new NullSolutionsRepository();
$this->output = $output ?: new ConsoleOutput();
$this->argumentFormatter = $argumentFormatter ?: new ArgumentFormatter();
$this->highlighter = $highlighter ?: new Highlighter();
$this->output = $output ?: new ConsoleOutput();
$this->argumentFormatter = $argumentFormatter ?: new ArgumentFormatter();
$this->highlighter = $highlighter ?: new Highlighter();
}
/**
@@ -115,16 +115,16 @@ final class Writer implements WriterContract
if ($this->showEditor
&& $editorFrame !== null
&& !$exception instanceof RenderlessEditor
&& ! $exception instanceof RenderlessEditor
) {
$this->renderEditor($editorFrame);
}
$this->renderSolution($inspector);
if ($this->showTrace && !empty($frames) && !$exception instanceof RenderlessTrace) {
if ($this->showTrace && ! empty($frames) && ! $exception instanceof RenderlessTrace) {
$this->renderTrace($frames);
} elseif (!$exception instanceof RenderlessEditor) {
} elseif (! $exception instanceof RenderlessEditor) {
$this->output->writeln('');
}
}
@@ -222,8 +222,8 @@ final class Writer implements WriterContract
protected function renderTitleAndDescription(Inspector $inspector): WriterContract
{
$exception = $inspector->getException();
$message = rtrim($exception->getMessage());
$class = $inspector->getExceptionName();
$message = rtrim($exception->getMessage());
$class = $inspector->getExceptionName();
if ($this->showTitle) {
$this->render("<bg=red;options=bold> $class </>");
@@ -244,19 +244,19 @@ final class Writer implements WriterContract
$solutions = $this->solutionsRepository->getFromThrowable($throwable);
foreach ($solutions as $solution) {
/** @var \Facade\IgnitionContracts\Solution $solution */
$title = $solution->getSolutionTitle();
/** @var \Spatie\Ignition\Contracts\Solution $solution */
$title = $solution->getSolutionTitle();
$description = $solution->getSolutionDescription();
$links = $solution->getDocumentationLinks();
$links = $solution->getDocumentationLinks();
$description = trim((string) preg_replace("/\n/", "\n ", $description));
$this->render(sprintf(
'<fg=blue;options=bold></><fg=default;options=bold>%s</>: %s %s',
'<fg=cyan;options=bold>i</> <fg=default;options=bold>%s</>: %s %s',
rtrim($title, '.'),
$description,
implode(', ', array_map(function (string $link) {
return sprintf("\n <fg=blue>%s</>", $link);
return sprintf("\n <fg=gray>%s</>", $link);
}, $links))
));
}
@@ -275,7 +275,7 @@ final class Writer implements WriterContract
// 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 . '</>');
$this->render('at <fg=green>'.$file.'</>'.':<fg=green>'.$line.'</>');
$content = $this->highlighter->highlight((string) $frame->getFileContents(), (int) $frame->getLine());
@@ -291,10 +291,11 @@ final class Writer implements WriterContract
protected function renderTrace(array $frames): WriterContract
{
$vendorFrames = 0;
$userFrames = 0;
$userFrames = 0;
foreach ($frames as $i => $frame) {
if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE && strpos($frame->getFile(), '/vendor/') !== false) {
$vendorFrames++;
continue;
}
@@ -304,12 +305,12 @@ final class Writer implements WriterContract
$userFrames++;
$file = $this->getFileRelativePath($frame->getFile());
$line = $frame->getLine();
$class = empty($frame->getClass()) ? '' : $frame->getClass() . '::';
$file = $this->getFileRelativePath($frame->getFile());
$line = $frame->getLine();
$class = empty($frame->getClass()) ? '' : $frame->getClass().'::';
$function = $frame->getFunction();
$args = $this->argumentFormatter->format($frame->getArgs());
$pos = str_pad((string) ((int) $i + 1), 4, ' ');
$args = $this->argumentFormatter->format($frame->getArgs());
$pos = str_pad((string) ((int) $i + 1), 4, ' ');
if ($vendorFrames > 0) {
$this->output->write(
@@ -319,7 +320,7 @@ final class Writer implements WriterContract
}
$this->render("<fg=yellow>$pos</><fg=default;options=bold>$file</>:<fg=default;options=bold>$line</>");
$this->render("<fg=white> $class$function($args)</>", false);
$this->render("<fg=gray> $class$function($args)</>", false);
}
return $this;
@@ -348,7 +349,7 @@ final class Writer implements WriterContract
{
$cwd = (string) getcwd();
if (!empty($cwd)) {
if (! empty($cwd)) {
return str_replace("$cwd/", '', $filePath);
}