From 0440e11bd58d76acce8b5f86d662b382880dfcfc Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Wed, 9 Aug 2023 15:24:00 +0200 Subject: [PATCH] Better testing and importing --- src/Actions/ConnectReferencesAction.php | 6 +- src/Actions/ResolveModuleImportsAction.php | 7 +- src/Actions/ResolveRelativePathAction.php | 10 +- src/Collections/ImportsCollection.php | 10 +- src/Support/ImportLocation.php | 7 +- src/Support/Location.php | 17 +++ src/Transformed/Transformed.php | 3 +- src/Writers/ModuleWriter.php | 15 +-- .../ResolveModuleImportsActionTest.php | 120 ++++++++++++++++++ .../Actions/ResolveRelativePathActionTest.php | 5 + tests/Factories/TransformedFactory.php | 9 +- tests/Writers/ModuleWriterTest.php | 39 +++++- 12 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 tests/Actions/ResolveModuleImportsActionTest.php diff --git a/src/Actions/ConnectReferencesAction.php b/src/Actions/ConnectReferencesAction.php index 097be24..07a3544 100644 --- a/src/Actions/ConnectReferencesAction.php +++ b/src/Actions/ConnectReferencesAction.php @@ -38,8 +38,10 @@ public function execute(TransformedCollection $collection): ReferenceMap return; } - $metadata['references'][] = $reference; - $typeReference->connect($referenceMap->get($reference)); + $transformed = $referenceMap->get($reference); + + $metadata['references'][] = $transformed; + $typeReference->connect($transformed); }, [TypeReference::class]); foreach ($collection as $transformed) { diff --git a/src/Actions/ResolveModuleImportsAction.php b/src/Actions/ResolveModuleImportsAction.php index 5e0dc9b..0f2ff1a 100644 --- a/src/Actions/ResolveModuleImportsAction.php +++ b/src/Actions/ResolveModuleImportsAction.php @@ -23,7 +23,6 @@ public function __construct( public function execute( Location $location, - ReferenceMap $referenceMap, ): ImportsCollection { $collection = new ImportsCollection(); @@ -32,9 +31,7 @@ public function execute( ); foreach ($location->transformed as $transformedItem) { - foreach ($transformedItem->references as $reference) { - $referencedTransformed = $referenceMap->get($reference); - + foreach ($transformedItem->references as $referencedTransformed) { if ($referencedTransformed->location === $location->segments) { continue; } @@ -80,7 +77,7 @@ protected function resolveImportedName( } if (! in_array("{$name}Import", $usedNamesInScope)) { - return "{$name}Alt"; + return "{$name}Import"; } $counter = 2; diff --git a/src/Actions/ResolveRelativePathAction.php b/src/Actions/ResolveRelativePathAction.php index 50647f8..00aad8f 100644 --- a/src/Actions/ResolveRelativePathAction.php +++ b/src/Actions/ResolveRelativePathAction.php @@ -26,10 +26,16 @@ public function execute(array $from, array $to): ?string $relativeSegments[] = '..'; } + $hasSuffixedSegments = false; + for ($i = $commonDepth; $i < count($to); $i++) { $relativeSegments[] = $to[$i]; + + $hasSuffixedSegments = true; } - return implode('/', $relativeSegments); - } + $relativePath = implode('/', $relativeSegments); + + return $hasSuffixedSegments ? $relativePath : $relativePath.'/'; + } } diff --git a/src/Collections/ImportsCollection.php b/src/Collections/ImportsCollection.php index 210e1b2..a8ed8ff 100644 --- a/src/Collections/ImportsCollection.php +++ b/src/Collections/ImportsCollection.php @@ -59,9 +59,15 @@ public function getIterator(): Traversable */ public function getTypeScriptNodes(): array { - return array_map( + return array_values(array_map( fn (ImportLocation $import) => $import->toTypeScriptNode(), $this->imports, - ); + )); + } + + /** @return array */ + public function toArray(): array + { + return array_values($this->imports); } } diff --git a/src/Support/ImportLocation.php b/src/Support/ImportLocation.php index f95c712..d500880 100644 --- a/src/Support/ImportLocation.php +++ b/src/Support/ImportLocation.php @@ -39,11 +39,6 @@ public function toTypeScriptNode(): ?TypeScriptImport return null; } - $names = array_unique(array_map( - fn (ImportName $name) => (string) $name, - $this->importNames, - )); - - return new TypeScriptImport($this->relativePath, $names); + return new TypeScriptImport($this->relativePath, $this->importNames); } } diff --git a/src/Support/Location.php b/src/Support/Location.php index 8ed839b..e28626c 100644 --- a/src/Support/Location.php +++ b/src/Support/Location.php @@ -2,6 +2,7 @@ namespace Spatie\TypeScriptTransformer\Support; +use Spatie\TypeScriptTransformer\References\Reference; use Spatie\TypeScriptTransformer\Transformed\Transformed; class Location @@ -15,4 +16,20 @@ public function __construct( public array $transformed, ) { } + + public function getTransformedByReference(Reference $reference): ?Transformed + { + foreach ($this->transformed as $transformed) { + if ($transformed->reference->getKey() === $reference->getKey()) { + return $transformed; + } + } + + return null; + } + + public function hasReference(Reference $reference): bool + { + return $this->getTransformedByReference($reference) !== null; + } } diff --git a/src/Transformed/Transformed.php b/src/Transformed/Transformed.php index 668b224..ebe5753 100644 --- a/src/Transformed/Transformed.php +++ b/src/Transformed/Transformed.php @@ -15,7 +15,7 @@ class Transformed /** * @param array $location - * @param array $references + * @param array $references */ public function __construct( public TypeScriptNode $typeScriptNode, @@ -26,7 +26,6 @@ public function __construct( ) { } - public function getName(): ?string { if (isset($this->name)) { diff --git a/src/Writers/ModuleWriter.php b/src/Writers/ModuleWriter.php index 1db70b5..335aba6 100644 --- a/src/Writers/ModuleWriter.php +++ b/src/Writers/ModuleWriter.php @@ -3,17 +3,13 @@ namespace Spatie\TypeScriptTransformer\Writers; use Spatie\TypeScriptTransformer\Actions\ResolveModuleImportsAction; -use Spatie\TypeScriptTransformer\Actions\ResolveRelativePathAction; use Spatie\TypeScriptTransformer\Actions\SplitTransformedPerLocationAction; use Spatie\TypeScriptTransformer\Collections\ReferenceMap; use Spatie\TypeScriptTransformer\References\Reference; -use Spatie\TypeScriptTransformer\Support\ImportName; use Spatie\TypeScriptTransformer\Support\Location; use Spatie\TypeScriptTransformer\Support\TransformedCollection; use Spatie\TypeScriptTransformer\Support\WriteableFile; use Spatie\TypeScriptTransformer\Support\WritingContext; -use Spatie\TypeScriptTransformer\Transformed\Transformed; -use Spatie\TypeScriptTransformer\TypeScript\TypeScriptImport; class ModuleWriter implements Writer { @@ -43,15 +39,12 @@ protected function writeLocation( Location $location, ReferenceMap $referenceMap, ): WriteableFile { - $imports = $this->resolveModuleImportsAction->execute( - $location, - $referenceMap, - ); + $imports = $this->resolveModuleImportsAction->execute($location); $output = ''; - $writingContext = new WritingContext(function (Reference $reference) use ($referenceMap, $imports) { - if($name = $imports->getAliasOrNameForReference($reference)){ + $writingContext = new WritingContext(function (Reference $reference) use ($location, $referenceMap, $imports) { + if ($name = $imports->getAliasOrNameForReference($reference)) { return $name; } @@ -63,7 +56,7 @@ protected function writeLocation( $output .= $import->write($writingContext); } - if($imports->isEmpty() === false){ + if ($imports->isEmpty() === false) { $output .= PHP_EOL; } diff --git a/tests/Actions/ResolveModuleImportsActionTest.php b/tests/Actions/ResolveModuleImportsActionTest.php new file mode 100644 index 0000000..823e71e --- /dev/null +++ b/tests/Actions/ResolveModuleImportsActionTest.php @@ -0,0 +1,120 @@ +action = new ResolveModuleImportsAction(); +}); + +it('wont resolve imports when types are in the same module', function () { + $location = new Location([], [ + $reference = TransformedFactory::alias('A', new TypeScriptString())->build(), + TransformedFactory::alias('B', new TypeReference($reference->reference), references: [ + $reference, + ])->build(), + ]); + + expect($this->action->execute($location)->isEmpty())->toBe(true); +}); + +it('will import a type from another module', function () { + $nestedReference = TransformedFactory::alias('Nested', new TypeScriptString(), location: ['parent', 'level', 'nested'])->build(); + $parentReference = TransformedFactory::alias('Parent', new TypeScriptString(), location: ['parent'])->build(); + $deeperParent = TransformedFactory::alias('DeeperParent', new TypeScriptString(), location: ['parent', 'deeper'])->build(); + $rootReference = TransformedFactory::alias('Root', new TypeScriptString(), location: [])->build(); + + $location = new Location(['parent', 'level'], [ + TransformedFactory::alias('Type', new TypeScriptString(), references: [ + $nestedReference, + $parentReference, + $deeperParent, + $rootReference, + ])->build(), + ]); + + $imports = $this->action->execute($location); + + expect($imports->toArray()) + ->toHaveCount(4) + ->each->toBeInstanceOf(ImportLocation::class); + + expect($imports->getTypeScriptNodes())->toEqual([ + new TypeScriptImport('nested', [new ImportName('Nested', $nestedReference->reference)]), + new TypeScriptImport('../', [new ImportName('Parent', $parentReference->reference)]), + new TypeScriptImport('../deeper', [new ImportName('DeeperParent', $deeperParent->reference)]), + new TypeScriptImport('../../', [new ImportName('Root', $rootReference->reference)]), + ]); +}); + +it('wont import the same type twice', function () { + $nestedReference = TransformedFactory::alias('Nested', new TypeScriptString(), location: ['nested'])->build(); + + $location = new Location([], [ + TransformedFactory::alias('TypeA', new TypeScriptString(), references: [ + $nestedReference, + ])->build(), + TransformedFactory::alias('TypeB', new TypeScriptString(), references: [ + $nestedReference, + ])->build(), + ]); + + $imports = $this->action->execute($location); + + expect($imports->toArray()) + ->toHaveCount(1) + ->each->toBeInstanceOf(ImportLocation::class); + + expect($imports->getTypeScriptNodes())->toEqual([ + new TypeScriptImport('nested', [new ImportName('Nested', $nestedReference->reference)]), + ]); +}); + +it('will alias a reference if it is already in the module', function (){ + $nestedCollection = TransformedFactory::alias('Collection', new TypeScriptString(), location: ['nested'])->build(); + + $location = new Location([], [ + TransformedFactory::alias('Collection', new TypeScriptString(), references: [ + $nestedCollection, + ])->build(), + ]); + + $imports = $this->action->execute($location); + + expect($imports->toArray()) + ->toHaveCount(1) + ->each->toBeInstanceOf(ImportLocation::class); + + expect($imports->getTypeScriptNodes())->toEqual([ + new TypeScriptImport('nested', [new ImportName('Collection', $nestedCollection->reference, 'CollectionImport')]), + ]); +}); + +it('will alias a reference if it is already in the module and already aliased by another import', function (){ + $nestedCollection = TransformedFactory::alias('Collection', new TypeScriptString(), location: ['nested'])->build(); + $otherNestedCollection = TransformedFactory::alias('Collection', new TypeScriptString(), location: ['otherNested'])->build(); + + $location = new Location([], [ + TransformedFactory::alias('Collection', new TypeScriptString(), references: [ + $nestedCollection, + $otherNestedCollection, + ])->build(), + ]); + + $imports = $this->action->execute($location); + + expect($imports->toArray()) + ->toHaveCount(2) + ->each->toBeInstanceOf(ImportLocation::class); + + expect($imports->getTypeScriptNodes())->toEqual([ + new TypeScriptImport('nested', [new ImportName('Collection', $nestedCollection->reference, 'CollectionImport')]), + new TypeScriptImport('otherNested', [new ImportName('Collection', $otherNestedCollection->reference, 'CollectionImport2')]), + ]); +}); diff --git a/tests/Actions/ResolveRelativePathActionTest.php b/tests/Actions/ResolveRelativePathActionTest.php index ce8554c..2dda2de 100644 --- a/tests/Actions/ResolveRelativePathActionTest.php +++ b/tests/Actions/ResolveRelativePathActionTest.php @@ -43,6 +43,11 @@ ['a', 'b', 'c', 'd'], ['a', 'b', 'e', 'd'], '../../e/d' + ], + [ + ['a', 'b'], + ['a'], + '../' ] ] ); diff --git a/tests/Factories/TransformedFactory.php b/tests/Factories/TransformedFactory.php index 231aafc..3b3776b 100644 --- a/tests/Factories/TransformedFactory.php +++ b/tests/Factories/TransformedFactory.php @@ -27,12 +27,19 @@ public static function alias( ?Reference $reference = null, ?array $location = null, bool $export = true, + ?array $references = null, ): TransformedFactory { + $reference = $reference ?? new CustomReference( + 'factory_alias', + ($location !== null ? implode('.', $location) : '').Str::slug($name) + ); + return new self( typeScriptNode: new TypeScriptAlias(new TypeScriptIdentifier($name), $typeScriptNode), - reference: $reference ?? new CustomReference('factory_alias', Str::slug($name)), + reference: $reference, location: $location, export: $export, + references: $references ); } diff --git a/tests/Writers/ModuleWriterTest.php b/tests/Writers/ModuleWriterTest.php index 678b670..bed43c1 100644 --- a/tests/Writers/ModuleWriterTest.php +++ b/tests/Writers/ModuleWriterTest.php @@ -113,8 +113,8 @@ expect($files[0]) ->path->toBe($this->path.'/index.ts') ->contents->toBe(<<path->toBe($this->path.'/index.ts') ->contents->toBe(<<path->toBe($this->path.'/nested/index.ts') ->contents->toBe(<<<'TypeScript' -import { A } from './..'; +import { A } from '../'; export type B = A; TypeScript); }); + +it('can automatically alias imported types', function () { + $reference = new CustomReference('test', 'A'); + + $transformedCollection = new TransformedCollection([ + TransformedFactory::alias('A', new TypeScriptString(), reference: $reference)->build(), + TransformedFactory::alias('A', new TypeReference($reference), location: ['nested'])->build(), + ]); + + $files = $this->writer->output( + $transformedCollection, + (new ConnectReferencesAction())->execute($transformedCollection), + ); + + expect($files) + ->toHaveCount(2) + ->each->toBeInstanceOf(WriteableFile::class); + + expect($files[0]) + ->path->toBe($this->path.'/index.ts') + ->contents->toBe('export type A = string;'.PHP_EOL); + + expect($files[1]) + ->path->toBe($this->path.'/nested/index.ts') + ->contents->toBe(<<<'TypeScript' +import { A as AImport } from '../'; + +export type A = AImport; + +TypeScript); +});