Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions config/modeltyper.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@
*/
'use-enums' => false,

/*
|--------------------------------------------------------------------------
| Emit Plain TypeScript Enums Instead of Const Enums
|--------------------------------------------------------------------------
|
| Determines whether enums emitted by use-enums use the plain "enum" syntax
| instead of "const enum". Const enum members are inlined by the TypeScript
| compiler and are not accessible at runtime; plain enums produce a real
| object, so their values can be read at runtime.
|
| Requires use-enums set to true
*/
'plain-enums' => false,

/*
|--------------------------------------------------------------------------
| Use TypeScript Types Instead of Interfaces
Expand Down
5 changes: 5 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ protected function firstName(): Attribute
- --global : Generate typescript interfaces in a global namespace named models
- --json : Output the result as json
- --use-enums : Use typescript enums instead of object literals
- --plain-enums : Emit a plain enum instead of a const enum when --use-enums is set, so values are readable at runtime
- --use-types : Use typescript types instead of interfaces
- --plurals : Output model plurals
- --no-relations : Do not include relations
Expand Down Expand Up @@ -492,6 +493,10 @@ get detected and bring in your enum class with your comments:
> ModelTyper uses Object Literals by default instead of TS
> Enums [for opinionated reasons](https://maxheiber.medium.com/alternatives-to-typescript-enums-50e4c16600b1). But you can
> use `--use-enums` option to use TS Enums instead of Object Literals.
>
> With `--use-enums`, ModelTyper emits `const enum` by default. Its members are inlined by the TypeScript compiler, so
> they aren't readable at runtime (e.g. `Object.values(...)` won't work). Add `--plain-enums` alongside `--use-enums`
> to emit a plain `enum` instead, which produces a real object you can read from at runtime.

`app/Enums/UserRoleEnum.php`

Expand Down
6 changes: 3 additions & 3 deletions src/Actions/GenerateCliOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GenerateCliOutput
*
* @throws \ReflectionException
*/
public function __invoke(Collection $models, array $mappings, bool $global = false, bool $useEnums = false, bool $useTypes = false, bool $plurals = false, bool $apiResources = false, bool $optionalRelations = false, bool $noRelations = false, bool $noHidden = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false, bool $optionalNullables = false, bool $fillables = false, string $fillableSuffix = 'Fillable'): string
public function __invoke(Collection $models, array $mappings, bool $global = false, bool $useEnums = false, bool $plainEnums = false, bool $useTypes = false, bool $plurals = false, bool $apiResources = false, bool $optionalRelations = false, bool $noRelations = false, bool $noHidden = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false, bool $optionalNullables = false, bool $fillables = false, string $fillableSuffix = 'Fillable'): string
{
$modelBuilder = app(BuildModelDetails::class);
$colAttrWriter = app(WriteColumnAttribute::class);
Expand Down Expand Up @@ -181,8 +181,8 @@ public function __invoke(Collection $models, array $mappings, bool $global = fal

collect($this->enumReflectors)
->unique(fn (ReflectionClass $reflector) => $reflector->getName())
->each(function (ReflectionClass $reflector) use ($useEnums) {
$this->output .= app(WriteEnumConst::class)($reflector, $this->indent, false, $useEnums);
->each(function (ReflectionClass $reflector) use ($useEnums, $plainEnums) {
$this->output .= app(WriteEnumConst::class)($reflector, $this->indent, false, $useEnums, $plainEnums);
});

collect($this->imports)
Expand Down
6 changes: 3 additions & 3 deletions src/Actions/GenerateJsonOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class GenerateJsonOutput
*
* @throws ReflectionException
*/
public function __invoke(Collection $models, array $mappings, bool $useEnums = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false): string
public function __invoke(Collection $models, array $mappings, bool $useEnums = false, bool $plainEnums = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false): string
{
$modelBuilder = app(BuildModelDetails::class);
$colAttrWriter = app(WriteColumnAttribute::class);
Expand Down Expand Up @@ -119,8 +119,8 @@ public function __invoke(Collection $models, array $mappings, bool $useEnums = f
})->toArray();
});

$this->output['enums'] = collect($this->enumReflectors)->map(function ($enum) use ($enumWriter, $useEnums) {
$enumConst = $enumWriter(reflection: $enum, jsonOutput: true, useEnums: $useEnums);
$this->output['enums'] = collect($this->enumReflectors)->map(function ($enum) use ($enumWriter, $useEnums, $plainEnums) {
$enumConst = $enumWriter(reflection: $enum, jsonOutput: true, useEnums: $useEnums, plainEnums: $plainEnums);

return [
$enumConst['name'] => [
Expand Down
8 changes: 5 additions & 3 deletions src/Actions/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Generator
* @throws ModelTyperException
* @throws ReflectionException
*/
public function __invoke(?string $specificModel = null, bool $global = false, bool $json = false, bool $useEnums = false, bool $useTypes = false, bool $plurals = false, bool $apiResources = false, bool $optionalRelations = false, bool $noRelations = false, bool $noHidden = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false, bool $timestampsDate = false, bool $optionalNullables = false, bool $fillables = false, string $fillableSuffix = 'Fillable'): string
public function __invoke(?string $specificModel = null, bool $global = false, bool $json = false, bool $useEnums = false, bool $plainEnums = false, bool $useTypes = false, bool $plurals = false, bool $apiResources = false, bool $optionalRelations = false, bool $noRelations = false, bool $noHidden = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false, bool $timestampsDate = false, bool $optionalNullables = false, bool $fillables = false, string $fillableSuffix = 'Fillable'): string
{
$models = app(GetModels::class)(
model: $specificModel,
Expand All @@ -37,6 +37,7 @@ public function __invoke(?string $specificModel = null, bool $global = false, bo
global: $global,
json: $json,
useEnums: $useEnums,
plainEnums: $plainEnums,
useTypes: $useTypes,
plurals: $plurals,
apiResources: $apiResources,
Expand All @@ -63,19 +64,20 @@ public function __invoke(?string $specificModel = null, bool $global = false, bo
*
* @throws ReflectionException
*/
protected function display(Collection $models, bool $global = false, bool $json = false, bool $useEnums = false, bool $useTypes = false, bool $plurals = false, bool $apiResources = false, bool $optionalRelations = false, bool $noRelations = false, bool $noHidden = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false, bool $timestampsDate = false, bool $optionalNullables = false, bool $fillables = false, string $fillableSuffix = 'Fillable'): string
protected function display(Collection $models, bool $global = false, bool $json = false, bool $useEnums = false, bool $plainEnums = false, bool $useTypes = false, bool $plurals = false, bool $apiResources = false, bool $optionalRelations = false, bool $noRelations = false, bool $noHidden = false, bool $noCounts = false, bool $optionalCounts = false, bool $noExists = false, bool $optionalExists = false, bool $noSums = false, bool $optionalSums = false, bool $timestampsDate = false, bool $optionalNullables = false, bool $fillables = false, string $fillableSuffix = 'Fillable'): string
{
$mappings = app(GetMappings::class)(setTimestampsToDate: $timestampsDate);

if ($json) {
return app(GenerateJsonOutput::class)(models: $models, mappings: $mappings, useEnums: $useEnums, noCounts: $noCounts, optionalCounts: $optionalCounts, noExists: $noExists, optionalExists: $optionalExists, noSums: $noSums, optionalSums: $optionalSums);
return app(GenerateJsonOutput::class)(models: $models, mappings: $mappings, useEnums: $useEnums, plainEnums: $plainEnums, noCounts: $noCounts, optionalCounts: $optionalCounts, noExists: $noExists, optionalExists: $optionalExists, noSums: $noSums, optionalSums: $optionalSums);
}

return app(GenerateCliOutput::class)(
models: $models,
mappings: $mappings,
global: $global,
useEnums: $useEnums,
plainEnums: $plainEnums,
useTypes: $useTypes,
plurals: $plurals,
apiResources: $apiResources,
Expand Down
5 changes: 3 additions & 2 deletions src/Actions/WriteEnumConst.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class WriteEnumConst
*
* @return array{type: string, name: string}|string
*/
public function __invoke(ReflectionClass $reflection, string $indent = '', bool $jsonOutput = false, bool $useEnums = false): array|string
public function __invoke(ReflectionClass $reflection, string $indent = '', bool $jsonOutput = false, bool $useEnums = false, bool $plainEnums = false): array|string
{
$entry = '';

Expand All @@ -30,7 +30,8 @@ public function __invoke(ReflectionClass $reflection, string $indent = '', bool

if ($cases->isNotEmpty()) {
if ($useEnums) {
$entry .= "{$indent}export const enum {$reflection->getShortName()} {" . PHP_EOL;
$enumKeyword = $plainEnums ? 'enum' : 'const enum';
$entry .= "{$indent}export {$enumKeyword} {$reflection->getShortName()} {" . PHP_EOL;
} else {
$entry .= "{$indent}const {$reflection->getShortName()} = {" . PHP_EOL;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Commands/ModelTyperCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class ModelTyperCommand extends Command
{--global : Generate typescript interfaces in a global namespace named models}
{--json : Output the result as json}
{--use-enums : Use typescript enums instead of object literals}
{--plain-enums : Emit a plain typescript enum instead of a const enum when --use-enums is set, so values are readable at runtime}
{--use-types : Use typescript types instead of interfaces}
{--plurals : Output model plurals}
{--no-relations : Do not include relations}
Expand Down Expand Up @@ -79,6 +80,7 @@ public function handle(Generator $generator): int
global: $this->getConfig('global'),
json: $this->getConfig('json'),
useEnums: $this->getConfig('use-enums'),
plainEnums: $this->getConfig('plain-enums'),
useTypes: $this->getConfig('use-types'),
plurals: $this->getConfig('plurals'),
apiResources: $this->getConfig('api-resources'),
Expand Down
26 changes: 26 additions & 0 deletions test/Tests/Feature/Actions/WriteEnumConstTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,30 @@ public function test_action_can_be_executed_and_returns_array()
$this->assertEquals('Roles', $result['name']);
$this->assertIsString($result['type']);
}

public function test_action_generates_const_enum_when_use_enums_is_enabled()
{
$action = app(WriteEnumConst::class);
$reflectionModel = $this->resolveClassAsReflection(Roles::class);

$result = $action(reflection: $reflectionModel, useEnums: true);

$expected = $this->getExpectedContent('enum-const.ts', true);

$this->assertIsString($result);
$this->assertEquals($expected, $result);
}

public function test_action_generates_plain_enum_when_plain_enums_is_enabled()
{
$action = app(WriteEnumConst::class);
$reflectionModel = $this->resolveClassAsReflection(Roles::class);

$result = $action(reflection: $reflectionModel, useEnums: true, plainEnums: true);

$expected = $this->getExpectedContent('enum-plain.ts', true);

$this->assertIsString($result);
$this->assertEquals($expected, $result);
}
}
11 changes: 11 additions & 0 deletions test/Tests/Feature/Console/ModelTyperCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ public function test_command_generates_use_enums_when_option_is_enabled()
])->expectsOutput($expected);
}

public function test_command_generates_plain_enums_when_option_is_enabled()
{
$expected = $this->getExpectedContent('user-plain-enums.ts');

$this->artisan(ModelTyperCommand::class, [
'--model' => User::class,
'--use-enums' => true,
'--plain-enums' => true,
])->expectsOutput($expected);
}

public function test_command_generates_plurals_when_option_is_enabled()
{
$expected = $this->getExpectedContent('user-plurals.ts');
Expand Down
10 changes: 10 additions & 0 deletions test/input/expectations/enum-const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const enum Roles {
/** Can do anything */
ADMIN = 'admin',
/** Standard readonly */
USER = 'user',
/** Value that needs string escaping */
USERCLASS = 'App\\Models\\User',
}

export type RolesEnum = `${Roles}`
10 changes: 10 additions & 0 deletions test/input/expectations/enum-plain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export enum Roles {
/** Can do anything */
ADMIN = 'admin',
/** Standard readonly */
USER = 'user',
/** Value that needs string escaping */
USERCLASS = 'App\\Models\\User',
}

export type RolesEnum = `${Roles}`
36 changes: 36 additions & 0 deletions test/input/expectations/user-plain-enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export interface User {
// columns
id: number
name: string
email: string
email_verified_at: string | null
password?: string
remember_token?: string | null
created_at: string | null
updated_at: string | null
// mutators
role_traditional: string
role_new: string
role_enum: RolesEnum
role_enum_traditional: RolesEnum
score: number
score_nullable: number | null
role_or_string: Roles | stringEnum
// relations
notifications: DatabaseNotification[]
// counts
notifications_count: number
// exists
notifications_exists: boolean
}

export enum Roles {
/** Can do anything */
ADMIN = 'admin',
/** Standard readonly */
USER = 'user',
/** Value that needs string escaping */
USERCLASS = 'App\\Models\\User',
}

export type RolesEnum = `${Roles}`
Loading