From 86af018ca9ba7bc4bbc10f4218c631271957a9d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 07:34:36 +0000 Subject: [PATCH 1/4] Initial plan From 49cda24d460e7bf66e4b8aecb327cb9b918af613 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 07:46:21 +0000 Subject: [PATCH 2/4] Add Importable interface and update all classes to implement it Co-authored-by: ruudk <104180+ruudk@users.noreply.github.com> --- src/Alias.php | 9 ++- src/ClassName.php | 9 ++- src/CodeGenerator.php | 6 +- src/FullyQualified.php | 9 ++- src/FunctionName.php | 9 ++- src/Importable.php | 23 ++++++ src/NamespaceName.php | 9 ++- tests/ImportableTest.php | 154 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 205 insertions(+), 23 deletions(-) create mode 100644 src/Importable.php create mode 100644 tests/ImportableTest.php diff --git a/src/Alias.php b/src/Alias.php index 1b5d74d..1ffdece 100644 --- a/src/Alias.php +++ b/src/Alias.php @@ -6,16 +6,15 @@ use InvalidArgumentException; use Override; -use Stringable; -final readonly class Alias implements Stringable +final readonly class Alias implements Importable { public string $alias; - public FullyQualified | FunctionName | NamespaceName $target; + public Importable $target; public function __construct( string $alias, - FullyQualified | FunctionName | NamespaceName $target, + Importable $target, ) { $alias = trim($alias); @@ -37,6 +36,7 @@ public function __toString() : string return sprintf('%s as %s', $this->target, $this->alias); } + #[Override] public function equals(object $other) : bool { return $other instanceof self @@ -44,6 +44,7 @@ public function equals(object $other) : bool && $this->target->equals($other->target); } + #[Override] public function compare(object $other) : int { // Aliases should sort by their target, not their alias name diff --git a/src/ClassName.php b/src/ClassName.php index b3b5494..5686355 100644 --- a/src/ClassName.php +++ b/src/ClassName.php @@ -6,9 +6,8 @@ use InvalidArgumentException; use Override; -use Stringable; -final readonly class ClassName implements Stringable +final readonly class ClassName implements Importable { /** * @var non-empty-string @@ -34,7 +33,7 @@ public function __construct( /** * @phpstan-return ($input is null ? null : self) */ - public static function maybeFromString(null | self | string $input) : ?self + public static function maybeFromString(null | Importable | self | string $input) : ?self { if ($input === null) { return null; @@ -44,7 +43,7 @@ public static function maybeFromString(null | self | string $input) : ?self return $input; } - return new self($input); + return new self((string) $input); } #[Override] @@ -53,11 +52,13 @@ public function __toString() : string return $this->name; } + #[Override] public function equals(object $other) : bool { return $other instanceof self && $this->name === $other->name; } + #[Override] public function compare(object $other) : int { if ($other instanceof self) { diff --git a/src/CodeGenerator.php b/src/CodeGenerator.php index de90d6e..9ccf355 100644 --- a/src/CodeGenerator.php +++ b/src/CodeGenerator.php @@ -159,7 +159,7 @@ public function maybeDump( /** * Finds an available alias for a type, appending numbers if the alias is already taken */ - private function findAvailableAlias(Alias | FullyQualified | FunctionName | NamespaceName $type, string $alias, int $i = 1) : string + private function findAvailableAlias(Importable $type, string $alias, int $i = 1) : string { $aliasToCheck = $i === 1 ? $alias : sprintf('%s%d', $alias, $i); @@ -188,7 +188,7 @@ public function importEnum(UnitEnum $enum) : string /** * Imports a class, namespace, or function and returns the alias to use in the generated code */ - public function import(FullyQualified | FunctionName | NamespaceName | string $fqcnOrEnum) : string + public function import(Importable | string $fqcnOrEnum) : string { if ($fqcnOrEnum instanceof FunctionName) { $alias = $this->findAvailableAlias($fqcnOrEnum, $fqcnOrEnum->shortName); @@ -214,7 +214,7 @@ public function import(FullyQualified | FunctionName | NamespaceName | string $f /** * Imports a class by importing its parent namespace and returning the relative path */ - public function importByParent(FullyQualified | string $name) : string + public function importByParent(Importable | string $name) : string { $fqcn = FullyQualified::maybeFromString($name); diff --git a/src/FullyQualified.php b/src/FullyQualified.php index 05aa5c5..f7c8672 100644 --- a/src/FullyQualified.php +++ b/src/FullyQualified.php @@ -6,9 +6,8 @@ use InvalidArgumentException; use Override; -use Stringable; -final readonly class FullyQualified implements Stringable +final readonly class FullyQualified implements Importable { public ClassName $className; public ?NamespaceName $namespace; @@ -37,7 +36,7 @@ public function __construct( /** * @phpstan-return ($input is null ? null : self) */ - public static function maybeFromString(null | self | string $input) : ?self + public static function maybeFromString(null | Importable | self | string $input) : ?self { if ($input === null) { return null; @@ -47,7 +46,7 @@ public static function maybeFromString(null | self | string $input) : ?self return $input; } - return new self($input); + return new self((string) $input); } #[Override] @@ -60,11 +59,13 @@ public function __toString() : string return $this->namespace . '\\' . $this->className; } + #[Override] public function equals(object $other) : bool { return $other instanceof self && (string) $this === (string) $other; } + #[Override] public function compare(object $other) : int { $thisStr = str_replace('\\', ' ', (string) $this); diff --git a/src/FunctionName.php b/src/FunctionName.php index bb926ec..86e980f 100644 --- a/src/FunctionName.php +++ b/src/FunctionName.php @@ -6,9 +6,8 @@ use InvalidArgumentException; use Override; -use Stringable; -final class FunctionName implements Stringable +final class FunctionName implements Importable { public readonly string $name; @@ -38,7 +37,7 @@ public function __construct( /** * @phpstan-return ($input is null ? null : self) */ - public static function maybeFromString(null | self | string $input) : ?self + public static function maybeFromString(null | Importable | self | string $input) : ?self { if ($input === null) { return null; @@ -48,7 +47,7 @@ public static function maybeFromString(null | self | string $input) : ?self return $input; } - return new self($input); + return new self((string) $input); } #[Override] @@ -57,11 +56,13 @@ public function __toString() : string return 'function ' . $this->name; } + #[Override] public function equals(object $other) : bool { return $other instanceof self && (string) $this === (string) $other; } + #[Override] public function compare(object $other) : int { $thisStr = str_replace('\\', ' ', $this->name); diff --git a/src/Importable.php b/src/Importable.php new file mode 100644 index 0000000..ed421b4 --- /dev/null +++ b/src/Importable.php @@ -0,0 +1,23 @@ +namespace, $part, ...$parts); } + #[Override] public function equals(object $other) : bool { return $other instanceof self && $this->namespace === $other->namespace; } + #[Override] public function compare(object $other) : int { $thisStr = str_replace('\\', ' ', $this->namespace); diff --git a/tests/ImportableTest.php b/tests/ImportableTest.php new file mode 100644 index 0000000..9e89fee --- /dev/null +++ b/tests/ImportableTest.php @@ -0,0 +1,154 @@ +equals($importable)); + // Test that string conversion works + $stringRepresentation = (string) $importable; + self::assertNotEmpty($stringRepresentation); + } + } + + public function testFullyQualifiedMaybeFromStringWithImportable() : void + { + $className = new ClassName('MyClass'); + $functionName = new FunctionName('App\\Helpers\\format'); + $namespaceName = new NamespaceName('App\\Models'); + + // Test with ClassName + $result1 = FullyQualified::maybeFromString($className); + self::assertSame('MyClass', (string) $result1); + + // Test with FunctionName - should convert its string representation + $result2 = FullyQualified::maybeFromString($functionName); + // FunctionName __toString includes "function " prefix, so this will create a class with that name + self::assertSame('function App\\Helpers\\format', (string) $result2); + + // Test with NamespaceName + $result3 = FullyQualified::maybeFromString($namespaceName); + self::assertSame('App\\Models', (string) $result3); + } + + public function testClassNameMaybeFromStringWithImportable() : void + { + $fullyQualified = new FullyQualified('User'); // No namespace, just class name + $namespaceName = new NamespaceName('Models'); + + // Test with FullyQualified (no namespace) + $result1 = ClassName::maybeFromString($fullyQualified); + self::assertSame('User', $result1->name); + + // Test with NamespaceName + $result2 = ClassName::maybeFromString($namespaceName); + self::assertSame('Models', $result2->name); + } + + public function testFunctionNameMaybeFromStringWithImportable() : void + { + $fullyQualified = new FullyQualified('App\\Helpers\\format'); + $namespaceName = new NamespaceName('App\\Helpers'); + + // Test with FullyQualified + $result1 = FunctionName::maybeFromString($fullyQualified); + self::assertSame('function App\\Helpers\\format', (string) $result1); + + // Test with NamespaceName + $result2 = FunctionName::maybeFromString($namespaceName); + self::assertSame('function App\\Helpers', (string) $result2); + } + + public function testNamespaceNameMaybeFromStringWithImportable() : void + { + $fullyQualified = new FullyQualified('App\\Models\\User'); + $className = new ClassName('Helper'); + + // Test with FullyQualified + $result1 = NamespaceName::maybeFromString($fullyQualified); + self::assertSame('App\\Models\\User', $result1->namespace); + + // Test with ClassName + $result2 = NamespaceName::maybeFromString($className); + self::assertSame('Helper', $result2->namespace); + } + + public function testCodeGeneratorImportWithImportableTypes() : void + { + $generator = new CodeGenerator('App\\Controllers'); + + $className = new ClassName('User'); + $fullyQualified = new FullyQualified('App\\Models\\Product'); + $functionName = new FunctionName('sprintf'); + $namespaceName = new NamespaceName('App\\Services'); + + // Test importing different Importable types + $alias1 = $generator->import($className); + $alias2 = $generator->import($fullyQualified); + $alias3 = $generator->import($functionName); + $alias4 = $generator->import($namespaceName); + + self::assertSame('User', $alias1); + self::assertSame('Product', $alias2); + self::assertSame('sprintf', $alias3); + self::assertSame('Services', $alias4); + } + + public function testCodeGeneratorImportByParentWithImportableTypes() : void + { + $generator = new CodeGenerator('App\\Controllers'); + + $fullyQualified = new FullyQualified('App\\Models\\User'); + $className = new ClassName('Helper'); + + // Test importByParent with different Importable types + $result1 = $generator->importByParent($fullyQualified); + $result2 = $generator->importByParent($className); + + self::assertSame('Models\\User', $result1); + self::assertSame('Helper', $result2); + } + + public function testAliasConstructorWithImportableTypes() : void + { + $fullyQualified = new FullyQualified('App\\Models\\User'); + $functionName = new FunctionName('App\\Helpers\\format'); + $namespaceName = new NamespaceName('App\\Services'); + $className = new ClassName('Helper'); + + // Test creating aliases with different Importable types + $alias1 = new Alias('UserModel', $fullyQualified); + $alias2 = new Alias('format', $functionName); + $alias3 = new Alias('Services', $namespaceName); + $alias4 = new Alias('HelperClass', $className); + + self::assertSame('UserModel', $alias1->alias); + self::assertSame($fullyQualified, $alias1->target); + + self::assertSame('format', $alias2->alias); + self::assertSame($functionName, $alias2->target); + + self::assertSame('Services', $alias3->alias); + self::assertSame($namespaceName, $alias3->target); + + self::assertSame('HelperClass', $alias4->alias); + self::assertSame($className, $alias4->target); + } +} From 37be26e1c9ec9faabcda33be4a393fb80baa81f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 07:47:03 +0000 Subject: [PATCH 3/4] Fix code style whitespace issue Co-authored-by: ruudk <104180+ruudk@users.noreply.github.com> --- tests/ImportableTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImportableTest.php b/tests/ImportableTest.php index 9e89fee..e454cb1 100644 --- a/tests/ImportableTest.php +++ b/tests/ImportableTest.php @@ -18,7 +18,7 @@ public function testImportableInterfaceBasicUsage() : void // Test that all classes have the required interface methods and can be converted to strings $importables = [$className, $fullyQualified, $functionName, $namespaceName, $alias]; - + foreach ($importables as $importable) { // Test basic Importable interface functionality self::assertTrue($importable->equals($importable)); From 34d22802e9902a3acbf2d7e12b76ec1e6fe9ad1d Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Sat, 23 Aug 2025 09:50:54 +0200 Subject: [PATCH 4/4] Tweak --- src/CodeGenerator.php | 2 +- src/FullyQualified.php | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/CodeGenerator.php b/src/CodeGenerator.php index 9ccf355..4156ef0 100644 --- a/src/CodeGenerator.php +++ b/src/CodeGenerator.php @@ -231,7 +231,7 @@ public function importByParent(Importable | string $name) : string // Import the namespace and return the alias with class name return (string) new FullyQualified( $this->import($fqcn->namespace), - (string) $fqcn->className, + $fqcn->className, ); } diff --git a/src/FullyQualified.php b/src/FullyQualified.php index f7c8672..a4bb88f 100644 --- a/src/FullyQualified.php +++ b/src/FullyQualified.php @@ -13,11 +13,17 @@ public ?NamespaceName $namespace; public function __construct( - string $part, - string ...$parts, + Importable | string $part, + Importable | string ...$parts, ) { $flattened = array_filter( - explode('\\', implode('\\', [$part, ...$parts])), + explode( + '\\', + implode( + '\\', + array_map(strval(...), [$part, ...$parts]), + ), + ), fn($p) => $p !== '', ); @@ -28,9 +34,7 @@ public function __construct( $classNamePart = array_pop($flattened); $this->className = new ClassName($classNamePart); - $this->namespace = $flattened !== [] - ? new NamespaceName(implode('\\', $flattened)) - : null; + $this->namespace = $flattened !== [] ? new NamespaceName(implode('\\', $flattened)) : null; } /**