I use Laravel model factories quite extensively. Here's an older related post, in case you are interested. I tend to create a lot of methods inside them to simplify tests, utilizing Factory States. The current docs show the following example for using Factory States:

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\User;

class UserFactory extends Factory {
    protected $model = User::class;

    public function suspended(): static
    {
        return $this->state(function (array $attributes) {
            return [
                'account_status' => 'suspended',
            ];
        });
    }
}

Now enable factories using HasFactory trait on the User model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;

class User extends Model {
    use HasFactory;

    // ...
}

Combination of the above makes the following possible:

<?php

$user = \App\Models\User::factory()->suspended()->create();

Pretty useful and pretty self-explanatory. For the completeness, the above creates a suspended user account. Now take a look at the factory() method:

<?php

namespace Illuminate\Database\Eloquent\Factories;

trait HasFactory
{
    /**
     * Get a new factory instance for the model.
     *
     * @param  callable|array|int|null  $count
     * @param  callable|array  $state
     * @return \Illuminate\Database\Eloquent\Factories\Factory<static>
     */
    public static function factory($count = null, $state = [])
    {
        $factory = static::newFactory() ?: Factory::factoryForModel(get_called_class());

        return $factory
            ->count(is_numeric($count) ? $count : null)
            ->state(is_callable($count) || is_array($count) ? $count : $state);
    }

    // ...
}

The problematic bit is the @return statement which specifically says that the parent class of Factory is returned, instead of our UserFactory, which contains the suspended() method. Trying to get IDE hinting for any such custom methods (states) simply does not work this way, because we need to somehow tell the language server that calling User::factory() really returns UserFactory instead of just Factory. One of the ways to do just that is doing so explicitly:

<?php
/** @var \Database\Factories\UserFactory $userFactory */
$userFactory = User::factory();

$userFactory->suspended()->create(); // now the autocompletion works

Works but it is quite ugly, eh. One downside is that this cannot be chained easily as one might be used to, because the variable has to be on it's own line:

<?php
/** @var \Database\Factories\UserFactory $userFactory */
$userFactory = User::factory()->suspended()->create(); // autocompletion wont work

Another, much worse downside is that we have to do this everywhere we want to have the autocomplete and it is simply not worth. If there just was and easy way to fix this... Wait, there is one:

<?php

namespace App\Models;

use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class User extends Model {
    use HasFactory {
        factory as traitFactory;
    }

    /**
     * @param  callable|array|int|null  $count
     * @param  callable|array  $state
     * @return UserFactory
     */
    public static function factory($count = null, $state = []) {
        return static::traitFactory($count, $state);
    }

    // ...
}

Nice and easy! But how it works? There are two factors in play now. First, we override the factory() method received in the User model from HasFactory trait and typehint the new return type to @return UserFactory. But since we want to call the original trait factory() method inside it, we need to use php trait conflict resolution and as operator to rename the method locally as traitFactory() like this:

<?php

// ...

use HasFactory {
  factory as traitFactory;
}

Enjoy!