aboutsummaryrefslogtreecommitdiffstats
path: root/build/psalm/OcpSinceChecker.php
diff options
context:
space:
mode:
Diffstat (limited to 'build/psalm/OcpSinceChecker.php')
-rw-r--r--build/psalm/OcpSinceChecker.php129
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)
+ )
+ );
+ }
+ }
+ }
+ }
+ }
}