From 1807e1c1f2fd39a81814044b871ee2998c456269 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 26 Oct 2026 10:43:53 +0100 Subject: [PATCH] fix(since): Ensure provided since version is from the current branch Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Joas Schilling --- build/psalm/OcpSinceChecker.php | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/build/psalm/OcpSinceChecker.php b/build/psalm/OcpSinceChecker.php index 9323b07feb7a6..964693d216e7f 100644 --- a/build/psalm/OcpSinceChecker.php +++ b/build/psalm/OcpSinceChecker.php @@ -140,6 +140,9 @@ private static function checkClassComment(ClassLike $stmt, FileSource $statement ) ); } + foreach ($parsedDocblock->tags['since'] ?? [] as $since) { + self::checkSinceIsReleasable(trim($since), $statementsSource, $stmt); + } if (isset($parsedDocblock->tags['depreacted'])) { IssueBuffer::maybeAdd( @@ -184,6 +187,9 @@ private static function checkStatementComment(Stmt $stmt, FileSource $statements ) ); } + foreach ($parsedDocblock->tags['since'] ?? [] as $since) { + self::checkSinceIsReleasable(trim($since), $statementsSource, $stmt); + } if (isset($parsedDocblock->tags['depreacted'])) { IssueBuffer::maybeAdd( @@ -213,9 +219,46 @@ private static function checkAttributeHasValidSinceVersion(\PhpParser\Node\Attri new CodeLocation($statementsSource, $stmt) ) ); + } else { + self::checkSinceIsReleasable($arg->value->value, $statementsSource, $stmt); } } } } } + + /** + * A since version must not be newer than the next patch release of the + * current branch, so backports with an unadjusted since fail + */ + private static function checkSinceIsReleasable(string $since, FileSource $statementsSource, \PhpParser\Node $stmt): void { + // Backports may list multiple versions: "33.0.1, 32.0.7" + if (!preg_match('/^\d+(\.\d+)*(\s*,\s*\d+(\.\d+)*)*/', $since, $list)) { + return; + } + + $maxVersion = self::getMaxSinceVersion(); + foreach (preg_split('/\s*,\s*/', $list[0]) as $version) { + if (version_compare($version, $maxVersion, '<=')) { + return; + } + } + + IssueBuffer::maybeAdd( + new InvalidDocblock( + 'Since version ' . $list[0] . ' is newer than the next release ' . $maxVersion . ' of this branch, please adjust it to the version the change is released in.', + new CodeLocation($statementsSource, $stmt) + ) + ); + } + + private static function getMaxSinceVersion(): string { + static $maxVersion = null; + if ($maxVersion === null) { + require __DIR__ . '/../../version.php'; + /** @var array{0: int, 1: int, 2: int, 3: int} $OC_Version */ + $maxVersion = $OC_Version[0] . '.' . $OC_Version[1] . '.' . ($OC_Version[2] + 1); + } + return $maxVersion; + } }