From 2baedfd0c2b3dfc23897550b022eaab54ea37092 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Sun, 22 Feb 2026 04:40:02 +0800 Subject: [PATCH 1/2] chore: test that `@deprecated` items have version numbers --- tests/system/AutoReview/FrameworkCodeTest.php | 91 ++++++++++++++++++- utils/phpstan-baseline/loader.neon | 2 +- .../missingType.iterableValue.neon | 17 +--- 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/tests/system/AutoReview/FrameworkCodeTest.php b/tests/system/AutoReview/FrameworkCodeTest.php index 4e59afd0f8c5..ca9c32bc91c4 100644 --- a/tests/system/AutoReview/FrameworkCodeTest.php +++ b/tests/system/AutoReview/FrameworkCodeTest.php @@ -30,10 +30,22 @@ final class FrameworkCodeTest extends TestCase { /** - * Cache of discovered test class names. + * Cache of source filenames. + * + * @var list + */ + private static array $sourceFiles = []; + + /** + * Cache of test class names. + * + * @var list */ private static array $testClasses = []; + /** + * @var list + */ private static array $recognizedGroupAttributeNames = [ 'AutoReview', 'CacheLive', @@ -42,6 +54,42 @@ final class FrameworkCodeTest extends TestCase 'SeparateProcess', ]; + public function testDeprecationsAreProperlyVersioned(): void + { + $deprecationsWithoutVersion = []; + + foreach ($this->getSourceFiles() as $file) { + $lines = file($file, FILE_IGNORE_NEW_LINES); + + if ($lines === false) { + continue; + } + + foreach ($lines as $number => $line) { + if (! str_contains($line, '@deprecated')) { + continue; + } + + if (preg_match('/((?:\/\*)?\*|\/\/)\s+@deprecated\s+(?P.+?)(?:\s*\*\s*)?$/', $line, $matches) === 1) { + $deprecationText = trim($matches['text']); + + if (preg_match('/^v?\d+\.\d+/', $deprecationText) !== 1) { + $deprecationsWithoutVersion[] = sprintf('%s:%d', $file, ++$number); + } + } + } + } + + $this->assertCount( + 0, + $deprecationsWithoutVersion, + sprintf( + "The following lines contain @deprecated annotations without a version number:\n%s", + implode("\n", array_map(static fn (string $location): string => " * {$location}", $deprecationsWithoutVersion)), + ), + ); + } + /** * @param class-string $class */ @@ -87,6 +135,9 @@ public static function provideEachTestClassHasCorrectGroupAttributeName(): itera } } + /** + * @return list + */ private static function getTestClasses(): array { if (self::$testClasses !== []) { @@ -94,7 +145,6 @@ private static function getTestClasses(): array } helper('filesystem'); - $directory = set_realpath(dirname(__DIR__), true); $iterator = new RecursiveIteratorIterator( @@ -145,4 +195,41 @@ static function (SplFileInfo $file) use ($directory): string { return $testClasses; } + + /** + * @return list + */ + private function getSourceFiles(): array + { + if (self::$sourceFiles !== []) { + return self::$sourceFiles; + } + + helper('filesystem'); + $phpFiles = []; + $basePath = dirname(__DIR__, 3); + + foreach (['system', 'app', 'tests'] as $dir) { + $directory = set_realpath($basePath . DIRECTORY_SEPARATOR . $dir, true); + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $directory, + FilesystemIterator::SKIP_DOTS, + ), + RecursiveIteratorIterator::CHILD_FIRST, + ); + + /** @var SplFileInfo $file */ + foreach ($iterator as $file) { + if ($file->isFile() && str_ends_with($file->getPathname(), '.php')) { + $phpFiles[] = $file->getRealPath(); + } + } + } + + self::$sourceFiles = $phpFiles; + + return $phpFiles; + } } diff --git a/utils/phpstan-baseline/loader.neon b/utils/phpstan-baseline/loader.neon index 43c1f7e4d896..0159006ad13e 100644 --- a/utils/phpstan-baseline/loader.neon +++ b/utils/phpstan-baseline/loader.neon @@ -1,4 +1,4 @@ -# total 2106 errors +# total 2102 errors includes: - argument.type.neon diff --git a/utils/phpstan-baseline/missingType.iterableValue.neon b/utils/phpstan-baseline/missingType.iterableValue.neon index 709e35a6f3c1..ae91b467abde 100644 --- a/utils/phpstan-baseline/missingType.iterableValue.neon +++ b/utils/phpstan-baseline/missingType.iterableValue.neon @@ -1,4 +1,4 @@ -# total 1248 errors +# total 1244 errors parameters: ignoreErrors: @@ -4712,26 +4712,11 @@ parameters: count: 1 path: ../../tests/system/AutoReview/ComposerJsonTest.php - - - message: '#^Method CodeIgniter\\AutoReview\\FrameworkCodeTest\:\:getTestClasses\(\) return type has no value type specified in iterable type array\.$#' - count: 1 - path: ../../tests/system/AutoReview/FrameworkCodeTest.php - - message: '#^Method CodeIgniter\\AutoReview\\FrameworkCodeTest\:\:provideEachTestClassHasCorrectGroupAttributeName\(\) return type has no value type specified in iterable type iterable\.$#' count: 1 path: ../../tests/system/AutoReview/FrameworkCodeTest.php - - - message: '#^Property CodeIgniter\\AutoReview\\FrameworkCodeTest\:\:\$recognizedGroupAttributeNames type has no value type specified in iterable type array\.$#' - count: 1 - path: ../../tests/system/AutoReview/FrameworkCodeTest.php - - - - message: '#^Property CodeIgniter\\AutoReview\\FrameworkCodeTest\:\:\$testClasses type has no value type specified in iterable type array\.$#' - count: 1 - path: ../../tests/system/AutoReview/FrameworkCodeTest.php - - message: '#^Method CodeIgniter\\CLI\\CLITest\:\:provideTable\(\) return type has no value type specified in iterable type iterable\.$#' count: 1 From aaa7d90933b4fa4f8e00fb71659260c06b0d0449 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Sun, 22 Feb 2026 05:17:46 +0800 Subject: [PATCH 2/2] Add guidance on deprecations --- contributing/internals.md | 25 +++++++++++++++++++++++++ system/Language/en/Email.php | 3 ++- system/Language/en/Images.php | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/contributing/internals.md b/contributing/internals.md index 124d19209ac8..35a8dd4d444a 100644 --- a/contributing/internals.md +++ b/contributing/internals.md @@ -44,6 +44,31 @@ afraid to use it when it's needed and can help things. - Start simple, refactor as necessary to achieve clean separation of code, but don't overdo it. +## Deprecations + +Deprecations happen when code no longer fits its purpose or is superseded by better solutions. +In such cases, it's important that deprecations are documented clearly to guide developers during +their upgrade process. + +- Add `@deprecated` tags in the doc block of deprecated functions, methods, classes, etc. Include + the version number where the deprecation was introduced, a description of why it's deprecated, + and recommended replacement(s), if any. If there are multiple alternative approaches, list all of them. + +- Do not add `@deprecated` tags to a function/method if you only mean to deprecate its parameter(s). + This will cause the whole function/method to be marked as deprecated by IDEs. Instead, trigger + a deprecation inside the function/method body if those parameters are passed. Ensure to include + the version number since when the deprecation occurred. Optionally, but encouraged, add a `@todo` + comment so that it can be found later on. + +- User-facing deprecation warnings should also be triggered via the framework's deprecation handling + mechanism (e.g., `@trigger_error()` or error log entries) to alert end users. + +- Do not add new tests for deprecated code paths. Instead, use tools and static analysis to ensure + that no code within the framework or official packages is using the deprecated functionality. + +- Document all deprecations in the changelog file for that release, under a "Deprecations" + section, so users are informed when upgrading. + ## Testing Any new packages submitted to the framework must be accompanied by unit diff --git a/system/Language/en/Email.php b/system/Language/en/Email.php index 44d4c03cae3e..03073ad9af81 100644 --- a/system/Language/en/Email.php +++ b/system/Language/en/Email.php @@ -34,6 +34,7 @@ 'SMTPAuthPassword' => 'Failed to authenticate password. Error: {0}', 'SMTPDataFailure' => 'Unable to send data: {0}', 'exitStatus' => 'Exit status code: {0}', - // @deprecated + + // @deprecated v4.7.0 'failedSMTPLogin' => 'Failed to send AUTH LOGIN command. Error: {0}', ]; diff --git a/system/Language/en/Images.php b/system/Language/en/Images.php index 2af9e0a2d1d3..f5dfc9435ea8 100644 --- a/system/Language/en/Images.php +++ b/system/Language/en/Images.php @@ -33,6 +33,6 @@ 'invalidDirection' => 'Flip direction can be only "vertical" or "horizontal". Given: "{0}"', 'exifNotSupported' => 'Reading EXIF data is not supported by this PHP installation.', - // @deprecated + // @deprecated 4.7.0 'libPathInvalid' => 'The path to your image library is not correct. Please set the correct path in your image preferences. "{0}"', ];