🔧
This commit is contained in:
4
vendor/spatie/ignition/README.md
vendored
4
vendor/spatie/ignition/README.md
vendored
@@ -56,6 +56,8 @@ For Symfony apps, go to [symfony-ignition-bundle](https://github.com/spatie/symf
|
||||
|
||||
For Drupal 10+ websites, use the [Ignition module](https://www.drupal.org/project/ignition).
|
||||
|
||||
For OpenMage websites, use the [Ignition module](https://github.com/empiricompany/openmage_ignition).
|
||||
|
||||
For all other PHP projects, install the package via composer:
|
||||
|
||||
```bash
|
||||
@@ -87,7 +89,7 @@ By default, Ignition uses a nice white based theme. If this is too bright for yo
|
||||
|
||||
```php
|
||||
\Spatie\Ignition\Ignition::make()
|
||||
->useDarkMode()
|
||||
->setTheme('dark')
|
||||
->register();
|
||||
```
|
||||
|
||||
|
||||
8
vendor/spatie/ignition/composer.json
vendored
8
vendor/spatie/ignition/composer.json
vendored
@@ -20,13 +20,13 @@
|
||||
"php": "^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"spatie/backtrace": "^1.5.3",
|
||||
"spatie/flare-client-php": "^1.4.0",
|
||||
"spatie/flare-client-php": "^1.7",
|
||||
"symfony/console": "^5.4|^6.0|^7.0",
|
||||
"symfony/var-dumper": "^5.4|^6.0|^7.0"
|
||||
"symfony/var-dumper": "^5.4|^6.0|^7.0",
|
||||
"spatie/error-solutions": "^1.0"
|
||||
},
|
||||
"require-dev" : {
|
||||
"illuminate/cache" : "^9.52|^10.0|^11.0",
|
||||
"illuminate/cache" : "^9.52|^10.0|^11.0|^12.0",
|
||||
"mockery/mockery" : "^1.4",
|
||||
"pestphp/pest" : "^1.20|^2.0",
|
||||
"phpstan/extension-installer" : "^1.1",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -203,6 +203,10 @@ class IgnitionConfig implements Arrayable
|
||||
'label' => 'VS Codium',
|
||||
'url' => 'vscodium://file/%path:%line',
|
||||
],
|
||||
'cursor' => [
|
||||
'label' => 'Cursor',
|
||||
'url' => 'cursor://file/%path:%line',
|
||||
],
|
||||
'atom' => [
|
||||
'label' => 'Atom',
|
||||
'url' => 'atom://core/open/file?filename=%path&line=%line',
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Contracts;
|
||||
|
||||
class BaseSolution implements Solution
|
||||
{
|
||||
protected string $title;
|
||||
|
||||
protected string $description = '';
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected array $links = [];
|
||||
|
||||
public static function create(string $title = ''): static
|
||||
{
|
||||
// It's important to keep the return type as static because
|
||||
// the old Facade Ignition contracts extend from this method.
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
return new static($title);
|
||||
}
|
||||
|
||||
public function __construct(string $title = '')
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function getSolutionTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setSolutionTitle(string $title): self
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSolutionDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setSolutionDescription(string $description): self
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function getDocumentationLinks(): array
|
||||
{
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
/** @param array<string, string> $links */
|
||||
public function setDocumentationLinks(array $links): self
|
||||
{
|
||||
$this->links = $links;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Contracts;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Interface used for SolutionProviders.
|
||||
*/
|
||||
interface HasSolutionsForThrowable
|
||||
{
|
||||
public function canSolve(Throwable $throwable): bool;
|
||||
|
||||
/** @return array<int, \Spatie\Ignition\Contracts\Solution> */
|
||||
public function getSolutions(Throwable $throwable): array;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Contracts;
|
||||
|
||||
/**
|
||||
* Interface to be used on exceptions that provide their own solution.
|
||||
*/
|
||||
interface ProvidesSolution
|
||||
{
|
||||
public function getSolution(): Solution;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Contracts;
|
||||
|
||||
interface RunnableSolution extends Solution
|
||||
{
|
||||
public function getSolutionActionDescription(): string;
|
||||
|
||||
public function getRunButtonText(): string;
|
||||
|
||||
/** @param array<string, mixed> $parameters */
|
||||
public function run(array $parameters = []): void;
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function getRunParameters(): array;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Contracts;
|
||||
|
||||
interface Solution
|
||||
{
|
||||
public function getSolutionTitle(): string;
|
||||
|
||||
public function getSolutionDescription(): string;
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function getDocumentationLinks(): array;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Contracts;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface SolutionProviderRepository
|
||||
{
|
||||
/**
|
||||
* @param class-string<HasSolutionsForThrowable>|HasSolutionsForThrowable $solutionProvider
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function registerSolutionProvider(string $solutionProvider): self;
|
||||
|
||||
/**
|
||||
* @param array<class-string<HasSolutionsForThrowable>|HasSolutionsForThrowable> $solutionProviders
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function registerSolutionProviders(array $solutionProviders): self;
|
||||
|
||||
/**
|
||||
* @param Throwable $throwable
|
||||
*
|
||||
* @return array<int, Solution>
|
||||
*/
|
||||
public function getSolutionsForThrowable(Throwable $throwable): array;
|
||||
|
||||
/**
|
||||
* @param class-string<Solution> $solutionClass
|
||||
*
|
||||
* @return null|Solution
|
||||
*/
|
||||
public function getSolutionForClass(string $solutionClass): ?Solution;
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace Spatie\Ignition\ErrorPage;
|
||||
|
||||
use Spatie\ErrorSolutions\Contracts\Solution;
|
||||
use Spatie\ErrorSolutions\Solutions\SolutionTransformer;
|
||||
use Spatie\FlareClient\Report;
|
||||
use Spatie\FlareClient\Truncation\ReportTrimmer;
|
||||
use Spatie\Ignition\Config\IgnitionConfig;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\Ignition\Solutions\SolutionTransformer;
|
||||
use Throwable;
|
||||
|
||||
class ErrorPageViewModel
|
||||
|
||||
35
vendor/spatie/ignition/src/Ignition.php
vendored
35
vendor/spatie/ignition/src/Ignition.php
vendored
@@ -4,6 +4,12 @@ namespace Spatie\Ignition;
|
||||
|
||||
use ArrayObject;
|
||||
use ErrorException;
|
||||
use Spatie\ErrorSolutions\Contracts\HasSolutionsForThrowable;
|
||||
use Spatie\ErrorSolutions\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
|
||||
use Spatie\ErrorSolutions\SolutionProviderRepository;
|
||||
use Spatie\ErrorSolutions\SolutionProviders\BadMethodCallSolutionProvider;
|
||||
use Spatie\ErrorSolutions\SolutionProviders\MergeConflictSolutionProvider;
|
||||
use Spatie\ErrorSolutions\SolutionProviders\UndefinedPropertySolutionProvider;
|
||||
use Spatie\FlareClient\Context\BaseContextProviderDetector;
|
||||
use Spatie\FlareClient\Context\ContextProviderDetector;
|
||||
use Spatie\FlareClient\Enums\MessageLevels;
|
||||
@@ -13,14 +19,8 @@ use Spatie\FlareClient\FlareMiddleware\AddSolutions;
|
||||
use Spatie\FlareClient\FlareMiddleware\FlareMiddleware;
|
||||
use Spatie\FlareClient\Report;
|
||||
use Spatie\Ignition\Config\IgnitionConfig;
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Spatie\Ignition\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
|
||||
use Spatie\Ignition\ErrorPage\ErrorPageViewModel;
|
||||
use Spatie\Ignition\ErrorPage\Renderer;
|
||||
use Spatie\Ignition\Solutions\SolutionProviders\BadMethodCallSolutionProvider;
|
||||
use Spatie\Ignition\Solutions\SolutionProviders\MergeConflictSolutionProvider;
|
||||
use Spatie\Ignition\Solutions\SolutionProviders\SolutionProviderRepository;
|
||||
use Spatie\Ignition\Solutions\SolutionProviders\UndefinedPropertySolutionProvider;
|
||||
use Throwable;
|
||||
|
||||
class Ignition
|
||||
@@ -58,9 +58,10 @@ class Ignition
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->flare = Flare::make();
|
||||
public function __construct(
|
||||
?Flare $flare = null,
|
||||
) {
|
||||
$this->flare = $flare ?? Flare::make();
|
||||
|
||||
$this->ignitionConfig = IgnitionConfig::loadFromConfigFile();
|
||||
|
||||
@@ -237,14 +238,14 @@ class Ignition
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function register(): self
|
||||
public function register(?int $errorLevels = null): self
|
||||
{
|
||||
error_reporting(-1);
|
||||
error_reporting($errorLevels ?? -1);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
set_error_handler([$this, 'renderError']);
|
||||
$errorLevels
|
||||
? set_error_handler([$this, 'renderError'], $errorLevels)
|
||||
: set_error_handler([$this, 'renderError']);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
set_exception_handler([$this, 'handleException']);
|
||||
|
||||
return $this;
|
||||
@@ -267,6 +268,12 @@ class Ignition
|
||||
int $line = 0,
|
||||
array $context = []
|
||||
): void {
|
||||
if (error_reporting() === (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE)) {
|
||||
// This happens when PHP version is >=8 and we caught an error that was suppressed with the "@" operator
|
||||
// See the first warning box in https://www.php.net/manual/en/language.operators.errorcontrol.php
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ErrorException($message, 0, $level, $file, $line);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\OpenAi;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
class DummyCache implements CacheInterface
|
||||
{
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMultiple(iterable $keys, mixed $default = null): iterable
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteMultiple(iterable $keys): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\OpenAi;
|
||||
|
||||
class OpenAiPromptViewModel
|
||||
{
|
||||
public function __construct(
|
||||
protected string $file,
|
||||
protected string $exceptionMessage,
|
||||
protected string $exceptionClass,
|
||||
protected string $snippet,
|
||||
protected string $line,
|
||||
protected string|null $applicationType = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function file(): string
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function line(): string
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
public function snippet(): string
|
||||
{
|
||||
return $this->snippet;
|
||||
}
|
||||
|
||||
public function exceptionMessage(): string
|
||||
{
|
||||
return $this->exceptionMessage;
|
||||
}
|
||||
|
||||
public function exceptionClass(): string
|
||||
{
|
||||
return $this->exceptionClass;
|
||||
}
|
||||
|
||||
public function applicationType(): string|null
|
||||
{
|
||||
return $this->applicationType;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\OpenAi;
|
||||
|
||||
use OpenAI;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Spatie\Backtrace\Backtrace;
|
||||
use Spatie\Backtrace\Frame;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\Ignition\ErrorPage\Renderer;
|
||||
use Spatie\Ignition\Ignition;
|
||||
use Throwable;
|
||||
|
||||
class OpenAiSolution implements Solution
|
||||
{
|
||||
public bool $aiGenerated = true;
|
||||
|
||||
protected string $prompt;
|
||||
|
||||
protected OpenAiSolutionResponse $openAiSolutionResponse;
|
||||
|
||||
public function __construct(
|
||||
protected Throwable $throwable,
|
||||
protected string $openAiKey,
|
||||
protected CacheInterface|null $cache = null,
|
||||
protected int|null $cacheTtlInSeconds = 60,
|
||||
protected string|null $applicationType = null,
|
||||
protected string|null $applicationPath = null,
|
||||
) {
|
||||
$this->prompt = $this->generatePrompt();
|
||||
|
||||
$this->openAiSolutionResponse = $this->getAiSolution();
|
||||
}
|
||||
|
||||
public function getSolutionTitle(): string
|
||||
{
|
||||
return 'AI Generated Solution';
|
||||
}
|
||||
|
||||
public function getSolutionDescription(): string
|
||||
{
|
||||
return $this->openAiSolutionResponse->description();
|
||||
}
|
||||
|
||||
public function getDocumentationLinks(): array
|
||||
{
|
||||
return $this->openAiSolutionResponse->links();
|
||||
}
|
||||
|
||||
public function getAiSolution(): ?OpenAiSolutionResponse
|
||||
{
|
||||
$solution = $this->cache->get($this->getCacheKey());
|
||||
|
||||
if ($solution) {
|
||||
return new OpenAiSolutionResponse($solution);
|
||||
}
|
||||
|
||||
$solutionText = OpenAI::client($this->openAiKey)
|
||||
->chat()
|
||||
->create([
|
||||
'model' => $this->getModel(),
|
||||
'messages' => [['role' => 'user', 'content' => $this->prompt]],
|
||||
'max_tokens' => 1000,
|
||||
'temperature' => 0,
|
||||
])->choices[0]->message->content;
|
||||
|
||||
$this->cache->set($this->getCacheKey(), $solutionText, $this->cacheTtlInSeconds);
|
||||
|
||||
return new OpenAiSolutionResponse($solutionText);
|
||||
}
|
||||
|
||||
protected function getCacheKey(): string
|
||||
{
|
||||
$hash = sha1($this->prompt);
|
||||
|
||||
return "ignition-solution-{$hash}";
|
||||
}
|
||||
|
||||
protected function generatePrompt(): string
|
||||
{
|
||||
$viewPath = Ignition::viewPath('aiPrompt');
|
||||
|
||||
$viewModel = new OpenAiPromptViewModel(
|
||||
file: $this->throwable->getFile(),
|
||||
exceptionMessage: $this->throwable->getMessage(),
|
||||
exceptionClass: get_class($this->throwable),
|
||||
snippet: $this->getApplicationFrame($this->throwable)->getSnippetAsString(15),
|
||||
line: $this->throwable->getLine(),
|
||||
applicationType: $this->applicationType,
|
||||
);
|
||||
|
||||
return (new Renderer())->renderAsString(
|
||||
['viewModel' => $viewModel],
|
||||
$viewPath,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getModel(): string
|
||||
{
|
||||
return 'gpt-3.5-turbo';
|
||||
}
|
||||
|
||||
protected function getApplicationFrame(Throwable $throwable): ?Frame
|
||||
{
|
||||
$backtrace = Backtrace::createForThrowable($throwable);
|
||||
|
||||
if ($this->applicationPath) {
|
||||
$backtrace->applicationPath($this->applicationPath);
|
||||
}
|
||||
|
||||
$frames = $backtrace->frames();
|
||||
|
||||
return $frames[$backtrace->firstApplicationFrameIndex()] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\OpenAi;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Throwable;
|
||||
|
||||
class OpenAiSolutionProvider implements HasSolutionsForThrowable
|
||||
{
|
||||
public function __construct(
|
||||
protected string $openAiKey,
|
||||
protected ?CacheInterface $cache = null,
|
||||
protected int $cacheTtlInSeconds = 60 * 60,
|
||||
protected string|null $applicationType = null,
|
||||
protected string|null $applicationPath = null,
|
||||
) {
|
||||
$this->cache ??= new DummyCache();
|
||||
}
|
||||
|
||||
public function canSolve(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSolutions(Throwable $throwable): array
|
||||
{
|
||||
return [
|
||||
new OpenAiSolution(
|
||||
$throwable,
|
||||
$this->openAiKey,
|
||||
$this->cache,
|
||||
$this->cacheTtlInSeconds,
|
||||
$this->applicationType,
|
||||
$this->applicationPath,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function applicationType(string $applicationType): self
|
||||
{
|
||||
$this->applicationType = $applicationType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function applicationPath(string $applicationPath): self
|
||||
{
|
||||
$this->applicationPath = $applicationPath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function useCache(CacheInterface $cache, int $cacheTtlInSeconds = 60 * 60): self
|
||||
{
|
||||
$this->cache = $cache;
|
||||
|
||||
$this->cacheTtlInSeconds = $cacheTtlInSeconds;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\OpenAi;
|
||||
|
||||
class OpenAiSolutionResponse
|
||||
{
|
||||
protected string $rawText;
|
||||
|
||||
public function __construct(string $rawText)
|
||||
{
|
||||
$this->rawText = trim($rawText);
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return $this->between('FIX', 'ENDFIX', $this->rawText);
|
||||
}
|
||||
|
||||
public function links(): array
|
||||
{
|
||||
$textLinks = $this->between('LINKS', 'ENDLINKS', $this->rawText);
|
||||
|
||||
$textLinks = explode(PHP_EOL, $textLinks);
|
||||
|
||||
$textLinks = array_map(function ($textLink) {
|
||||
$textLink = str_replace('\\', '\\\\', $textLink);
|
||||
$textLink = str_replace('\\\\\\', '\\\\', $textLink);
|
||||
|
||||
return json_decode($textLink, true);
|
||||
}, $textLinks);
|
||||
|
||||
array_filter($textLinks);
|
||||
|
||||
$links = [];
|
||||
foreach ($textLinks as $textLink) {
|
||||
$links[$textLink['title']] = $textLink['url'];
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
protected function between(string $start, string $end, string $text): string
|
||||
{
|
||||
$startPosition = strpos($text, $start);
|
||||
if ($startPosition === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$startPosition += strlen($start);
|
||||
$endPosition = strpos($text, $end, $startPosition);
|
||||
|
||||
if ($endPosition === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return trim(substr($text, $startPosition, $endPosition - $startPosition));
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\SolutionProviders;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Illuminate\Support\Collection;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use Spatie\Ignition\Contracts\BaseSolution;
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Throwable;
|
||||
|
||||
class BadMethodCallSolutionProvider implements HasSolutionsForThrowable
|
||||
{
|
||||
protected const REGEX = '/([a-zA-Z\\\\]+)::([a-zA-Z]+)/m';
|
||||
|
||||
public function canSolve(Throwable $throwable): bool
|
||||
{
|
||||
if (! $throwable instanceof BadMethodCallException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_null($this->getClassAndMethodFromExceptionMessage($throwable->getMessage()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSolutions(Throwable $throwable): array
|
||||
{
|
||||
return [
|
||||
BaseSolution::create('Bad Method Call')
|
||||
->setSolutionDescription($this->getSolutionDescription($throwable)),
|
||||
];
|
||||
}
|
||||
|
||||
public function getSolutionDescription(Throwable $throwable): string
|
||||
{
|
||||
if (! $this->canSolve($throwable)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
extract($this->getClassAndMethodFromExceptionMessage($throwable->getMessage()), EXTR_OVERWRITE);
|
||||
|
||||
$possibleMethod = $this->findPossibleMethod($class ?? '', $method ?? '');
|
||||
|
||||
$class ??= 'UnknownClass';
|
||||
|
||||
return "Did you mean {$class}::{$possibleMethod?->name}() ?";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @return null|array<string, mixed>
|
||||
*/
|
||||
protected function getClassAndMethodFromExceptionMessage(string $message): ?array
|
||||
{
|
||||
if (! preg_match(self::REGEX, $message, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'class' => $matches[1],
|
||||
'method' => $matches[2],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
* @param string $invalidMethodName
|
||||
*
|
||||
* @return \ReflectionMethod|null
|
||||
*/
|
||||
protected function findPossibleMethod(string $class, string $invalidMethodName): ?ReflectionMethod
|
||||
{
|
||||
return $this->getAvailableMethods($class)
|
||||
->sortByDesc(function (ReflectionMethod $method) use ($invalidMethodName) {
|
||||
similar_text($invalidMethodName, $method->name, $percentage);
|
||||
|
||||
return $percentage;
|
||||
})->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<int, ReflectionMethod>
|
||||
*/
|
||||
protected function getAvailableMethods(string $class): Collection
|
||||
{
|
||||
$class = new ReflectionClass($class);
|
||||
|
||||
return Collection::make($class->getMethods());
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\SolutionProviders;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use ParseError;
|
||||
use Spatie\Ignition\Contracts\BaseSolution;
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Throwable;
|
||||
|
||||
class MergeConflictSolutionProvider implements HasSolutionsForThrowable
|
||||
{
|
||||
public function canSolve(Throwable $throwable): bool
|
||||
{
|
||||
if (! ($throwable instanceof ParseError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->hasMergeConflictExceptionMessage($throwable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = (string)file_get_contents($throwable->getFile());
|
||||
|
||||
if (! str_contains($file, '=======')) {
|
||||
return false;
|
||||
}
|
||||
if (! str_contains($file, '>>>>>>>')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSolutions(Throwable $throwable): array
|
||||
{
|
||||
$file = (string)file_get_contents($throwable->getFile());
|
||||
preg_match('/\>\>\>\>\>\>\> (.*?)\n/', $file, $matches);
|
||||
$source = $matches[1];
|
||||
|
||||
$target = $this->getCurrentBranch(basename($throwable->getFile()));
|
||||
|
||||
return [
|
||||
BaseSolution::create("Merge conflict from branch '$source' into $target")
|
||||
->setSolutionDescription('You have a Git merge conflict. To undo your merge do `git reset --hard HEAD`'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getCurrentBranch(string $directory): string
|
||||
{
|
||||
$branch = "'".trim((string)shell_exec("cd {$directory}; git branch | grep \\* | cut -d ' ' -f2"))."'";
|
||||
|
||||
if ($branch === "''") {
|
||||
$branch = 'current branch';
|
||||
}
|
||||
|
||||
return $branch;
|
||||
}
|
||||
|
||||
protected function hasMergeConflictExceptionMessage(Throwable $throwable): bool
|
||||
{
|
||||
// For PHP 7.x and below
|
||||
if (Str::startsWith($throwable->getMessage(), 'syntax error, unexpected \'<<\'')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For PHP 8+
|
||||
if (Str::startsWith($throwable->getMessage(), 'syntax error, unexpected token "<<"')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\SolutionProviders;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Spatie\Ignition\Contracts\ProvidesSolution;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
use Spatie\Ignition\Contracts\SolutionProviderRepository as SolutionProviderRepositoryContract;
|
||||
use Throwable;
|
||||
|
||||
class SolutionProviderRepository implements SolutionProviderRepositoryContract
|
||||
{
|
||||
/** @var Collection<int, class-string<HasSolutionsForThrowable>|HasSolutionsForThrowable> */
|
||||
protected Collection $solutionProviders;
|
||||
|
||||
/** @param array<int, class-string<HasSolutionsForThrowable>|HasSolutionsForThrowable> $solutionProviders */
|
||||
public function __construct(array $solutionProviders = [])
|
||||
{
|
||||
$this->solutionProviders = Collection::make($solutionProviders);
|
||||
}
|
||||
|
||||
public function registerSolutionProvider(string|HasSolutionsForThrowable $solutionProvider): SolutionProviderRepositoryContract
|
||||
{
|
||||
$this->solutionProviders->push($solutionProvider);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function registerSolutionProviders(array $solutionProviderClasses): SolutionProviderRepositoryContract
|
||||
{
|
||||
$this->solutionProviders = $this->solutionProviders->merge($solutionProviderClasses);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSolutionsForThrowable(Throwable $throwable): array
|
||||
{
|
||||
$solutions = [];
|
||||
|
||||
if ($throwable instanceof Solution) {
|
||||
$solutions[] = $throwable;
|
||||
}
|
||||
|
||||
if ($throwable instanceof ProvidesSolution) {
|
||||
$solutions[] = $throwable->getSolution();
|
||||
}
|
||||
|
||||
$providedSolutions = $this
|
||||
->initialiseSolutionProviderRepositories()
|
||||
->filter(function (HasSolutionsForThrowable $solutionProvider) use ($throwable) {
|
||||
try {
|
||||
return $solutionProvider->canSolve($throwable);
|
||||
} catch (Throwable $exception) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
->map(function (HasSolutionsForThrowable $solutionProvider) use ($throwable) {
|
||||
try {
|
||||
return $solutionProvider->getSolutions($throwable);
|
||||
} catch (Throwable $exception) {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
->flatten()
|
||||
->toArray();
|
||||
|
||||
return array_merge($solutions, $providedSolutions);
|
||||
}
|
||||
|
||||
public function getSolutionForClass(string $solutionClass): ?Solution
|
||||
{
|
||||
if (! class_exists($solutionClass)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! in_array(Solution::class, class_implements($solutionClass) ?: [])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! function_exists('app')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return app($solutionClass);
|
||||
}
|
||||
|
||||
/** @return Collection<int, HasSolutionsForThrowable> */
|
||||
protected function initialiseSolutionProviderRepositories(): Collection
|
||||
{
|
||||
return $this->solutionProviders
|
||||
->filter(fn (HasSolutionsForThrowable|string $provider) => in_array(HasSolutionsForThrowable::class, class_implements($provider) ?: []))
|
||||
->map(function (string|HasSolutionsForThrowable $provider): HasSolutionsForThrowable {
|
||||
if (is_string($provider)) {
|
||||
return new $provider;
|
||||
}
|
||||
|
||||
return $provider;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions\SolutionProviders;
|
||||
|
||||
use ErrorException;
|
||||
use Illuminate\Support\Collection;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use Spatie\Ignition\Contracts\BaseSolution;
|
||||
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
|
||||
use Throwable;
|
||||
|
||||
class UndefinedPropertySolutionProvider implements HasSolutionsForThrowable
|
||||
{
|
||||
protected const REGEX = '/([a-zA-Z\\\\]+)::\$([a-zA-Z]+)/m';
|
||||
protected const MINIMUM_SIMILARITY = 80;
|
||||
|
||||
public function canSolve(Throwable $throwable): bool
|
||||
{
|
||||
if (! $throwable instanceof ErrorException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_null($this->getClassAndPropertyFromExceptionMessage($throwable->getMessage()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->similarPropertyExists($throwable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSolutions(Throwable $throwable): array
|
||||
{
|
||||
return [
|
||||
BaseSolution::create('Unknown Property')
|
||||
->setSolutionDescription($this->getSolutionDescription($throwable)),
|
||||
];
|
||||
}
|
||||
|
||||
public function getSolutionDescription(Throwable $throwable): string
|
||||
{
|
||||
if (! $this->canSolve($throwable) || ! $this->similarPropertyExists($throwable)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
extract(
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->getClassAndPropertyFromExceptionMessage($throwable->getMessage()),
|
||||
EXTR_OVERWRITE,
|
||||
);
|
||||
|
||||
$possibleProperty = $this->findPossibleProperty($class ?? '', $property ?? '');
|
||||
|
||||
$class = $class ?? '';
|
||||
|
||||
return "Did you mean {$class}::\${$possibleProperty->name} ?";
|
||||
}
|
||||
|
||||
protected function similarPropertyExists(Throwable $throwable): bool
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
extract($this->getClassAndPropertyFromExceptionMessage($throwable->getMessage()), EXTR_OVERWRITE);
|
||||
|
||||
$possibleProperty = $this->findPossibleProperty($class ?? '', $property ?? '');
|
||||
|
||||
return $possibleProperty !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @return null|array<string, string>
|
||||
*/
|
||||
protected function getClassAndPropertyFromExceptionMessage(string $message): ?array
|
||||
{
|
||||
if (! preg_match(self::REGEX, $message, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'class' => $matches[1],
|
||||
'property' => $matches[2],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
* @param string $invalidPropertyName
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function findPossibleProperty(string $class, string $invalidPropertyName): mixed
|
||||
{
|
||||
return $this->getAvailableProperties($class)
|
||||
->sortByDesc(function (ReflectionProperty $property) use ($invalidPropertyName) {
|
||||
similar_text($invalidPropertyName, $property->name, $percentage);
|
||||
|
||||
return $percentage;
|
||||
})
|
||||
->filter(function (ReflectionProperty $property) use ($invalidPropertyName) {
|
||||
similar_text($invalidPropertyName, $property->name, $percentage);
|
||||
|
||||
return $percentage >= self::MINIMUM_SIMILARITY;
|
||||
})->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
*
|
||||
* @return Collection<int, ReflectionProperty>
|
||||
*/
|
||||
protected function getAvailableProperties(string $class): Collection
|
||||
{
|
||||
$class = new ReflectionClass($class);
|
||||
|
||||
return Collection::make($class->getProperties());
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
/** @implements Arrayable<string, array<string,string>|string|false> */
|
||||
class SolutionTransformer implements Arrayable
|
||||
{
|
||||
protected Solution $solution;
|
||||
|
||||
public function __construct(Solution $solution)
|
||||
{
|
||||
$this->solution = $solution;
|
||||
}
|
||||
|
||||
/** @return array<string, array<string,string>|string|false> */
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'class' => get_class($this->solution),
|
||||
'title' => $this->solution->getSolutionTitle(),
|
||||
'links' => $this->solution->getDocumentationLinks(),
|
||||
'description' => $this->solution->getSolutionDescription(),
|
||||
'is_runnable' => false,
|
||||
'ai_generated' => $this->solution->aiGenerated ?? false,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class SuggestCorrectVariableNameSolution implements Solution
|
||||
{
|
||||
protected ?string $variableName;
|
||||
|
||||
protected ?string $viewFile;
|
||||
|
||||
protected ?string $suggested;
|
||||
|
||||
public function __construct(string $variableName = null, string $viewFile = null, string $suggested = null)
|
||||
{
|
||||
$this->variableName = $variableName;
|
||||
|
||||
$this->viewFile = $viewFile;
|
||||
|
||||
$this->suggested = $suggested;
|
||||
}
|
||||
|
||||
public function getSolutionTitle(): string
|
||||
{
|
||||
return 'Possible typo $'.$this->variableName;
|
||||
}
|
||||
|
||||
public function getDocumentationLinks(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getSolutionDescription(): string
|
||||
{
|
||||
return "Did you mean `$$this->suggested`?";
|
||||
}
|
||||
|
||||
public function isRunnable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Ignition\Solutions;
|
||||
|
||||
use Spatie\Ignition\Contracts\Solution;
|
||||
|
||||
class SuggestImportSolution implements Solution
|
||||
{
|
||||
protected string $class;
|
||||
|
||||
public function __construct(string $class)
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
public function getSolutionTitle(): string
|
||||
{
|
||||
return 'A class import is missing';
|
||||
}
|
||||
|
||||
public function getSolutionDescription(): string
|
||||
{
|
||||
return 'You have a missing class import. Try importing this class: `'.$this->class.'`.';
|
||||
}
|
||||
|
||||
public function getDocumentationLinks(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user