diff options
Diffstat (limited to 'build/psalm/OcpSinceChecker.php')
-rw-r--r-- | build/psalm/OcpSinceChecker.php | 129 |
1 files changed, 104 insertions, 25 deletions
diff --git a/build/psalm/OcpSinceChecker.php b/build/psalm/OcpSinceChecker.php index 92437a8f3ab..38af92cf4ef 100644 --- a/build/psalm/OcpSinceChecker.php +++ b/build/psalm/OcpSinceChecker.php @@ -1,26 +1,10 @@ <?php declare(strict_types=1); - /** - * @copyright 2023 Daniel Kesselberg <mail@danielkesselberg.de> - * - * @author 2023 Daniel Kesselberg <mail@danielkesselberg.de> - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only */ - use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\ClassLike; use Psalm\CodeLocation; @@ -33,17 +17,88 @@ use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent; class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface { public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void { - $stmt = $event->getStmt(); + $classLike = $event->getStmt(); $statementsSource = $event->getStatementsSource(); - self::checkClassComment($stmt, $statementsSource); + if (!str_contains($statementsSource->getFilePath(), '/lib/public/')) { + return; + } - foreach ($stmt->getMethods() as $method) { - self::checkMethodOrConstantComment($method, $statementsSource, 'method'); + $isTesting = str_contains($statementsSource->getFilePath(), '/lib/public/Notification/') + || str_contains($statementsSource->getFilePath(), '/lib/public/Config/') + || str_contains($statementsSource->getFilePath(), 'CalendarEventStatus'); + + if ($isTesting) { + self::checkStatementAttributes($classLike, $statementsSource); + } else { + self::checkClassComment($classLike, $statementsSource); } - foreach ($stmt->getConstants() as $constant) { - self::checkMethodOrConstantComment($constant, $statementsSource, 'constant'); + foreach ($classLike->stmts as $stmt) { + if ($stmt instanceof ClassConst) { + self::checkStatementComment($stmt, $statementsSource, 'constant'); + } + + if ($stmt instanceof ClassMethod) { + self::checkStatementComment($stmt, $statementsSource, 'method'); + } + + if ($stmt instanceof EnumCase) { + if ($isTesting) { + self::checkStatementAttributes($classLike, $statementsSource); + } else { + self::checkStatementComment($stmt, $statementsSource, 'enum'); + } + } + } + } + + private static function checkStatementAttributes(ClassLike $stmt, FileSource $statementsSource): void { + $hasAppFrameworkAttribute = false; + $mustBeConsumable = false; + $isConsumable = false; + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if (in_array($attr->name->getLast(), [ + 'Catchable', + 'Consumable', + 'Dispatchable', + 'Implementable', + 'Listenable', + 'Throwable', + ], true)) { + $hasAppFrameworkAttribute = true; + self::checkAttributeHasValidSinceVersion($attr, $statementsSource); + } + if (in_array($attr->name->getLast(), [ + 'Catchable', + 'Consumable', + 'Listenable', + ], true)) { + $isConsumable = true; + } + if ($attr->name->getLast() === 'ExceptionalImplementable') { + $mustBeConsumable = true; + } + } + } + + if ($mustBeConsumable && !$isConsumable) { + IssueBuffer::maybeAdd( + new InvalidDocblock( + 'Attribute OCP\\AppFramework\\Attribute\\ExceptionalImplementable is only valid on classes that also have OCP\\AppFramework\\Attribute\\Consumable', + new CodeLocation($statementsSource, $stmt) + ) + ); + } + + if (!$hasAppFrameworkAttribute) { + IssueBuffer::maybeAdd( + new InvalidDocblock( + 'At least one of the OCP\\AppFramework\\Attribute attributes is required', + new CodeLocation($statementsSource, $stmt) + ) + ); } } @@ -91,7 +146,7 @@ class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitIn } } - private static function checkMethodOrConstantComment(Stmt $stmt, FileSource $statementsSource, string $type): void { + private static function checkStatementComment(Stmt $stmt, FileSource $statementsSource, string $type): void { $docblock = $stmt->getDocComment(); if ($docblock === null) { @@ -134,4 +189,28 @@ class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitIn ); } } + + private static function checkAttributeHasValidSinceVersion(\PhpParser\Node\Attribute $stmt, FileSource $statementsSource): void { + foreach ($stmt->args as $arg) { + if ($arg->name?->name === 'since') { + if (!$arg->value instanceof \PhpParser\Node\Scalar\String_) { + IssueBuffer::maybeAdd( + new InvalidDocblock( + 'Attribute since argument is not a valid version string', + new CodeLocation($statementsSource, $stmt) + ) + ); + } else { + if (!preg_match('/^[1-9][0-9]*(\.[0-9]+){0,3}$/', $arg->value->value)) { + IssueBuffer::maybeAdd( + new InvalidDocblock( + 'Attribute since argument is not a valid version string', + new CodeLocation($statementsSource, $stmt) + ) + ); + } + } + } + } + } } |