diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 64cd01c7..3ca2e087 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,8 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - - name: Bug Report - url: https://github.com/spatie/ignition/issues/new - about: Report a bug - name: Feature Request - url: https://github.com/spatie/ignition/discussions/new?category_id=3330702 + url: https://github.com/spatie/ignition/discussions/new?category=ideas about: Share ideas for new features - name: Ask a Question - url: https://github.com/spatie/ignition/discussions/new?category_id=3330701 + url: https://github.com/spatie/ignition/discussions/new?category=q-a about: Ask the community for help diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83e29a0a..657b80ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,12 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.head_ref }} - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '18.x' @@ -34,6 +34,6 @@ jobs: run: yarn run bundle-temp - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: Add new JS bundle build diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 611344aa..8dffc03b 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.1.0 + uses: dependabot/fetch-metadata@v2.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml index b2bd42b1..d974597c 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/php-cs-fixer.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Fix style uses: docker://oskarstark/php-cs-fixer-ga @@ -21,7 +21,7 @@ jobs: id: extract_branch - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: Fix styling branch: ${{ steps.extract_branch.outputs.branch }} diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index bb24fb18..39f1a58c 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -16,7 +16,7 @@ jobs: name: phpstan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -25,7 +25,7 @@ jobs: coverage: none - name: Install composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@v4 - name: Install extra package run: composer require openai-php/client diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1570c2e6..8184533a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,14 +9,14 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest ] - php: [ 8.0, 8.1, 8.2, 8.3 ] + php: [ 8.0, 8.1, 8.2, 8.3, 8.4, 8.5 ] stability: [ prefer-lowest, prefer-stable ] name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -30,6 +30,21 @@ jobs: echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Require Pest 1 for PHP 8.0/8.1 + if: matrix.php == '8.0' || matrix.php == '8.1' + shell: bash + run: composer require "pestphp/pest:^1.20" --dev --no-update + + - name: Require Pest 2 for PHP 8.2/8.3 + if: matrix.php == '8.2' || matrix.php == '8.3' + shell: bash + run: composer require "pestphp/pest:^2.0" --dev --no-update + + - name: Require Pest 3 for PHP 8.4+ + if: matrix.php == '8.4' || matrix.php == '8.5' + shell: bash + run: composer require "pestphp/pest:^3.0" "illuminate/cache:^12.0" --dev --no-update + - name: Install dependencies run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 0cdea233..86459827 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: main @@ -21,7 +21,7 @@ jobs: release-notes: ${{ github.event.release.body }} - name: Commit updated CHANGELOG - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@v7 with: branch: main commit_message: Update CHANGELOG diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd0252c..aa856da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,47 @@ All notable changes to `ignition` will be documented in this file +## 1.16.0 - 2026-03-17 + +### What's Changed + +#### PHP 8.4 & 8.5 Compatibility + +- Bump minimum dependency versions for PHP 8.4 compatibility +- Bump spatie/backtrace minimum to ^1.7.1 to fix implicit nullable deprecation on PHP 8.5 +- Add symfony/http-foundation and symfony/mime constraints for PHP 8.4 +- Allow pestphp/pest ^3.0 + +**Full Changelog**: https://github.com/spatie/ignition/compare/1.15.1...1.16.0 + +## 1.15.1 - 2025-02-21 + +### What's Changed + +* Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/ignition/pull/683 +* Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/ignition/pull/785 +* Supports Laravel 12 by @duncanmcclean in https://github.com/spatie/ignition/pull/790 + +### New Contributors + +* @duncanmcclean made their first contribution in https://github.com/spatie/ignition/pull/790 + +**Full Changelog**: https://github.com/spatie/ignition/compare/1.15.0...1.15.1 + +## 1.15.0 - 2024-06-12 + +### What's Changed + +* Solutions refactor by @rubenvanassche in https://github.com/spatie/ignition/pull/671 + +**Full Changelog**: https://github.com/spatie/ignition/compare/1.14.2...1.15.0 + +## 1.14.2 - 2024-05-29 + +- Add Cursor as an editor + +**Full Changelog**: https://github.com/spatie/ignition/compare/1.14.1...1.14.2 + ## 1.14.1 - 2024-05-03 ### What's Changed diff --git a/README.md b/README.md index 36f0eded..70f6748f 100644 --- a/README.md +++ b/README.md @@ -89,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(); ``` diff --git a/composer.json b/composer.json index a749b10c..00210cb1 100644 --- a/composer.json +++ b/composer.json @@ -20,21 +20,24 @@ "php": "^8.0", "ext-json": "*", "ext-mbstring": "*", - "spatie/backtrace": "^1.5.3", - "spatie/flare-client-php": "^1.4.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "spatie/backtrace": "^1.7.1", + "spatie/flare-client-php": "^1.9", + "symfony/console": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/http-foundation": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/mime": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.4.42|^6.0|^7.0|^8.0", + "spatie/error-solutions": "^1.1.2" }, "require-dev" : { - "illuminate/cache" : "^9.52|^10.0|^11.0", + "illuminate/cache" : "^9.52|^10.0|^11.0|^12.0|^13.0", "mockery/mockery" : "^1.4", - "pestphp/pest" : "^1.20|^2.0", + "pestphp/pest" : "^1.20|^2.0|^3.0", "phpstan/extension-installer" : "^1.1", "phpstan/phpstan-deprecation-rules" : "^1.0", "phpstan/phpstan-phpunit" : "^1.0", "psr/simple-cache-implementation" : "*", - "symfony/cache" : "^5.4|^6.0|^7.0", - "symfony/process" : "^5.4|^6.0|^7.0", + "symfony/cache" : "^5.4.38|^6.0|^7.0|^8.0", + "symfony/process" : "^5.4.35|^6.0|^7.0|^8.0", "vlucas/phpdotenv" : "^5.5" }, "suggest" : { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0c39cdfa..363209dd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^PHPDoc tag @param for parameter \\$solutionProvider with type class\\-string\\\\|Spatie\\\\Ignition\\\\Contracts\\\\HasSolutionsForThrowable is not subtype of native type string\\.$#" - count: 1 - path: src/Contracts/SolutionProviderRepository.php - - message: "#^Method Spatie\\\\Ignition\\\\ErrorPage\\\\Renderer\\:\\:renderAsString\\(\\) has parameter \\$date with no value type specified in iterable type array\\.$#" count: 1 @@ -24,43 +19,3 @@ parameters: message: "#^Parameter \\#1 \\$callback of function set_exception_handler expects \\(callable\\(Throwable\\)\\: void\\)\\|null, array\\{\\$this\\(Spatie\\\\Ignition\\\\Ignition\\), 'handleException'\\} given\\.$#" count: 1 path: src/Ignition.php - - - - message: "#^Method Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\DummyCache\\:\\:setMultiple\\(\\) has parameter \\$values with no value type specified in iterable type iterable\\.$#" - count: 1 - path: src/Solutions/OpenAi/DummyCache.php - - - - message: "#^Cannot call method get\\(\\) on Psr\\\\SimpleCache\\\\CacheInterface\\|null\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolution.php - - - - message: "#^Cannot call method getSnippetAsString\\(\\) on Spatie\\\\Backtrace\\\\Frame\\|null\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolution.php - - - - message: "#^Cannot call method set\\(\\) on Psr\\\\SimpleCache\\\\CacheInterface\\|null\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolution.php - - - - message: "#^Parameter \\#1 \\$rawText of class Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\OpenAiSolutionResponse constructor expects string, string\\|null given\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolution.php - - - - message: "#^Parameter \\$line of class Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\OpenAiPromptViewModel constructor expects string, int given\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolution.php - - - - message: "#^Property Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\OpenAiSolution\\:\\:\\$openAiSolutionResponse \\(Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\OpenAiSolutionResponse\\) does not accept Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\OpenAiSolutionResponse\\|null\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolution.php - - - - message: "#^Method Spatie\\\\Ignition\\\\Solutions\\\\OpenAi\\\\OpenAiSolutionResponse\\:\\:links\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Solutions/OpenAi/OpenAiSolutionResponse.php diff --git a/src/Config/IgnitionConfig.php b/src/Config/IgnitionConfig.php index 2cedb803..2563636a 100644 --- a/src/Config/IgnitionConfig.php +++ b/src/Config/IgnitionConfig.php @@ -219,6 +219,14 @@ protected function getDefaultOptions(): array 'label' => 'NetBeans', 'url' => 'netbeans://open/?f=%path:%line', ], + 'zed' => [ + 'label' => 'Zed', + 'url' => 'zed://file/%path:%line', + ], + 'windsurf' => [ + 'label' => 'Windsurf', + 'url' => 'windsurf://file/%path:%line', + ], 'xdebug' => [ 'label' => 'Xdebug', 'url' => 'xdebug://%path@%line', diff --git a/src/Contracts/BaseSolution.php b/src/Contracts/BaseSolution.php deleted file mode 100644 index 1670baa2..00000000 --- a/src/Contracts/BaseSolution.php +++ /dev/null @@ -1,65 +0,0 @@ - */ - 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 */ - public function getDocumentationLinks(): array - { - return $this->links; - } - - /** @param array $links */ - public function setDocumentationLinks(array $links): self - { - $this->links = $links; - - return $this; - } -} diff --git a/src/Contracts/HasSolutionsForThrowable.php b/src/Contracts/HasSolutionsForThrowable.php deleted file mode 100644 index 3b8b1685..00000000 --- a/src/Contracts/HasSolutionsForThrowable.php +++ /dev/null @@ -1,16 +0,0 @@ - */ - public function getSolutions(Throwable $throwable): array; -} diff --git a/src/Contracts/ProvidesSolution.php b/src/Contracts/ProvidesSolution.php deleted file mode 100644 index 7d935fef..00000000 --- a/src/Contracts/ProvidesSolution.php +++ /dev/null @@ -1,11 +0,0 @@ - $parameters */ - public function run(array $parameters = []): void; - - /** @return array */ - public function getRunParameters(): array; -} diff --git a/src/Contracts/Solution.php b/src/Contracts/Solution.php deleted file mode 100644 index 58eddc27..00000000 --- a/src/Contracts/Solution.php +++ /dev/null @@ -1,13 +0,0 @@ - */ - public function getDocumentationLinks(): array; -} diff --git a/src/Contracts/SolutionProviderRepository.php b/src/Contracts/SolutionProviderRepository.php deleted file mode 100644 index 2c4ebe06..00000000 --- a/src/Contracts/SolutionProviderRepository.php +++ /dev/null @@ -1,36 +0,0 @@ -|HasSolutionsForThrowable $solutionProvider - * - * @return $this - */ - public function registerSolutionProvider(string $solutionProvider): self; - - /** - * @param array|HasSolutionsForThrowable> $solutionProviders - * - * @return $this - */ - public function registerSolutionProviders(array $solutionProviders): self; - - /** - * @param Throwable $throwable - * - * @return array - */ - public function getSolutionsForThrowable(Throwable $throwable): array; - - /** - * @param class-string $solutionClass - * - * @return null|Solution - */ - public function getSolutionForClass(string $solutionClass): ?Solution; -} diff --git a/src/ErrorPage/ErrorPageViewModel.php b/src/ErrorPage/ErrorPageViewModel.php index efae9297..d3ae8b3c 100644 --- a/src/ErrorPage/ErrorPageViewModel.php +++ b/src/ErrorPage/ErrorPageViewModel.php @@ -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 diff --git a/src/Ignition.php b/src/Ignition.php index 75be968b..18f44867 100644 --- a/src/Ignition.php +++ b/src/Ignition.php @@ -4,6 +4,12 @@ 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\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 @@ -268,7 +268,7 @@ public function renderError( 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)) { + 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; diff --git a/src/Solutions/OpenAi/DummyCache.php b/src/Solutions/OpenAi/DummyCache.php deleted file mode 100644 index 43abf616..00000000 --- a/src/Solutions/OpenAi/DummyCache.php +++ /dev/null @@ -1,48 +0,0 @@ -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; - } -} diff --git a/src/Solutions/OpenAi/OpenAiSolution.php b/src/Solutions/OpenAi/OpenAiSolution.php deleted file mode 100644 index deb2a132..00000000 --- a/src/Solutions/OpenAi/OpenAiSolution.php +++ /dev/null @@ -1,115 +0,0 @@ -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; - } -} diff --git a/src/Solutions/OpenAi/OpenAiSolutionProvider.php b/src/Solutions/OpenAi/OpenAiSolutionProvider.php deleted file mode 100644 index 2cac8683..00000000 --- a/src/Solutions/OpenAi/OpenAiSolutionProvider.php +++ /dev/null @@ -1,62 +0,0 @@ -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; - } -} diff --git a/src/Solutions/OpenAi/OpenAiSolutionResponse.php b/src/Solutions/OpenAi/OpenAiSolutionResponse.php deleted file mode 100644 index 27799978..00000000 --- a/src/Solutions/OpenAi/OpenAiSolutionResponse.php +++ /dev/null @@ -1,58 +0,0 @@ -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)); - } -} diff --git a/src/Solutions/SolutionProviders/BadMethodCallSolutionProvider.php b/src/Solutions/SolutionProviders/BadMethodCallSolutionProvider.php deleted file mode 100644 index df1544b8..00000000 --- a/src/Solutions/SolutionProviders/BadMethodCallSolutionProvider.php +++ /dev/null @@ -1,98 +0,0 @@ -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 - */ - 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 - */ - protected function getAvailableMethods(string $class): Collection - { - $class = new ReflectionClass($class); - - return Collection::make($class->getMethods()); - } -} diff --git a/src/Solutions/SolutionProviders/MergeConflictSolutionProvider.php b/src/Solutions/SolutionProviders/MergeConflictSolutionProvider.php deleted file mode 100644 index 974d1f7b..00000000 --- a/src/Solutions/SolutionProviders/MergeConflictSolutionProvider.php +++ /dev/null @@ -1,74 +0,0 @@ -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; - } -} diff --git a/src/Solutions/SolutionProviders/SolutionProviderRepository.php b/src/Solutions/SolutionProviders/SolutionProviderRepository.php deleted file mode 100644 index 648e69b9..00000000 --- a/src/Solutions/SolutionProviders/SolutionProviderRepository.php +++ /dev/null @@ -1,101 +0,0 @@ -|HasSolutionsForThrowable> */ - protected Collection $solutionProviders; - - /** @param array|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 */ - 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; - }); - } -} diff --git a/src/Solutions/SolutionProviders/UndefinedPropertySolutionProvider.php b/src/Solutions/SolutionProviders/UndefinedPropertySolutionProvider.php deleted file mode 100644 index 574a6efd..00000000 --- a/src/Solutions/SolutionProviders/UndefinedPropertySolutionProvider.php +++ /dev/null @@ -1,121 +0,0 @@ -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 - */ - 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 - */ - protected function getAvailableProperties(string $class): Collection - { - $class = new ReflectionClass($class); - - return Collection::make($class->getProperties()); - } -} diff --git a/src/Solutions/SolutionTransformer.php b/src/Solutions/SolutionTransformer.php deleted file mode 100644 index d1de59b4..00000000 --- a/src/Solutions/SolutionTransformer.php +++ /dev/null @@ -1,30 +0,0 @@ -|string|false> */ -class SolutionTransformer implements Arrayable -{ - protected Solution $solution; - - public function __construct(Solution $solution) - { - $this->solution = $solution; - } - - /** @return array|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, - ]; - } -} diff --git a/src/Solutions/SuggestCorrectVariableNameSolution.php b/src/Solutions/SuggestCorrectVariableNameSolution.php deleted file mode 100644 index d00e438e..00000000 --- a/src/Solutions/SuggestCorrectVariableNameSolution.php +++ /dev/null @@ -1,43 +0,0 @@ -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; - } -} diff --git a/src/Solutions/SuggestImportSolution.php b/src/Solutions/SuggestImportSolution.php deleted file mode 100644 index ccfa2553..00000000 --- a/src/Solutions/SuggestImportSolution.php +++ /dev/null @@ -1,30 +0,0 @@ -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 []; - } -} diff --git a/tests/Solutions/OpenAiSolutionProviderTest.php b/tests/Solutions/OpenAiSolutionProviderTest.php deleted file mode 100644 index eba84e78..00000000 --- a/tests/Solutions/OpenAiSolutionProviderTest.php +++ /dev/null @@ -1,29 +0,0 @@ -markTestSkipped('Cannot run AI test'); - - return; - } - - $repository = new Repository(new ArrayStore()); - - $solutionProvider = new OpenAiSolutionProvider( - env('OPEN_API_KEY'), - $repository, - ); - - $solutions = $solutionProvider->getSolutions(new Exception('T_PAAMAYIM_NEKUDOTAYIM expected')); - - $solution = $solutions[0]; - - expect($solution->getSolutionDescription())->toBeString(); -}); diff --git a/tests/Solutions/UndefinedPropertySolutionProviderTest.php b/tests/Solutions/UndefinedPropertySolutionProviderTest.php deleted file mode 100644 index d0176b60..00000000 --- a/tests/Solutions/UndefinedPropertySolutionProviderTest.php +++ /dev/null @@ -1,41 +0,0 @@ -canSolve(getUndefinedPropertyException()); - - expect($canSolve)->toBeTrue(); -}); - -it('cannot solve an undefined property exception when there is no similar property', function () { - $providerClass = UndefinedPropertySolutionProvider::class; - - $canSolve = (new $providerClass)->canSolve(getUndefinedPropertyException('balance')); - - expect($canSolve)->toBeFalse(); -}); - -it('can recommend a property name when there is a similar property', function () { - $providerClass = UndefinedPropertySolutionProvider::class; - - $solution = (new $providerClass)->getSolutions(getUndefinedPropertyException())[0]; - - expect($solution->getSolutionDescription())->toEqual('Did you mean Spatie\Ignition\Tests\TestClasses\Models\Car::$color ?'); -}); - -it('cannot recommend a property name when there is no similar property', function () { - $providerClass = UndefinedPropertySolutionProvider::class; - - $solution = (new $providerClass)->getSolutions(getUndefinedPropertyException('balance'))[0]; - - expect($solution->getSolutionDescription())->toEqual(''); -}); - -// Helpers -function getUndefinedPropertyException(string $property = 'colro'): ErrorException -{ - return new ErrorException("Undefined property: Spatie\Ignition\Tests\TestClasses\Models\Car::$$property "); -}