diff --git a/src/Collections/TransformedCollection.php b/src/Collections/TransformedCollection.php index 00c92b2..8a3231c 100644 --- a/src/Collections/TransformedCollection.php +++ b/src/Collections/TransformedCollection.php @@ -93,13 +93,24 @@ public function onlyChanged(): Generator } } - public function findTransformedByPath(string $path): ?Transformed + public function findTransformedByFile(string $path): ?Transformed { $path = $this->cleanupFilePath($path); return $this->fileMapping[$path] ?? null; } + public function findTransformedByDirectory(string $path): Generator + { + $path = $this->cleanupFilePath($path); + + foreach ($this->fileMapping as $transformedPath => $transformed) { + if (str_starts_with($transformedPath, $path)) { + yield $transformed; + } + } + } + public function hasChanges(): bool { foreach ($this->items as $item) { diff --git a/src/Handlers/Watch/DirectoryDeletedWatchEventHandler.php b/src/Handlers/Watch/DirectoryDeletedWatchEventHandler.php index 5304a0b..0f7c952 100644 --- a/src/Handlers/Watch/DirectoryDeletedWatchEventHandler.php +++ b/src/Handlers/Watch/DirectoryDeletedWatchEventHandler.php @@ -2,10 +2,27 @@ namespace Spatie\TypeScriptTransformer\Handlers\Watch; +use Spatie\TypeScriptTransformer\Collections\TransformedCollection; +use Spatie\TypeScriptTransformer\Events\Watch\DirectoryDeletedWatchEvent; +use Spatie\TypeScriptTransformer\TypeScriptTransformer; + class DirectoryDeletedWatchEventHandler implements WatchEventHandler { + public function __construct( + protected TypeScriptTransformer $typeScriptTransformer, + protected TransformedCollection $transformedCollection, + ) { + } + + /** + * @param DirectoryDeletedWatchEvent $event + */ public function handle($event): void { - // TODO: Implement handle() method. + $transformedItems = $this->transformedCollection->findTransformedByDirectory($event->path); + + foreach ($transformedItems as $transformed) { + $this->transformedCollection->remove($transformed->reference); + } } } diff --git a/src/Handlers/Watch/FileDeletedWatchEventHandler.php b/src/Handlers/Watch/FileDeletedWatchEventHandler.php index b3a3c56..1fbd4c9 100644 --- a/src/Handlers/Watch/FileDeletedWatchEventHandler.php +++ b/src/Handlers/Watch/FileDeletedWatchEventHandler.php @@ -19,7 +19,7 @@ public function __construct( */ public function handle($event): void { - $transformed = $this->transformedCollection->findTransformedByPath($event->path); + $transformed = $this->transformedCollection->findTransformedByFile($event->path); if ($transformed === null) { return; diff --git a/src/Handlers/Watch/FileUpdatedOrCreatedWatchEventHandler.php b/src/Handlers/Watch/FileUpdatedOrCreatedWatchEventHandler.php index eb12682..50cacc5 100644 --- a/src/Handlers/Watch/FileUpdatedOrCreatedWatchEventHandler.php +++ b/src/Handlers/Watch/FileUpdatedOrCreatedWatchEventHandler.php @@ -43,7 +43,7 @@ public function handle($event): void throw $throwable; } - $originalTransformed = $this->transformedCollection->findTransformedByPath( + $originalTransformed = $this->transformedCollection->findTransformedByFile( $event->path ); diff --git a/src/Transformed/Transformed.php b/src/Transformed/Transformed.php index 97bfd17..796e5d4 100644 --- a/src/Transformed/Transformed.php +++ b/src/Transformed/Transformed.php @@ -129,6 +129,8 @@ public function markReferenceMissing( unset($this->references[$key]); $this->missingReferences[$key] = $typeReferences; + + $this->markAsChanged(); } public function markAsChanged(): void diff --git a/src/TypeScriptTransformer.php b/src/TypeScriptTransformer.php index dabc506..5a4d6af 100644 --- a/src/TypeScriptTransformer.php +++ b/src/TypeScriptTransformer.php @@ -71,16 +71,11 @@ public function execute(): void * - Further write docs + check them -> only Laravel specific stuff * - Check old Laravel tests if we missed something * - Check in Flare whether everything is working as expected -> PR ready, needs fixing TS + * - Make sure nullables can be exported as optional: https://github.com/spatie/typescript-transformer/pull/88/files * - Release */ - $transformedCollection = $this->provideTypesAction->execute(); - - $this->executeProvidedClosuresAction->execute($transformedCollection); - - $this->connectReferencesAction->execute($transformedCollection); - - $this->executeConnectedClosuresAction->execute($transformedCollection); + $transformedCollection = $this->resolveTransformedCollection(); $this->outputTransformed($transformedCollection); @@ -94,6 +89,19 @@ public function execute(): void } } + public function resolveTransformedCollection(): TransformedCollection + { + $transformedCollection = $this->provideTypesAction->execute(); + + $this->executeProvidedClosuresAction->execute($transformedCollection); + + $this->connectReferencesAction->execute($transformedCollection); + + $this->executeConnectedClosuresAction->execute($transformedCollection); + + return $transformedCollection; + } + public function outputTransformed( TransformedCollection $transformedCollection, ): void { diff --git a/tests/Actions/ConnectReferencesActionTest.php b/tests/Actions/ConnectReferencesActionTest.php index ceefb08..2a30c24 100644 --- a/tests/Actions/ConnectReferencesActionTest.php +++ b/tests/Actions/ConnectReferencesActionTest.php @@ -25,8 +25,6 @@ $action->execute($collection); - ray($transformedClass, $transformedEnum); - expect($transformedEnum->references)->toHaveCount(0); expect($transformedEnum->referencedBy)->toHaveCount(1); expect($transformedEnum->referencedBy)->toContain($transformedClass->reference->getKey()); diff --git a/tests/Collections/TransformedCollectionTest.php b/tests/Collections/TransformedCollectionTest.php new file mode 100644 index 0000000..0aaa720 --- /dev/null +++ b/tests/Collections/TransformedCollectionTest.php @@ -0,0 +1,172 @@ +toHaveCount(1); +}); + +it('can add transformed items to the collection', function () { + $collection = new TransformedCollection(); + + $collection->add( + $initialTransformed = transformSingle(SimpleClass::class), + ); + + expect($collection)->toHaveCount(1); +}); + +it('can get a transformed item by reference', function () { + $collection = new TransformedCollection([ + $classTransformed = transformSingle(SimpleClass::class), + $manualTransformed = new Transformed( + new TypeScriptString(), + new CustomReference('vendor', 'package'), + [], + ), + ]); + + expect($collection->has(new ClassStringReference(SimpleClass::class)))->toBeTrue(); + expect($collection->get(new ClassStringReference(SimpleClass::class)))->toBe($classTransformed); + expect($collection->has(new CustomReference('vendor', 'package')))->toBeTrue(); + expect($collection->get(new CustomReference('vendor', 'package')))->toBe($manualTransformed); +}); + +it('can loop over items in the collection', function () { + $collection = new TransformedCollection([ + $a = transformSingle(SimpleClass::class), + $b = transformSingle(TypeScriptAttributedClass::class), + ]); + + $found = []; + + foreach ($collection as $transformed) { + $found[] = $transformed; + } + + expect($found)->toBe([$a, $b]); +}); + +it('can loop over only changed items in the collection', function () { + $collection = new TransformedCollection([ + $a = transformSingle(SimpleClass::class), + $b = transformSingle(TypeScriptAttributedClass::class), + ]); + + $a->changed = true; + $b->changed = false; + + $found = []; + + foreach ($collection->onlyChanged() as $transformed) { + $found[] = $transformed; + } + + expect($found)->toBe([$a]); +}); + +it('all items added to the collection are marked as changed', function () { + new TransformedCollection([ + $a = transformSingle(SimpleClass::class), + $b = transformSingle(TypeScriptAttributedClass::class), + ]); + + expect($a->changed)->toBeTrue(); + expect($b->changed)->toBeTrue(); +}); + +it('can find transformed items by file path', function () { + $collection = new TransformedCollection([ + $transformed = transformSingle(SimpleClass::class), + ]); + + $path = __DIR__.'/../Fakes/TypesToProvide/SimpleClass.php'; + + expect($collection->findTransformedByFile($path))->toBe($transformed); +}); + +it('can find transformed items by directory path', function () { + $collection = new TransformedCollection([ + $a = transformSingle(SimpleClass::class), + $b = transformSingle(TypeScriptAttributedClass::class), + $c = transformSingle(CircularA::class), + ]); + + $path = __DIR__.'/../Fakes/TypesToProvide'; + + $found = []; + + foreach ($collection->findTransformedByDirectory($path) as $transformed) { + $found[] = $transformed; + } + + expect($found)->toBe([$a, $b]); +}); + +it('can check if any items in the collection have changed', function () { + $collection = new TransformedCollection([ + $a = transformSingle(SimpleClass::class), + $b = transformSingle(TypeScriptAttributedClass::class), + ]); + + $a->changed = false; + $b->changed = false; + + expect($collection->hasChanges())->toBeFalse(); + + $a->changed = true; + + expect($collection->hasChanges())->toBeTrue(); +}); + +it('can remove a transformed item by reference', function () { + $collection = new TransformedCollection([ + transformSingle(SimpleClass::class), + ]); + + $collection->remove(new ClassStringReference(SimpleClass::class)); + + expect($collection->has(new ClassStringReference(SimpleClass::class)))->toBeFalse(); +}); + +it('can remove a transformed item by reference and update references', function () { + $collection = TypeScriptTransformer::create( + TypeScriptTransformerConfigFactory::create() + ->transformer(new AllClassTransformer()) + ->watchDirectories(__DIR__.'/../Fakes/Circular') + )->resolveTransformedCollection(); + + foreach ($collection as $transformed) { + $transformed->changed = false; + } + + $collection->remove($referenceA = new ClassStringReference(CircularA::class)); + + expect($collection)->toHaveCount(1); + + $transformedB = $collection->get($referenceB = new ClassStringReference(CircularB::class)); + + expect($transformedB->changed)->toBeTrue(); + expect($transformedB->missingReferences)->toHaveCount(1); + expect($transformedB->missingReferences)->toHaveKey($referenceA->getKey()); + expect($transformedB->missingReferences[$referenceA->getKey()]) + ->toBeArray() + ->each() + ->toBeInstanceOf(TypeReference::class); +}); diff --git a/tests/Fakes/Integration/Enum.php b/tests/Fakes/Integration/Enum.php deleted file mode 100644 index 1051711..0000000 --- a/tests/Fakes/Integration/Enum.php +++ /dev/null @@ -1,9 +0,0 @@ - */ - public $complex_union; - - public Enum $enum; - - public Exception $non_typescript_type; - - /** @var IntegrationItem[] */ - public array $array_of_reference; - - public DateTime $replacement_type; - - /** @var \DateTime */ - public $annotated_replacement_type; - - /** @var \DateTime[] */ - public array $array_annotated_replacement_type; - - public LevelUpClass $level_up_class; - - #[Hidden] - public string $hidden; - - public readonly string $readonly; - - #[Optional] - public string $optional; - - /** - * @param array $constructor_annotated_array - */ - public function __construct( - public array $constructor_annotated_array, - /** @var array */ - public array $constructor_inline_annotated_array, - ) { - } -} diff --git a/tests/Fakes/Integration/IntegrationItem.php b/tests/Fakes/Integration/IntegrationItem.php deleted file mode 100644 index 1a7bd1f..0000000 --- a/tests/Fakes/Integration/IntegrationItem.php +++ /dev/null @@ -1,8 +0,0 @@ -typeScriptNode)->toBeInstanceOf(TypeScriptNamedNode::class); + expect($transformed->getName())->toBe('StringBackedEnum'); +}); + +it('can get the name of a transformed when having a forwarding named node', function () { + $transformed = transformSingle( + \Spatie\TypeScriptTransformer\Tests\Fakes\TypesToProvide\StringBackedEnum::class, + new EnumTransformer(useUnionEnums: true) + ); + + expect($transformed->typeScriptNode)->toBeInstanceOf(TypeScriptForwardingNamedNode::class); + expect($transformed->getName())->toBe('StringBackedEnum'); +}); + +it('can manually set the name of a transformed', function () { + $transformed = transformSingle( + \Spatie\TypeScriptTransformer\Tests\Fakes\TypesToProvide\StringBackedEnum::class, + new EnumTransformer(useUnionEnums: false) + ); + + $transformed->nameAs('MyEnum'); + + expect($transformed->getName())->toBe('MyEnum'); +}); + +it('can add a missing reference', function () { + $missing = transformSingle(SimpleClass::class); + + $transformed = new Transformed( + new TypeScriptObject([ + new TypeScriptProperty('first_name', $typeReferenceA = new TypeReference($missing->reference)), + new TypeScriptProperty('last_name', $typeReferenceB = new TypeReference($missing->reference)), + ]), + new CustomReference('vendor', 'package'), + [], + ); + + $transformed->addMissingReference( + $missing->reference, + $typeReferenceA + ); + + expect($transformed->missingReferences)->toBe([ + $missing->reference->getKey() => [$typeReferenceA], + ]); + + $transformed->addMissingReference( + $missing->reference, + $typeReferenceB + ); + + expect($transformed->missingReferences)->toBe([ + $missing->reference->getKey() => [$typeReferenceA, $typeReferenceB], + ]); +}); + +it('can mark a missing reference as found', function () { + $missing = transformSingle(SimpleClass::class); + + $transformed = new Transformed( + new TypeScriptObject([ + new TypeScriptProperty('first_name', $typeReferenceA = new TypeReference($missing->reference)), + new TypeScriptProperty('last_name', $typeReferenceB = new TypeReference($missing->reference)), + ]), + new CustomReference('vendor', 'package'), + [], + ); + + $transformed->addMissingReference( + $missing->reference, + $typeReferenceA + ); + + $transformed->addMissingReference( + $missing->reference, + $typeReferenceB + ); + + $missing->changed = false; + $transformed->changed = false; + + $transformed->markMissingReferenceFound($missing); + + expect($missing->changed)->toBeFalse(); + expect($transformed->changed)->toBeTrue(); + + expect($transformed->missingReferences)->toBeEmpty(); + expect($transformed->references[$missing->reference->getKey()])->toBe([ + $typeReferenceA, + $typeReferenceB, + ]); + + expect($typeReferenceA->referenced)->toBe($missing); + expect($typeReferenceB->referenced)->toBe($missing); + + expect($missing->referencedBy)->toBe([$transformed->reference->getKey()]); +}); + +it('can mark a reference as missing', function () { + $found = transformSingle(SimpleClass::class); + + $transformed = new Transformed( + new TypeScriptObject([ + new TypeScriptProperty('first_name', $typeReferenceA = new TypeReference($found->reference)), + new TypeScriptProperty('last_name', $typeReferenceB = new TypeReference($found->reference)), + ]), + new CustomReference('vendor', 'package'), + [], + ); + + $connector = new ConnectReferencesAction( + new TypeScriptTransformerLog(new WrappedNullConsole()) + ); + + $connector->execute(new TransformedCollection([$found, $transformed])); + + $transformed->changed = false; + $found->changed = false; + + expect($transformed->missingReferences)->toBeEmpty(); + + $transformed->markReferenceMissing($found); + + expect($transformed->changed)->toBeTrue(); + + expect($transformed->references)->toBeEmpty(); + expect($transformed->missingReferences)->toBe([ + $found->reference->getKey() => [$typeReferenceA, $typeReferenceB], + ]); + + expect($typeReferenceA->referenced)->toBeNull(); + expect($typeReferenceB->referenced)->toBeNull(); +});