- Introduced multiple discovery files to enhance Laravel IDE support in Visual Studio Code. - Implemented functionality to retrieve and parse test cases, translations, Livewire components, and middleware information. - Enhanced error handling and logging configurations for better debugging experience. - Utilized Laravel's service provider for bootstrapping necessary configurations.
311 lines
8.4 KiB
PHP
311 lines
8.4 KiB
PHP
<?php
|
|
|
|
|
|
error_reporting(E_ERROR | E_PARSE);
|
|
|
|
define('LARAVEL_START', microtime(true));
|
|
|
|
require_once __DIR__ . '/../autoload.php';
|
|
|
|
class LaravelVsCode
|
|
{
|
|
public static function relativePath($path)
|
|
{
|
|
if (!str_contains($path, base_path())) {
|
|
return (string) $path;
|
|
}
|
|
|
|
return ltrim(str_replace(base_path(), '', realpath($path) ?: $path), DIRECTORY_SEPARATOR);
|
|
}
|
|
|
|
public static function isVendor($path)
|
|
{
|
|
return str_contains($path, base_path("vendor"));
|
|
}
|
|
|
|
public static function outputMarker($key)
|
|
{
|
|
return '__VSCODE_LARAVEL_' . $key . '__';
|
|
}
|
|
|
|
public static function startupError(\Throwable $e)
|
|
{
|
|
throw new Error(self::outputMarker('STARTUP_ERROR') . ': ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
try {
|
|
$app = require_once __DIR__ . '/../../bootstrap/app.php';
|
|
} catch (\Throwable $e) {
|
|
LaravelVsCode::startupError($e);
|
|
exit(1);
|
|
}
|
|
|
|
$app->register(new class($app) extends \Illuminate\Support\ServiceProvider
|
|
{
|
|
public function boot()
|
|
{
|
|
config([
|
|
'logging.channels.null' => [
|
|
'driver' => 'monolog',
|
|
'handler' => \Monolog\Handler\NullHandler::class,
|
|
],
|
|
'logging.default' => 'null',
|
|
]);
|
|
}
|
|
});
|
|
|
|
try {
|
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
|
$kernel->bootstrap();
|
|
} catch (\Throwable $e) {
|
|
LaravelVsCode::startupError($e);
|
|
exit(1);
|
|
}
|
|
|
|
echo LaravelVsCode::outputMarker('START_OUTPUT');
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use Pest\Contracts\HasPrintableTestCaseName;
|
|
use Pest\Support\Str as PestStr;
|
|
use Pest\TestSuite;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use PHPUnit\Runner\TestSuiteLoader;
|
|
use PHPUnit\TextUI\XmlConfiguration\LoadedFromFileConfiguration;
|
|
use PHPUnit\TextUI\XmlConfiguration\Loader as XmlConfigurationLoader;
|
|
use SebastianBergmann\FileIterator\Facade as FileIterator;
|
|
|
|
$tests = new class {
|
|
public function all(): array
|
|
{
|
|
if (! $this->isPhpUnitInstalled()) {
|
|
return [];
|
|
}
|
|
|
|
if ($this->isPestInstalled()) {
|
|
$this->bootPest();
|
|
}
|
|
|
|
return collect(
|
|
$this->getTestSuites()
|
|
)->map(fn ($suite) => [
|
|
'name' => $suite['name'],
|
|
'files' => collect(array_merge(
|
|
$this->collectPhpUnitTests($suite['files']),
|
|
$this->collectPestTests($suite['files']),
|
|
))->map($this->pathsTransformer($suite['directories'])),
|
|
])->all();
|
|
}
|
|
|
|
protected function pathsTransformer(array $directories): callable
|
|
{
|
|
$directories = array_map(LaravelVsCode::relativePath(...), $directories);
|
|
|
|
return function (array $file) use ($directories) {
|
|
return array_merge($file, [
|
|
'path' => $path = LaravelVsCode::relativePath($file['path']),
|
|
'name' => str($path)->basename()->replace('.php', ''),
|
|
'directories' => str($path)->dirname()
|
|
->replace($directories, '')
|
|
->ltrim(DIRECTORY_SEPARATOR)
|
|
->explode(DIRECTORY_SEPARATOR)
|
|
->filter(),
|
|
]);
|
|
};
|
|
}
|
|
|
|
protected function isPhpUnitInstalled(): bool
|
|
{
|
|
return class_exists(TestCase::class);
|
|
}
|
|
|
|
protected function isPestInstalled(): bool
|
|
{
|
|
return class_exists(TestSuite::class);
|
|
}
|
|
|
|
protected function bootPest(): void
|
|
{
|
|
require_once base_path('vendor/pestphp/pest/overrides/Runner/TestSuiteLoader.php');
|
|
|
|
TestSuite::getInstance(base_path(), 'tests');
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{name: string, directories: array<int, string>, files: array<int, string>}>
|
|
*/
|
|
protected function getTestSuites(): array
|
|
{
|
|
if (is_null($config = $this->loadConfig())) {
|
|
return [[
|
|
'name' => 'default',
|
|
'directories' => [base_path('tests')],
|
|
'files' => $this->findTestFiles(base_path('tests')),
|
|
]];
|
|
}
|
|
|
|
$result = [];
|
|
|
|
foreach ($config->testSuite() as $suite) {
|
|
$files = collect();
|
|
|
|
foreach ($suite->directories() as $directory) {
|
|
$files->push(...$this->findTestFiles($directory->path()));
|
|
}
|
|
|
|
foreach ($suite->files() as $file) {
|
|
$files->push($file->path());
|
|
}
|
|
|
|
$result[] = [
|
|
'name' => $suite->name(),
|
|
'directories' => collect($suite->directories()->asArray())->map->path()->all(),
|
|
'files' => $files->all(),
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function loadConfig(): ?LoadedFromFileConfiguration
|
|
{
|
|
foreach ([
|
|
base_path('phpunit.xml'),
|
|
base_path('phpunit.xml.dist'),
|
|
] as $path) {
|
|
if (File::exists($path)) {
|
|
return (new XmlConfigurationLoader)->load($path);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
protected function findTestFiles(string $directory): array
|
|
{
|
|
return (new FileIterator)->getFilesAsArray($directory, 'Test.php');
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $files
|
|
* @return array{path:string, tests: array<int, array{name: string, line: int}>}
|
|
*/
|
|
protected function collectPestTests(array $files): array
|
|
{
|
|
if (! $this->isPestInstalled()) {
|
|
return [];
|
|
}
|
|
|
|
if (is_null($pest = TestSuite::getInstance())) {
|
|
return [];
|
|
}
|
|
|
|
$results = [];
|
|
|
|
foreach ($files as $path) {
|
|
if (is_null($factory = $pest->tests->get($path))) {
|
|
continue;
|
|
}
|
|
|
|
$results[] = [
|
|
'path' => $path,
|
|
'tests' => collect($factory->methods)
|
|
->whereNotNull('description')
|
|
->map(fn ($method) => [
|
|
'name' => $method->description,
|
|
'eventName' => PestStr::evaluable($method->description),
|
|
'line' => (new ReflectionFunction($method->closure))->getStartLine(),
|
|
])
|
|
->values()
|
|
->all(),
|
|
];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $files
|
|
* @return array{path:string, tests: array<int, array{name: string, line: int}>}
|
|
*/
|
|
protected function collectPhpUnitTests(array $files): array
|
|
{
|
|
$loader = new TestSuiteLoader;
|
|
|
|
$results = [];
|
|
|
|
foreach ($files as $path) {
|
|
try {
|
|
$reflection = $loader->load($path);
|
|
} catch (Throwable $e) {
|
|
continue;
|
|
}
|
|
|
|
if (! $this->isPhpUnitTest($reflection)) {
|
|
continue;
|
|
}
|
|
|
|
$tests = $this->extractTestMethods($reflection);
|
|
|
|
if (! empty($tests)) {
|
|
$results[] = compact('path', 'tests');
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function isPhpUnitTest(ReflectionClass $reflection): bool
|
|
{
|
|
if (! $this->isPestInstalled()) {
|
|
return $reflection->isSubclassOf(TestCase::class);
|
|
}
|
|
|
|
return $reflection->isSubclassOf(TestCase::class)
|
|
&& ! $reflection->implementsInterface(HasPrintableTestCaseName::class);
|
|
}
|
|
|
|
/**
|
|
* @param ReflectionClass<TestCase> $reflection
|
|
* @return array<int, array{name: string, line: int}>
|
|
*/
|
|
protected function extractTestMethods(ReflectionClass $reflection): array
|
|
{
|
|
$tests = [];
|
|
|
|
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
|
if ($method->getDeclaringClass()->getName() !== $reflection->getName()) {
|
|
continue;
|
|
}
|
|
|
|
if (! $this->isTestMethod($method)) {
|
|
continue;
|
|
}
|
|
|
|
$tests[] = [
|
|
'name' => $name = $method->getName(),
|
|
'eventName' => $name,
|
|
'line' => $method->getStartLine(),
|
|
];
|
|
}
|
|
|
|
return $tests;
|
|
}
|
|
|
|
protected function isTestMethod(ReflectionMethod $method): bool
|
|
{
|
|
return str_starts_with($method->getName(), 'test')
|
|
|| $method->getAttributes(Test::class) !== [];
|
|
}
|
|
};
|
|
|
|
echo json_encode($tests->all());
|
|
|
|
echo LaravelVsCode::outputMarker('END_OUTPUT');
|
|
|
|
exit(0);
|