Skip to content

Commit 2a81539

Browse files
Merge pull request #88 from innocenzi/feat/null-to-optional
feat: support `nullToOptional` config
2 parents bba3a3d + eb36c2c commit 2a81539

10 files changed

+86
-17
lines changed

src/Actions/TranspileTypeToTypeScriptAction.php

+8
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,15 @@ class TranspileTypeToTypeScriptAction
3232

3333
private ?string $currentClass;
3434

35+
private bool $nullablesAreOptional;
36+
3537
public function __construct(
3638
MissingSymbolsCollection $missingSymbolsCollection,
39+
bool $nullablesAreOptional = false,
3740
?string $currentClass = null
3841
) {
3942
$this->missingSymbolsCollection = $missingSymbolsCollection;
43+
$this->nullablesAreOptional = $nullablesAreOptional;
4044
$this->currentClass = $currentClass;
4145
}
4246

@@ -84,6 +88,10 @@ private function resolveListType(AbstractList $list): string
8488

8589
private function resolveNullableType(Nullable $nullable): string
8690
{
91+
if ($this->nullablesAreOptional) {
92+
return $this->execute($nullable->getActualType());
93+
}
94+
8795
return "{$this->execute($nullable->getActualType())} | null";
8896
}
8997

src/Collectors/DefaultCollector.php

+2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ protected function resolveAlreadyTransformedType(ClassTypeReflector $reflector):
3737
{
3838
$missingSymbols = new MissingSymbolsCollection();
3939
$name = $reflector->getName();
40+
$nullablesAreOptional = $this->config->shouldConsiderNullAsOptional();
4041

4142
$transpiler = new TranspileTypeToTypeScriptAction(
4243
$missingSymbols,
44+
$nullablesAreOptional,
4345
$name
4446
);
4547

src/Transformers/DtoTransformer.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,26 @@ protected function transformProperties(
5454
ReflectionClass $class,
5555
MissingSymbolsCollection $missingSymbols
5656
): string {
57-
$isOptional = ! empty($class->getAttributes(Optional::class));
57+
$isClassOptional = ! empty($class->getAttributes(Optional::class));
58+
$nullablesAreOptional = $this->config->shouldConsiderNullAsOptional();
5859

5960
return array_reduce(
6061
$this->resolveProperties($class),
61-
function (string $carry, ReflectionProperty $property) use ($isOptional, $missingSymbols) {
62+
function (string $carry, ReflectionProperty $property) use ($isClassOptional, $missingSymbols, $nullablesAreOptional) {
6263
$isHidden = ! empty($property->getAttributes(Hidden::class));
6364

6465
if ($isHidden) {
6566
return $carry;
6667
}
6768

68-
$isOptional = $isOptional || ! empty($property->getAttributes(Optional::class));
69+
$isOptional = $isClassOptional
70+
|| ! empty($property->getAttributes(Optional::class))
71+
|| ($property->getType()?->allowsNull() && $nullablesAreOptional);
6972

7073
$transformed = $this->reflectionToTypeScript(
7174
$property,
7275
$missingSymbols,
76+
$isOptional,
7377
...$this->typeProcessors()
7478
);
7579

src/Transformers/InterfaceTransformer.php

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function (string $parameterCarry, \ReflectionParameter $parameter) use ($missing
3535
$type = $this->reflectionToTypeScript(
3636
$parameter,
3737
$missingSymbols,
38+
false,
3839
...$this->typeProcessors()
3940
);
4041

@@ -53,6 +54,7 @@ function (string $parameterCarry, \ReflectionParameter $parameter) use ($missing
5354
$returnType = $this->reflectionToTypeScript(
5455
$method,
5556
$missingSymbols,
57+
false,
5658
...$this->typeProcessors()
5759
);
5860
}

src/Transformers/TransformsTypes.php

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ trait TransformsTypes
1616
protected function reflectionToTypeScript(
1717
ReflectionMethod | ReflectionProperty | ReflectionParameter $reflection,
1818
MissingSymbolsCollection $missingSymbolsCollection,
19+
bool $nullablesAreOptional = false,
1920
TypeProcessor ...$typeProcessors
2021
): ?string {
2122
$type = $this->reflectionToType(
@@ -31,6 +32,7 @@ protected function reflectionToTypeScript(
3132
return $this->typeToTypeScript(
3233
$type,
3334
$missingSymbolsCollection,
35+
$nullablesAreOptional,
3436
$reflection->getDeclaringClass()?->getName()
3537
);
3638
}
@@ -60,10 +62,12 @@ protected function reflectionToType(
6062
protected function typeToTypeScript(
6163
Type $type,
6264
MissingSymbolsCollection $missingSymbolsCollection,
65+
bool $nullablesAreOptional = false,
6366
?string $currentClass = null,
6467
): string {
6568
$transpiler = new TranspileTypeToTypeScriptAction(
6669
$missingSymbolsCollection,
70+
$nullablesAreOptional,
6771
$currentClass,
6872
);
6973

src/TypeScriptTransformerConfig.php

+14
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class TypeScriptTransformerConfig
2929

3030
private bool $transformToNativeEnums = false;
3131

32+
private bool $nullToOptional = false;
33+
3234
public static function create(): self
3335
{
3436
return new self();
@@ -90,6 +92,13 @@ public function transformToNativeEnums(bool $transformToNativeEnums = true): sel
9092
return $this;
9193
}
9294

95+
public function nullToOptional(bool $nullToOptional = false): self
96+
{
97+
$this->nullToOptional = $nullToOptional;
98+
99+
return $this;
100+
}
101+
93102
public function getAutoDiscoverTypesPaths(): array
94103
{
95104
return $this->autoDiscoverTypesPaths;
@@ -162,4 +171,9 @@ public function shouldTransformToNativeEnums(): bool
162171
{
163172
return $this->transformToNativeEnums;
164173
}
174+
175+
public function shouldConsiderNullAsOptional(): bool
176+
{
177+
return $this->nullToOptional;
178+
}
165179
}

tests/Actions/TranspileTypeToTypeScriptActionTest.php

+19-3
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,23 @@
44
use phpDocumentor\Reflection\Types\Self_;
55
use phpDocumentor\Reflection\Types\Static_;
66
use phpDocumentor\Reflection\Types\This;
7-
use function PHPUnit\Framework\assertContains;
8-
use function PHPUnit\Framework\assertEquals;
9-
use function Spatie\Snapshots\assertMatchesSnapshot;
107
use Spatie\TypeScriptTransformer\Actions\TranspileTypeToTypeScriptAction;
118
use Spatie\TypeScriptTransformer\Structures\MissingSymbolsCollection;
129
use Spatie\TypeScriptTransformer\Tests\FakeClasses\Enum\RegularEnum;
1310
use Spatie\TypeScriptTransformer\Types\StructType;
1411

12+
use function PHPUnit\Framework\assertContains;
13+
use function PHPUnit\Framework\assertEquals;
14+
use function Spatie\Snapshots\assertMatchesSnapshot;
15+
1516
beforeEach(function () {
1617
$this->missingSymbols = new MissingSymbolsCollection();
1718

1819
$this->typeResolver = new TypeResolver();
1920

2021
$this->action = new TranspileTypeToTypeScriptAction(
2122
$this->missingSymbols,
23+
false,
2224
'fake_class'
2325
);
2426
});
@@ -62,3 +64,17 @@
6264

6365
expect($transformed)->toBe('string | number');
6466
});
67+
68+
it('does not add nullable unions to optional properties', function () {
69+
$action = new TranspileTypeToTypeScriptAction(
70+
$this->missingSymbols,
71+
true
72+
);
73+
74+
$transformed = $action->execute(StructType::fromArray([
75+
'a_string' => 'string',
76+
'a_nullable_string' => '?string',
77+
]));
78+
79+
assertEquals('{a_string:string;a_nullable_string:string;}', $transformed);
80+
});

tests/Transformers/DtoTransformerTest.php

+23-8
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
use phpDocumentor\Reflection\Type;
44
use phpDocumentor\Reflection\Types\String_;
5-
use function PHPUnit\Framework\assertEquals;
6-
use function Spatie\Snapshots\assertMatchesSnapshot;
7-
use function Spatie\Snapshots\assertMatchesTextSnapshot;
85
use Spatie\TypeScriptTransformer\Attributes\Hidden;
96
use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType;
107
use Spatie\TypeScriptTransformer\Attributes\Optional;
@@ -20,6 +17,10 @@
2017
use Spatie\TypeScriptTransformer\TypeProcessors\TypeProcessor;
2118
use Spatie\TypeScriptTransformer\TypeScriptTransformerConfig;
2219

20+
use function PHPUnit\Framework\assertEquals;
21+
use function Spatie\Snapshots\assertMatchesSnapshot;
22+
use function Spatie\Snapshots\assertMatchesTextSnapshot;
23+
2324
beforeEach(function () {
2425
$config = TypeScriptTransformerConfig::create()
2526
->defaultTypeReplacements([
@@ -48,10 +49,10 @@
4849
it('a type processor can remove properties', function () {
4950
$config = TypeScriptTransformerConfig::create();
5051

51-
$transformer = new class($config) extends DtoTransformer {
52+
$transformer = new class ($config) extends DtoTransformer {
5253
protected function typeProcessors(): array
5354
{
54-
$onlyStringPropertiesProcessor = new class implements TypeProcessor {
55+
$onlyStringPropertiesProcessor = new class () implements TypeProcessor {
5556
public function process(
5657
Type $type,
5758
ReflectionProperty | ReflectionParameter | ReflectionMethod $reflection,
@@ -74,7 +75,7 @@ public function process(
7475
});
7576

7677
it('will take transform as typescript attributes into account', function () {
77-
$class = new class {
78+
$class = new class () {
7879
#[TypeScriptType('int')]
7980
public $int;
8081

@@ -102,7 +103,7 @@ public function process(
102103
});
103104

104105
it('transforms properties to optional ones when using optional attribute', function () {
105-
$class = new class {
106+
$class = new class () {
106107
#[Optional]
107108
public string $string;
108109
};
@@ -133,7 +134,7 @@ class DummyOptionalDto
133134

134135

135136
it('transforms properties to hidden ones when using hidden attribute', function () {
136-
$class = new class() {
137+
$class = new class () {
137138
public string $visible;
138139
#[Hidden]
139140
public string $hidden;
@@ -146,3 +147,17 @@ class DummyOptionalDto
146147

147148
assertMatchesSnapshot($type->transformed);
148149
});
150+
151+
it('transforms nullable properties to optional ones according to config', function () {
152+
$class = new class () {
153+
public ?string $string;
154+
};
155+
156+
$config = TypeScriptTransformerConfig::create()->nullToOptional(true);
157+
$type = (new DtoTransformer($config))->transform(
158+
new ReflectionClass($class),
159+
'Typed'
160+
);
161+
162+
$this->assertMatchesSnapshot($type->transformed);
163+
});

tests/Transformers/InterfaceTransformerTest.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<?php
22

3-
use function PHPUnit\Framework\assertNotNull;
4-
use function PHPUnit\Framework\assertNull;
5-
use function Spatie\Snapshots\assertMatchesTextSnapshot;
63
use Spatie\TypeScriptTransformer\Tests\Fakes\FakeInterface;
74
use Spatie\TypeScriptTransformer\Transformers\InterfaceTransformer;
85
use Spatie\TypeScriptTransformer\TypeScriptTransformerConfig;
96

7+
use function PHPUnit\Framework\assertNotNull;
8+
use function PHPUnit\Framework\assertNull;
9+
use function Spatie\Snapshots\assertMatchesTextSnapshot;
10+
1011
it('will only convert interfaces', function () {
1112
$transformer = new InterfaceTransformer(
1213
TypeScriptTransformerConfig::create()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
string?: string;
3+
}

0 commit comments

Comments
 (0)