Skip to content

Commit

Permalink
Generate As feature (#20)
Browse files Browse the repository at this point in the history
* Generate As feature

Ability to set preferred language

Also some type coverage enhancements and cleanup

* L10 support

* l10 support

* Fixed test on 8.1 - L10

* Fixed code style

---------

Co-authored-by: Luís Dalmolin <luis.nh@gmail.com>
  • Loading branch information
brandonferens and luisdalmolin authored Oct 29, 2024
1 parent 5b90df2 commit 37c46eb
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 32 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: run-tests
name: CI

on:
push:
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:
run: |
composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
composer require "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update --dev
composer require "pestphp/pest:${{ matrix.pest }}" "pestphp/pest-plugin-laravel:${{ matrix.pest }}" --no-interaction --no-update --dev
composer require "pestphp/pest:${{ matrix.pest }}" "pestphp/pest-plugin-laravel:${{ matrix.pest }}" "pestphp/pest-plugin-type-coverage:${{ matrix.pest }}" --no-interaction --no-update --dev
composer update --prefer-dist --no-interaction --no-suggest --dev
composer dump
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"laravel/pint": "^1.18",
"orchestra/testbench": "^9.5",
"pestphp/pest": "^3.2",
"pestphp/pest-plugin-laravel": "^3.0"
"pestphp/pest-plugin-laravel": "^3.0",
"pestphp/pest-plugin-type-coverage": "^3.1"
},
"autoload": {
"psr-4": {
Expand Down
28 changes: 28 additions & 0 deletions config/paragon.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
<?php

return [
/*
|--------------------------------------------------------------------------
| Default Generation Language
|--------------------------------------------------------------------------
|
| Here you may specify the language that Paragon will use when generating files. The default
| is TypeScript which allows for type hinting in IDE's while also providing strong typing.
| If your project doesn't support TypeScript you may instead change this to Javascript.
|
| Please note you may use the `--javascript` or `-j` flag as well if you need to generate Javascript.
|
| Supported: "typescript", "javascript"
|
*/

'generate-as' => 'typescript',

/*
|--------------------------------------------------------------------------
| Paragon Enums
|--------------------------------------------------------------------------
|
| Here you may specify the settings for enum code generation. You have the ability to change
| file paths for locating php enums, what to ignore, and where the generated files should
| be placed. By default, Paragon will look in your entire app/ directory for all enums.
|
*/

'enums' => [
'abstract-class' => 'Enum',

Expand Down
26 changes: 20 additions & 6 deletions src/Commands/GenerateEnumsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
use Kirschbaum\Paragon\Concerns\Builders\EnumJsBuilder;
use Kirschbaum\Paragon\Concerns\Builders\EnumTsBuilder;
use Kirschbaum\Paragon\Concerns\DiscoverEnums;
use Kirschbaum\Paragon\Concerns\GenerateAs;
use Kirschbaum\Paragon\Concerns\IgnoreParagon;
use Kirschbaum\Paragon\Generators\AbstractEnumGenerator;
use Kirschbaum\Paragon\Generators\EnumGenerator;
use ReflectionEnum;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use UnitEnum;

#[AsCommand(name: 'paragon:generate-enums', description: 'Generate Typescript versions of existing PHP enums')]
class GenerateEnumsCommand extends Command
Expand All @@ -28,7 +30,7 @@ public function handle(): int
$builder = $this->builder();

$generatedEnums = $this->enums()
->map(fn ($enum) => app(EnumGenerator::class, ['enum' => $enum, 'builder' => $builder])())
->map(fn (string $enum) => app(EnumGenerator::class, ['enum' => $enum, 'builder' => $builder])())
->filter();

$this->components->info("{$generatedEnums->count()} enums have been (re)generated.");
Expand All @@ -43,15 +45,15 @@ public function handle(): int
/**
* Gather all enum namespaces for searching.
*
* @return Collection<int,class-string<\UnitEnum>>
* @return Collection<int,class-string<UnitEnum>>
*/
protected function enums(): Collection
{
/** @var string */
$phpPath = config('paragon.enums.paths.php');

return DiscoverEnums::within(app_path($phpPath))
->reject(function ($enum) {
->reject(function (string $enum) {
if (! enum_exists($enum)) {
return true;
}
Expand All @@ -71,10 +73,16 @@ protected function enums(): Collection

protected function builder(): EnumBuilder
{
return $this->option('javascript')
? app(EnumJsBuilder::class)
: app(EnumTsBuilder::class);
/** @var string */
$generateAs = config('paragon.generate-as');

$builder = match (true) {
$this->option('javascript') => EnumJsBuilder::class,
$this->option('typescript') => EnumTsBuilder::class,
default => GenerateAs::from($generateAs)->builder()
};

return app($builder);
}

/**
Expand All @@ -91,6 +99,12 @@ protected function getOptions(): array
mode: InputOption::VALUE_NONE,
description: 'Output Javascript files',
),
new InputOption(
name: 'typescript',
shortcut: 't',
mode: InputOption::VALUE_NONE,
description: 'Output TypeScript files',
),
];
}
}
2 changes: 1 addition & 1 deletion src/Commands/MakeEnumMethodCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class MakeEnumMethodCommand extends GeneratorCommand
/**
* Execute the console command.
*
* @throws FileNotFoundException
* @throws Exception
* @throws FileNotFoundException
*/
public function handle(): ?bool
{
Expand Down
5 changes: 3 additions & 2 deletions src/Concerns/Builders/EnumJsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Kirschbaum\Paragon\Concerns\Builders;

use BackedEnum;
use ReflectionEnumBackedCase;
use ReflectionEnumUnitCase;
use ReflectionMethod;

Expand Down Expand Up @@ -35,7 +36,7 @@ public function fileExtension(): string
/**
* Prepare the method and its respective values so it can get injected into the case object.
*/
public function caseMethod(ReflectionMethod $method, ReflectionEnumUnitCase $case): string
public function caseMethod(ReflectionMethod $method, ReflectionEnumUnitCase|ReflectionEnumBackedCase $case): string
{
$value = $case->getValue()->{$method->getName()}();
$class = class_basename($method->getDeclaringClass()->getName());
Expand All @@ -53,7 +54,7 @@ public function caseMethod(ReflectionMethod $method, ReflectionEnumUnitCase $cas
/**
* Assemble the static getter method code for the enum case object.
*/
public function assembleCaseGetter(ReflectionEnumUnitCase $case): string
public function assembleCaseGetter(ReflectionEnumUnitCase|ReflectionEnumBackedCase $case): string
{
return <<<JS
static get {$case->name}() {
Expand Down
5 changes: 3 additions & 2 deletions src/Concerns/Builders/EnumTsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Kirschbaum\Paragon\Concerns\Builders;

use BackedEnum;
use ReflectionEnumBackedCase;
use ReflectionEnumUnitCase;
use ReflectionMethod;

Expand Down Expand Up @@ -35,7 +36,7 @@ public function fileExtension(): string
/**
* Prepare the method and its respective values so it can get injected into the case object.
*/
public function caseMethod(ReflectionMethod $method, ReflectionEnumUnitCase $case): string
public function caseMethod(ReflectionMethod $method, ReflectionEnumUnitCase|ReflectionEnumBackedCase $case): string
{
$value = $case->getValue()->{$method->getName()}();
$class = class_basename($method->getDeclaringClass()->getName());
Expand All @@ -53,7 +54,7 @@ public function caseMethod(ReflectionMethod $method, ReflectionEnumUnitCase $cas
/**
* Assemble the static getter method code for the enum case object.
*/
public function assembleCaseGetter(ReflectionEnumUnitCase $case): string
public function assembleCaseGetter(ReflectionEnumUnitCase|ReflectionEnumBackedCase $case): string
{
$class = class_basename($case->getDeclaringClass()->name);

Expand Down
19 changes: 12 additions & 7 deletions src/Concerns/DiscoverEnums.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
use ReflectionException;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
use UnitEnum;

class DiscoverEnums
{
/**
* Get all the enums by searching the given directory.
*
* @param array<int,string>|string $path
* @param array<int, string>|string $path
*
* @return Collection<class-string<\UnitEnum>,class-string<\UnitEnum>>
* @return Collection<class-string<UnitEnum>, class-string<UnitEnum>>
*/
public static function within(array|string $path): Collection
{
Expand All @@ -25,13 +26,15 @@ public static function within(array|string $path): Collection
/**
* Filter the files down to only enums.
*
* @param Finder<string,SplFileInfo> $files
* @param Finder<string, SplFileInfo> $files
*
* @return Collection<class-string<\UnitEnum>,class-string<\UnitEnum>>
* @return Collection<class-string<UnitEnum>, class-string<UnitEnum>>
*/
protected static function getEnums(Finder $files): Collection
{
/** @var Collection<int, SplFileInfo> $fileCollection */
/**
* @var Collection<int, SplFileInfo> $fileCollection
*/
$fileCollection = collect($files);

return $fileCollection
Expand All @@ -56,11 +59,13 @@ protected static function getEnums(Finder $files): Collection
/**
* Extract the class name from the given file path.
*
* @return class-string<\UnitEnum>
* @return class-string<UnitEnum>
*/
protected static function classFromFile(SplFileInfo $file): string
{
/** @var class-string<\UnitEnum> */
/**
* @var class-string<UnitEnum>
*/
return str($file->getRealPath())
->replaceFirst(base_path(), '')
->trim(DIRECTORY_SEPARATOR)
Expand Down
25 changes: 25 additions & 0 deletions src/Concerns/GenerateAs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Kirschbaum\Paragon\Concerns;

use Kirschbaum\Paragon\Concerns\Builders\EnumBuilder;
use Kirschbaum\Paragon\Concerns\Builders\EnumJsBuilder;
use Kirschbaum\Paragon\Concerns\Builders\EnumTsBuilder;

enum GenerateAs: string
{
case Javascript = 'javascript';

case TypeScript = 'typescript';

/**
* @return class-string<EnumBuilder>
*/
public function builder(): string
{
return match ($this) {
self::Javascript => EnumJsBuilder::class,
self::TypeScript => EnumTsBuilder::class,
};
}
}
4 changes: 3 additions & 1 deletion src/Generators/AbstractEnumGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ protected function imports(): Collection
return collect();
}

/** @var Collection<int, SplFileInfo> $fileCollection */
/**
* @var Collection<int, SplFileInfo> $fileCollection
*/
$fileCollection = collect($files);

return $fileCollection
Expand Down
22 changes: 12 additions & 10 deletions src/Generators/EnumGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Fluent;
use Illuminate\Support\Stringable;
use Kirschbaum\Paragon\Concerns\Builders\EnumBuilder;
use Kirschbaum\Paragon\Concerns\IgnoreParagon;
use ReflectionEnum;
use ReflectionEnumBackedCase;
use ReflectionEnumUnitCase;
use ReflectionMethod;
use UnitEnum;

class EnumGenerator
{
Expand All @@ -25,7 +27,7 @@ class EnumGenerator
/**
* Create new EnumGenerator instance.
*
* @param class-string<\UnitEnum> $enum
* @param class-string<UnitEnum> $enum
*/
public function __construct(protected string $enum, protected EnumBuilder $builder)
{
Expand Down Expand Up @@ -112,11 +114,11 @@ protected function relativePath(): string
protected function buildTypeDefinition(): string
{
return $this->methods()
->map(fn ($method) => PHP_EOL . " {$method->getName()}();")
->map(fn (ReflectionMethod $method) => PHP_EOL . " {$method->getName()}();")
->sortDesc()
->when(
$this->reflector->isBacked(),
fn ($collection) => $collection->push(PHP_EOL . " value: {$this->valueReturnType()};")
fn (Collection $collection) => $collection->push(PHP_EOL . " value: {$this->valueReturnType()};")
)
->reverse()
->join('');
Expand Down Expand Up @@ -147,12 +149,12 @@ protected function valueReturnType(): string
/**
* Build all the case objects.
*
* @param Collection<int,ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
* @param Collection<int, ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
*/
protected function buildCases(Collection $cases): string
{
return $cases
->map(function (ReflectionEnumUnitCase $case) {
->map(function (ReflectionEnumUnitCase|ReflectionEnumBackedCase $case) {
$value = $this->caseValueProperty($case);

$methodValues = $this->methods()
Expand All @@ -173,10 +175,10 @@ protected function caseValueProperty(ReflectionEnumUnitCase|ReflectionEnumBacked
->prepend(PHP_EOL . ' ')
->when(
$this->reflector->getBackingType()->getName() === 'int',
fn ($string) => $case->getValue() instanceof BackedEnum
fn (Stringable $string) => $case->getValue() instanceof BackedEnum
? $string->append("{$case->getValue()->value}")
: $string,
fn ($string) => $case->getValue() instanceof BackedEnum
fn (Stringable $string) => $case->getValue() instanceof BackedEnum
? $string->append("'{$case->getValue()->value}'")
: $string,
)
Expand All @@ -189,7 +191,7 @@ protected function caseValueProperty(ReflectionEnumUnitCase|ReflectionEnumBacked
/**
* Assemble the actual enum case object code including the name, value if needed, and any public methods.
*
* @param Collection<int,string> $methodValues
* @param Collection<int, string> $methodValues
*/
protected function assembleCaseObject(
ReflectionEnumUnitCase|ReflectionEnumBackedCase $case,
Expand All @@ -208,12 +210,12 @@ protected function assembleCaseObject(
/**
* Build all case object getter methods.
*
* @param Collection<int,ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
* @param Collection<int, ReflectionEnumUnitCase|ReflectionEnumBackedCase> $cases
*/
protected function buildGetters(Collection $cases): string
{
return $cases
->map(fn ($case) => $this->builder->assembleCaseGetter($case))
->map(fn (ReflectionEnumUnitCase|ReflectionEnumBackedCase $case) => $this->builder->assembleCaseGetter($case))
->join(PHP_EOL . PHP_EOL);
}

Expand Down
Loading

0 comments on commit 37c46eb

Please sign in to comment.