From 1a8151f9efddf339bcf70acf6c11ae53ae7b84df Mon Sep 17 00:00:00 2001
From: Eser DENIZ <srwiez@gmail.com>
Date: Thu, 19 Dec 2024 15:44:09 +0100
Subject: [PATCH] feat: phpstan level 5 (#446)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: phpstan level 5

* fix: $database undefined

* fix: Unsafe usage of new static()

* fix: ignore NativeAppServiceProvider not existing

* fix: FreshCommand constructor invoked with 1 parameter, 0 required

* fix: MenuBuilder facade function duplicates and arguments

* fix: Type void cannot be part of a union type declaration.

* fix: Php compactor missing imports and return statement

* fix: missing SeedDatabaseCommand@handle return statement

* Fix: cannot assign a value to a public readonly property outside of the constructor

* Fix: PowerMonitor invoked Client::get() with 2 parameters, 1 required

* fix: alternative for FreshCommand migrator argument

* Revert "fix: alternative for FreshCommand migrator argument"

This reverts commit cac9ea1442e5a8a4019e97aa58fdc39b9b3aa4c9.

* Revert "fix: FreshCommand constructor invoked with 1 parameter, 0 required"

This reverts commit cc1cb879145df52c11751f2370471a298f25b0a2.

* fix: trying something

* fix: phpstan.yml

* Revert "fix: trying something"

This reverts commit 6b88d133254bcb8881df7b4fc88a4aa5f4edc72a.

* fix: trying to lower the minimum laravel 10 dependency

* fix: final fix 🎉

* Revert "Fix: cannot assign a value to a public readonly property outside of the constructor"

This reverts commit 585fb4727ced16a729f18a32a188a99a7b1cd1ea.

* fix: put back previous fixes and ignore phpstan errors
---
 .github/workflows/phpstan.yml        | 40 +++++++++++++++++++---------
 .gitignore                           |  1 -
 composer.json                        | 10 ++++---
 phpstan-baseline.neon                |  0
 phpstan.neon                         | 18 +++++++++++++
 phpstan.neon.dist                    | 13 ---------
 src/ChildProcess.php                 | 15 ++++++-----
 src/Commands/SeedDatabaseCommand.php |  2 +-
 src/Compactor/Php.php                |  5 ++--
 src/Dialog.php                       |  2 +-
 src/Dock.php                         |  4 ++-
 src/Facades/Menu.php                 |  5 ++--
 src/NativeServiceProvider.php        | 33 +++++++++++++----------
 src/Notification.php                 |  2 +-
 src/ProgressBar.php                  |  2 +-
 15 files changed, 92 insertions(+), 60 deletions(-)
 delete mode 100644 phpstan-baseline.neon
 create mode 100644 phpstan.neon
 delete mode 100644 phpstan.neon.dist

diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
index 3855a08..84219d8 100644
--- a/.github/workflows/phpstan.yml
+++ b/.github/workflows/phpstan.yml
@@ -1,26 +1,42 @@
 name: PHPStan
 
 on:
+  workflow_dispatch:
   push:
-    paths:
-      - '**.php'
-      - 'phpstan.neon.dist'
+    branches-ignore:
+      - 'dependabot/npm_and_yarn/*'
 
 jobs:
   phpstan:
-    name: phpstan
-    runs-on: ubuntu-latest
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest]
+        php: [8.3]
+
     steps:
-      - uses: actions/checkout@v4
+
+      - name: Checkout code
+        uses: actions/checkout@v4
 
       - name: Setup PHP
         uses: shivammathur/setup-php@v2
         with:
-          php-version: '8.1'
-          coverage: none
+          php-version: ${{ matrix.php }}
+
+      - name: Get composer cache directory
+        id: composer-cache
+        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+      - name: Cache Composer dependencies
+        uses: actions/cache@v4
+        with:
+          path: ${{ steps.composer-cache.outputs.dir }}
+          key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
+          restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer-
 
-      - name: Install composer dependencies
-        uses: ramsey/composer-install@v3
+      - name: Install Dependencies
+        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
 
-      - name: Run PHPStan
-        run: ./vendor/bin/phpstan --error-format=github
+      - name: Run analysis
+        run: ./vendor/bin/phpstan analyse --error-format=github
diff --git a/.gitignore b/.gitignore
index e26945a..3dd7896 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,6 @@ composer.lock
 coverage
 docs
 phpunit.xml
-phpstan.neon
 testbench.yaml
 vendor
 node_modules
diff --git a/composer.json b/composer.json
index c3950b1..9f7b760 100644
--- a/composer.json
+++ b/composer.json
@@ -38,9 +38,9 @@
     },
     "require-dev": {
         "guzzlehttp/guzzle": "^7.0",
+        "larastan/larastan": "^2.0|^3.0",
         "laravel/pint": "^1.0",
         "nunomaduro/collision": "^7.9",
-        "nunomaduro/larastan": "^2.0.1",
         "orchestra/testbench": "^8.0",
         "pestphp/pest": "^2.0",
         "pestphp/pest-plugin-arch": "^2.0",
@@ -52,8 +52,7 @@
     },
     "autoload": {
         "psr-4": {
-            "Native\\Laravel\\": "src/",
-            "Native\\Laravel\\Database\\Factories\\": "database/factories/"
+            "Native\\Laravel\\": "src/"
         }
     },
     "autoload-dev": {
@@ -62,6 +61,11 @@
         }
     },
     "scripts": {
+        "qa" : [
+            "@composer format",
+            "@composer analyse",
+            "@composer test"
+        ],
         "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi",
         "analyse": "vendor/bin/phpstan analyse",
         "test": "vendor/bin/pest",
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
deleted file mode 100644
index e69de29..0000000
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..33be1e3
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,18 @@
+parameters:
+
+    paths:
+        - src/
+        - config/
+#        - tests/
+
+
+    # Level 9 is the highest level
+    level: 5
+
+    ignoreErrors:
+       - '#Class App\\Providers\\NativeAppServiceProvider not found#'
+       - '#Class Native\\Laravel\\ChildProcess has an uninitialized readonly property#'
+
+#
+#    excludePaths:
+#        - ./*/*/FileToBeExcluded.php
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
deleted file mode 100644
index 260b5e1..0000000
--- a/phpstan.neon.dist
+++ /dev/null
@@ -1,13 +0,0 @@
-includes:
-    - phpstan-baseline.neon
-
-parameters:
-    level: 4
-    paths:
-        - src
-        - config
-        - database
-    tmpDir: build/phpstan
-    checkOctaneCompatibility: true
-    checkModelProperties: true
-
diff --git a/src/ChildProcess.php b/src/ChildProcess.php
index 634e3f4..f524370 100644
--- a/src/ChildProcess.php
+++ b/src/ChildProcess.php
@@ -19,9 +19,9 @@ class ChildProcess implements ChildProcessContract
 
     public readonly bool $persistent;
 
-    public function __construct(protected Client $client) {}
+    final public function __construct(protected Client $client) {}
 
-    public function get(?string $alias = null): ?static
+    public function get(?string $alias = null): ?self
     {
         $alias = $alias ?? $this->alias;
 
@@ -62,7 +62,7 @@ public function start(
         ?string $cwd = null,
         ?array $env = null,
         bool $persistent = false
-    ): static {
+    ): self {
         $cmd = is_array($cmd) ? array_values($cmd) : [$cmd];
 
         $process = $this->client->post('child-process/start', [
@@ -87,7 +87,7 @@ public function php(string|array $cmd, string $alias, ?array $env = null, ?bool
         $process = $this->client->post('child-process/start-php', [
             'alias' => $alias,
             'cmd' => $cmd,
-            'cwd' => $cwd ?? base_path(),
+            'cwd' => base_path(),
             'env' => $env,
             'persistent' => $persistent,
         ])->json();
@@ -115,7 +115,7 @@ public function stop(?string $alias = null): void
         ])->json();
     }
 
-    public function restart(?string $alias = null): ?static
+    public function restart(?string $alias = null): ?self
     {
         $process = $this->client->post('child-process/restart', [
             'alias' => $alias ?? $this->alias,
@@ -128,7 +128,7 @@ public function restart(?string $alias = null): ?static
         return $this->fromRuntimeProcess($process);
     }
 
-    public function message(string $message, ?string $alias = null): static
+    public function message(string $message, ?string $alias = null): self
     {
         $this->client->post('child-process/message', [
             'alias' => $alias ?? $this->alias,
@@ -138,9 +138,10 @@ public function message(string $message, ?string $alias = null): static
         return $this;
     }
 
-    protected function fromRuntimeProcess($process): static
+    protected function fromRuntimeProcess($process)
     {
         if (isset($process['pid'])) {
+            // @phpstan-ignore-next-line
             $this->pid = $process['pid'];
         }
 
diff --git a/src/Commands/SeedDatabaseCommand.php b/src/Commands/SeedDatabaseCommand.php
index c83879e..cdc032e 100644
--- a/src/Commands/SeedDatabaseCommand.php
+++ b/src/Commands/SeedDatabaseCommand.php
@@ -15,6 +15,6 @@ public function handle()
     {
         (new NativeServiceProvider($this->laravel))->rewriteDatabase();
 
-        parent::handle();
+        return parent::handle();
     }
 }
diff --git a/src/Compactor/Php.php b/src/Compactor/Php.php
index f4838bf..7b5d3ad 100644
--- a/src/Compactor/Php.php
+++ b/src/Compactor/Php.php
@@ -3,6 +3,8 @@
 namespace Native\Laravel\Compactor;
 
 use PhpToken;
+use RuntimeException;
+use Webmozart\Assert\Assert;
 
 class Php
 {
@@ -17,7 +19,7 @@ public function compact(string $file, string $contents): string
             return $this->compactContent($contents);
         }
 
-        $this->compactContent($contents);
+        return $this->compactContent($contents);
     }
 
     protected function compactContent(string $contents): string
@@ -145,7 +147,6 @@ private function retokenizeAttribute(array &$tokens, int $opener): ?array
     {
         Assert::keyExists($tokens, $opener);
 
-        /** @var PhpToken $token */
         $token = $tokens[$opener];
         $attributeBody = mb_substr($token->text, 2);
         $subTokens = PhpToken::tokenize('<?php '.$attributeBody);
diff --git a/src/Dialog.php b/src/Dialog.php
index 6188efa..5b8d891 100644
--- a/src/Dialog.php
+++ b/src/Dialog.php
@@ -26,7 +26,7 @@ class Dialog
 
     protected $windowReference;
 
-    public function __construct(protected Client $client) {}
+    final public function __construct(protected Client $client) {}
 
     public static function new()
     {
diff --git a/src/Dock.php b/src/Dock.php
index bdbb298..9691ff8 100644
--- a/src/Dock.php
+++ b/src/Dock.php
@@ -43,12 +43,14 @@ public function cancelBounce()
         $this->client->post('dock/cancel-bounce');
     }
 
-    public function badge(?string $label = null): void|string
+    public function badge(?string $label = null): ?string
     {
         if (is_null($label)) {
             return $this->client->get('dock/badge');
         }
 
         $this->client->post('dock/badge', ['label' => $label]);
+
+        return null;
     }
 }
diff --git a/src/Facades/Menu.php b/src/Facades/Menu.php
index e12c2cb..332de24 100644
--- a/src/Facades/Menu.php
+++ b/src/Facades/Menu.php
@@ -3,6 +3,7 @@
 namespace Native\Laravel\Facades;
 
 use Illuminate\Support\Facades\Facade;
+use Native\Laravel\Contracts\MenuItem;
 use Native\Laravel\Menu\Items\Checkbox;
 use Native\Laravel\Menu\Items\Label;
 use Native\Laravel\Menu\Items\Link;
@@ -11,7 +12,7 @@
 use Native\Laravel\Menu\Items\Separator;
 
 /**
- * @method static \Native\Laravel\Menu\Menu make(\Native\Laravel\Menu\Items\MenuItem ...$items)
+ * @method static \Native\Laravel\Menu\Menu make(MenuItem ...$items)
  * @method static Checkbox checkbox(string $label, bool $checked = false, ?string $hotkey = null)
  * @method static Label label(string $label)
  * @method static Link link(string $url, string $label = null, ?string $hotkey = null)
@@ -23,7 +24,6 @@
  * @method static Role view()
  * @method static Role window()
  * @method static Role help()
- * @method static Role window()
  * @method static Role fullscreen()
  * @method static Role separator()
  * @method static Role devTools()
@@ -37,7 +37,6 @@
  * @method static Role minimize()
  * @method static Role close()
  * @method static Role quit()
- * @method static Role help()
  * @method static Role hide()
  * @method static void create(MenuItem ...$items)
  * @method static void default()
diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php
index 079bb0a..22e3991 100644
--- a/src/NativeServiceProvider.php
+++ b/src/NativeServiceProvider.php
@@ -49,6 +49,7 @@ public function packageRegistered()
         $this->mergeConfigFrom($this->package->basePath('/../config/nativephp-internal.php'), 'nativephp-internal');
 
         $this->app->singleton(FreshCommand::class, function ($app) {
+            /* @phpstan-ignore-next-line (beacause we support Laravel 10 & 11) */
             return new FreshCommand($app['migrator']);
         });
 
@@ -148,13 +149,15 @@ public function rewriteDatabase()
             }
         }
 
-        config(['database.connections.nativephp' => [
-            'driver' => 'sqlite',
-            'url' => env('DATABASE_URL'),
-            'database' => $databasePath,
-            'prefix' => '',
-            'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
-        ]]);
+        config([
+            'database.connections.nativephp' => [
+                'driver' => 'sqlite',
+                'url' => env('DATABASE_URL'),
+                'database' => $databasePath,
+                'prefix' => '',
+                'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
+            ],
+        ]);
 
         config(['database.default' => 'nativephp']);
 
@@ -174,7 +177,7 @@ public function removeDatabase()
 
         @unlink($databasePath);
         @unlink($databasePath.'-shm');
-        @unlink($database.'-wal');
+        @unlink($databasePath.'-wal');
     }
 
     protected function configureDisks(): void
@@ -197,12 +200,14 @@ protected function configureDisks(): void
                 continue;
             }
 
-            config(['filesystems.disks.'.$disk => [
-                'driver' => 'local',
-                'root' => env($env, ''),
-                'throw' => false,
-                'links' => 'skip',
-            ]]);
+            config([
+                'filesystems.disks.'.$disk => [
+                    'driver' => 'local',
+                    'root' => env($env, ''),
+                    'throw' => false,
+                    'links' => 'skip',
+                ],
+            ]);
         }
     }
 }
diff --git a/src/Notification.php b/src/Notification.php
index 85c7a73..82d13d8 100644
--- a/src/Notification.php
+++ b/src/Notification.php
@@ -12,7 +12,7 @@ class Notification
 
     protected string $event = '';
 
-    public function __construct(protected Client $client) {}
+    final public function __construct(protected Client $client) {}
 
     public static function new()
     {
diff --git a/src/ProgressBar.php b/src/ProgressBar.php
index 74ca9d7..c9e318f 100644
--- a/src/ProgressBar.php
+++ b/src/ProgressBar.php
@@ -16,7 +16,7 @@ class ProgressBar
 
     protected float $maxSecondsBetweenRedraws = 1;
 
-    public function __construct(protected int $maxSteps, protected Client $client) {}
+    final public function __construct(protected int $maxSteps, protected Client $client) {}
 
     public static function create(int $maxSteps): static
     {