From fea296beff0f10c9cfad9380559a6bbcb366eed4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Dec 2023 10:59:34 +0900 Subject: [PATCH 1/7] docs: make PHPDoc types more accurate --- system/Autoloader/Autoloader.php | 7 ++++--- system/Config/AutoloadConfig.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php index 4c6fed350fe2..c6ddd84f2efc 100644 --- a/system/Autoloader/Autoloader.php +++ b/system/Autoloader/Autoloader.php @@ -63,14 +63,14 @@ class Autoloader /** * Stores namespaces as key, and path as values. * - * @var array> + * @var array> */ protected $prefixes = []; /** * Stores class name as key, and path as values. * - * @var array + * @var array */ protected $classmap = []; @@ -215,7 +215,8 @@ public function addNamespace($namespace, ?string $path = null) * * If a prefix param is set, returns only paths to the given prefix. * - * @return array + * @return array>|list + * @phpstan-return ($prefix is null ? array> : list) */ public function getNamespace(?string $prefix = null) { diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index f70d07155613..e5f270a58702 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -106,7 +106,7 @@ class AutoloadConfig * searched for within one or more directories as they would if they * were being autoloaded through a namespace. * - * @var array + * @var array */ protected $coreClassmap = [ AbstractLogger::class => SYSTEMPATH . 'ThirdParty/PSR/Log/AbstractLogger.php', From 81edb298884ea67764632a8f0a9522f69dcbe39f Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Dec 2023 11:07:37 +0900 Subject: [PATCH 2/7] feat: support multiple paths for namespace "CodeIgniter" --- system/Autoloader/FileLocator.php | 6 ++---- system/Commands/Utilities/Namespaces.php | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index 2aff0b4a018a..fb6592ff489f 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -237,7 +237,7 @@ protected function getNamespaces() foreach ($this->autoloader->getNamespace() as $prefix => $paths) { foreach ($paths as $path) { if ($prefix === 'CodeIgniter') { - $system = [ + $system[] = [ 'prefix' => $prefix, 'path' => rtrim($path, '\\/') . DIRECTORY_SEPARATOR, ]; @@ -252,9 +252,7 @@ protected function getNamespaces() } } - $namespaces[] = $system; - - return $namespaces; + return array_merge($namespaces, $system); } /** diff --git a/system/Commands/Utilities/Namespaces.php b/system/Commands/Utilities/Namespaces.php index 47450812b4e0..f3f047613eb9 100644 --- a/system/Commands/Utilities/Namespaces.php +++ b/system/Commands/Utilities/Namespaces.php @@ -139,13 +139,13 @@ private function outputCINamespaces(array $params): array $tbody = []; foreach ($config->psr4 as $ns => $paths) { - if (array_key_exists('r', $params)) { - $pathOutput = $this->truncate($paths, $maxLength); - } else { - $pathOutput = $this->truncate(clean_path($paths), $maxLength); - } - foreach ((array) $paths as $path) { + if (array_key_exists('r', $params)) { + $pathOutput = $this->truncate($path, $maxLength); + } else { + $pathOutput = $this->truncate(clean_path($path), $maxLength); + } + $path = realpath($path) ?: $path; $tbody[] = [ From 8af20df90d7b6a64c7f26eedef5b27404530642d Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 30 Dec 2023 09:33:01 +0900 Subject: [PATCH 3/7] config: add namespace path for system test files --- system/Config/AutoloadConfig.php | 2 +- tests/system/Commands/Utilities/NamespacesTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/system/Config/AutoloadConfig.php b/system/Config/AutoloadConfig.php index e5f270a58702..3866ce760a56 100644 --- a/system/Config/AutoloadConfig.php +++ b/system/Config/AutoloadConfig.php @@ -91,7 +91,7 @@ class AutoloadConfig * @var array */ protected $corePsr4 = [ - 'CodeIgniter' => SYSTEMPATH, + 'CodeIgniter' => [SYSTEMPATH, TESTPATH . 'system'], 'Config' => APPPATH . 'Config', 'Tests' => ROOTPATH . 'tests', ]; diff --git a/tests/system/Commands/Utilities/NamespacesTest.php b/tests/system/Commands/Utilities/NamespacesTest.php index 809a3a64d29d..0785e8d1e6c9 100644 --- a/tests/system/Commands/Utilities/NamespacesTest.php +++ b/tests/system/Commands/Utilities/NamespacesTest.php @@ -58,6 +58,7 @@ public function testNamespacesCommandCodeIgniterOnly(): void | Namespace | Path | Found? | +---------------+-------------------------+--------+ | CodeIgniter | ROOTPATH/system | Yes | + | CodeIgniter | ROOTPATH/tests/system | Yes | | Config | APPPATH/Config | Yes | | Tests | ROOTPATH/tests | Yes | | App | ROOTPATH/app | Yes | From 28c40a4e6c786572d5338142819ec3e49d507191 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Dec 2023 11:51:57 +0900 Subject: [PATCH 4/7] fix: make sure return value is a list --- system/Autoloader/FileLocator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/Autoloader/FileLocator.php b/system/Autoloader/FileLocator.php index fb6592ff489f..b2d7bcd1464b 100644 --- a/system/Autoloader/FileLocator.php +++ b/system/Autoloader/FileLocator.php @@ -175,6 +175,8 @@ public function getClassname(string $file): string * 'app/Modules/foo/Config/Routes.php', * 'app/Modules/bar/Config/Routes.php', * ] + * + * @return list */ public function search(string $path, string $ext = 'php', bool $prioritizeApp = true): array { @@ -203,7 +205,7 @@ public function search(string $path, string $ext = 'php', bool $prioritizeApp = } // Remove any duplicates - return array_unique($foundPaths); + return array_values(array_unique($foundPaths)); } /** From 90245793db9dcaf3bd8d93a9d6a1e947a132e10f Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 30 Dec 2023 10:04:10 +0900 Subject: [PATCH 5/7] feat: add GeneratorTrait::normalizeInputClassName() --- system/CLI/GeneratorTrait.php | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/system/CLI/GeneratorTrait.php b/system/CLI/GeneratorTrait.php index d82bd05dc7e2..3a5347df868c 100644 --- a/system/CLI/GeneratorTrait.php +++ b/system/CLI/GeneratorTrait.php @@ -263,6 +263,25 @@ protected function basename(string $filename): string * Parses the class name and checks if it is already qualified. */ protected function qualifyClassName(): string + { + $class = $this->normalizeInputClassName(); + + // Gets the namespace from input. Don't forget the ending backslash! + $namespace = $this->getNamespace() . '\\'; + + if (strncmp($class, $namespace, strlen($namespace)) === 0) { + return $class; // @codeCoverageIgnore + } + + $directoryString = ($this->directory !== null) ? $this->directory . '\\' : ''; + + return $namespace . $directoryString . str_replace('/', '\\', $class); + } + + /** + * Normalize input classname. + */ + private function normalizeInputClassName(): string { // Gets the class name from input. $class = $this->params[0] ?? CLI::getSegment(2); @@ -298,7 +317,7 @@ protected function qualifyClassName(): string } // Trims input, normalize separators, and ensure that all paths are in Pascalcase. - $class = ltrim( + return ltrim( implode( '\\', array_map( @@ -308,17 +327,6 @@ protected function qualifyClassName(): string ), '\\/' ); - - // Gets the namespace from input. Don't forget the ending backslash! - $namespace = $this->getNamespace() . '\\'; - - if (strncmp($class, $namespace, strlen($namespace)) === 0) { - return $class; // @codeCoverageIgnore - } - - $directoryString = ($this->directory !== null) ? $this->directory . '\\' : ''; - - return $namespace . $directoryString . str_replace('/', '\\', $class); } /** From 9ab2708b19a0f577194fd633bb116e23ca589690 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Dec 2023 11:08:16 +0900 Subject: [PATCH 6/7] feat: make:test creates test files in /tests/ directories --- system/Commands/Generators/TestGenerator.php | 108 ++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/system/Commands/Generators/TestGenerator.php b/system/Commands/Generators/TestGenerator.php index d09ecda5ae83..c061d41f31ac 100644 --- a/system/Commands/Generators/TestGenerator.php +++ b/system/Commands/Generators/TestGenerator.php @@ -14,7 +14,9 @@ namespace CodeIgniter\Commands\Generators; use CodeIgniter\CLI\BaseCommand; +use CodeIgniter\CLI\CLI; use CodeIgniter\CLI\GeneratorTrait; +use Config\Services; /** * Generates a skeleton command file. @@ -66,7 +68,8 @@ class TestGenerator extends BaseCommand * @var array */ protected $options = [ - '--force' => 'Force overwrite existing file.', + '--namespace' => 'Set root namespace. Default: "Tests".', + '--force' => 'Force overwrite existing file.', ]; /** @@ -76,9 +79,110 @@ public function run(array $params) { $this->component = 'Test'; $this->template = 'test.tpl.php'; - $this->namespace = 'Tests'; $this->classNameLang = 'CLI.generator.className.test'; $this->generateClass($params); } + + /** + * Gets the namespace from input or the default namespace. + */ + protected function getNamespace(): string + { + if ($this->namespace !== null) { + return $this->namespace; + } + + if ($this->getOption('namespace') !== null) { + return trim( + str_replace( + '/', + '\\', + $this->getOption('namespace') + ), + '\\' + ); + } + + $class = $this->normalizeInputClassName(); + $classPaths = explode('\\', $class); + + $namespaces = Services::autoloader()->getNamespace(); + + while ($classPaths !== []) { + array_pop($classPaths); + $namespace = implode('\\', $classPaths); + + foreach (array_keys($namespaces) as $prefix) { + if ($prefix === $namespace) { + // The input classname is FQCN, and use the namespace. + return $namespace; + } + } + } + + return 'Tests'; + } + + /** + * Builds the test file path from the class name. + * + * @param string $class namespaced classname. + */ + protected function buildPath(string $class): string + { + $namespace = $this->getNamespace(); + + $base = $this->searchTestFilePath($namespace); + + if ($base === null) { + CLI::error( + lang('CLI.namespaceNotDefined', [$namespace]), + 'light_gray', + 'red' + ); + CLI::newLine(); + + return ''; + } + + $realpath = realpath($base); + $base = ($realpath !== false) ? $realpath : $base; + + $file = $base . DIRECTORY_SEPARATOR + . str_replace( + '\\', + DIRECTORY_SEPARATOR, + trim(str_replace($namespace . '\\', '', $class), '\\') + ) . '.php'; + + return implode( + DIRECTORY_SEPARATOR, + array_slice( + explode(DIRECTORY_SEPARATOR, $file), + 0, + -1 + ) + ) . DIRECTORY_SEPARATOR . $this->basename($file); + } + + /** + * Returns test file path for the namespace. + */ + private function searchTestFilePath(string $namespace): ?string + { + $bases = Services::autoloader()->getNamespace($namespace); + + $base = null; + + foreach ($bases as $candidate) { + if (str_contains($candidate, '/tests/')) { + $base = $candidate; + + break; + } + } + + return $base; + } } From 86696e9123e223bbc6649ab970ead277a41f7f73 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 28 Dec 2023 11:25:12 +0900 Subject: [PATCH 7/7] docs: update docs --- user_guide_src/source/cli/cli_generators.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/cli/cli_generators.rst b/user_guide_src/source/cli/cli_generators.rst index 2f8d7c9d55bf..86f577e7d064 100644 --- a/user_guide_src/source/cli/cli_generators.rst +++ b/user_guide_src/source/cli/cli_generators.rst @@ -248,6 +248,7 @@ Argument: Options: ======== +* ``--namespace``: Set the root namespace. Defaults to value of ``Tests``. * ``--force``: Set this flag to overwrite existing files on destination. make:migration