ci: migrate ocp since checker to psalm

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
This commit is contained in:
Daniel Kesselberg 2023-01-17 10:47:22 +01:00
parent f867a2d65e
commit 258c919b3c
No known key found for this signature in database
GPG Key ID: 36E3664E099D0614
8 changed files with 126 additions and 126 deletions

View File

@ -10,8 +10,6 @@ php ./build/triple-dot-checker.php
RESULT=$(($RESULT+$?))
php ./build/htaccess-checker.php
RESULT=$(($RESULT+$?))
php ./build/OCPSinceChecker.php
RESULT=$(($RESULT+$?))
php ./build/files-checker.php
RESULT=$(($RESULT+$?))

View File

@ -1,124 +0,0 @@
<?php
/**
* @author Morris Jobke <hey@morrisjobke.de>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
*
* 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/>
*
*/
require_once(dirname(__DIR__) . '/3rdparty/autoload.php');
/**
* Class SinceTagCheckVisitor
*
* this class checks all methods for the presence of the @since tag
*/
class SinceTagCheckVisitor extends \PhpParser\NodeVisitorAbstract {
/** @var string */
protected $namespace = '';
/** @var string */
protected $className = '';
/** @var bool */
protected $deprecatedClass = false;
/** @var array */
protected $errors = [];
public function enterNode(\PhpParser\Node $node) {
if ($this->deprecatedClass) {
return;
}
if ($node instanceof \PhpParser\Node\Stmt\Namespace_) {
$this->namespace = $node->name;
}
if ($node instanceof \PhpParser\Node\Stmt\Interface_ or
$node instanceof \PhpParser\Node\Stmt\Class_) {
$this->className = $node->name;
/** @var \PhpParser\Comment\Doc[] $comments */
$comments = $node->getAttribute('comments');
if (empty($comments)) {
$this->errors[] = 'PHPDoc is needed for ' . $this->namespace . '\\' . $this->className . '::' . $node->name;
return;
}
$comment = $comments[count($comments) - 1];
$text = $comment->getText();
if (strpos($text, '@deprecated') !== false) {
$this->deprecatedClass = true;
}
if ($this->deprecatedClass === false && strpos($text, '@since') === false && strpos($text, '@deprecated') === false) {
$type = $node instanceof \PhpParser\Node\Stmt\Interface_ ? 'interface' : 'class';
$this->errors[] = '@since or @deprecated tag is needed in PHPDoc for ' . $type . ' ' . $this->namespace . '\\' . $this->className;
return;
}
}
if ($node instanceof \PhpParser\Node\Stmt\ClassMethod) {
/** @var \PhpParser\Node\Stmt\ClassMethod $node */
/** @var \PhpParser\Comment\Doc[] $comments */
$comments = $node->getAttribute('comments');
if (empty($comments)) {
$this->errors[] = 'PHPDoc is needed for ' . $this->namespace . '\\' . $this->className . '::' . $node->name;
return;
}
$comment = $comments[count($comments) - 1];
$text = $comment->getText();
if (strpos($text, '@since') === false && strpos($text, '@deprecated') === false) {
$this->errors[] = '@since or @deprecated tag is needed in PHPDoc for ' . $this->namespace . '\\' . $this->className . '::' . $node->name;
return;
}
}
}
public function getErrors() {
return $this->errors;
}
}
echo 'Parsing all files in lib/public for the presence of @since or @deprecated on each method...' . PHP_EOL . PHP_EOL;
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
/* iterate over all .php files in lib/public */
$Directory = new RecursiveDirectoryIterator(dirname(__DIR__) . '/lib/public');
$Iterator = new RecursiveIteratorIterator($Directory);
$Regex = new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
$errors = [];
foreach ($Regex as $file) {
$stmts = $parser->parse(file_get_contents($file[0]));
$visitor = new SinceTagCheckVisitor();
$traverser = new \PhpParser\NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse($stmts);
$errors = array_merge($errors, $visitor->getErrors());
}
if (count($errors)) {
echo join(PHP_EOL, $errors) . PHP_EOL . PHP_EOL;
exit(1);
}

View File

@ -0,0 +1,115 @@
<?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/>
*
*/
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\ClassLike;
use Psalm\CodeLocation;
use Psalm\DocComment;
use Psalm\Exception\DocblockParseException;
use Psalm\FileSource;
use Psalm\Issue\InvalidDocblock;
use Psalm\IssueBuffer;
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface {
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void {
$stmt = $event->getStmt();
$statementsSource = $event->getStatementsSource();
self::checkClassComment($stmt, $statementsSource);
foreach ($stmt->getMethods() as $method) {
self::checkMethodComment($method, $statementsSource);
}
}
private static function checkClassComment(ClassLike $stmt, FileSource $statementsSource): void {
$docblock = $stmt->getDocComment();
if ($docblock === null) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
'PHPDoc is required for classes/interfaces in OCP.',
new CodeLocation($statementsSource, $stmt)
)
);
return;
}
try {
$parsedDocblock = DocComment::parsePreservingLength($docblock);
} catch (DocblockParseException $e) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($statementsSource, $stmt)
)
);
return;
}
if (!isset($parsedDocblock->tags['since'])) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
'@since is required for classes/interfaces in OCP.',
new CodeLocation($statementsSource, $stmt)
)
);
}
}
private static function checkMethodComment(Stmt $stmt, FileSource $statementsSource): void {
$docblock = $stmt->getDocComment();
if ($docblock === null) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
'PHPDoc is required for methods in OCP.',
new CodeLocation($statementsSource, $stmt)
),
);
return;
}
try {
$parsedDocblock = DocComment::parsePreservingLength($docblock);
} catch (DocblockParseException $e) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($statementsSource, $stmt)
)
);
return;
}
if (!isset($parsedDocblock->tags['since'])) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
'@since is required for methods in OCP.',
new CodeLocation($statementsSource, $stmt)
)
);
}
}
}

View File

@ -41,6 +41,10 @@ use OCP\EventDispatcher\Event;
class RegisterWidgetEvent extends Event {
private $manager;
/**
* @param IManager $manager
* @since 20.0.0
*/
public function __construct(IManager $manager) {
parent::__construct();

View File

@ -462,5 +462,9 @@ interface Storage extends IStorage {
*/
public function setAvailability($isAvailable);
/**
* @since 12.0.0
* @return mixed
*/
public function needsPartFile();
}

View File

@ -33,6 +33,7 @@ use OCP\GroupInterface;
abstract class ABackend implements GroupInterface {
/**
* @deprecated 14.0.0
* @since 14.0.0
*
* @param int $actions The action to check for
* @return bool

View File

@ -36,6 +36,7 @@ use OCP\UserInterface;
abstract class ABackend implements IUserBackend, UserInterface {
/**
* @deprecated 14.0.0
* @since 14.0.0
*
* @param int $actions The action to check for
* @return bool

View File

@ -9,6 +9,7 @@
>
<plugins>
<plugin filename="build/psalm/AppFrameworkTainter.php" />
<plugin filename="build/psalm/OcpSinceChecker.php" />
</plugins>
<projectFiles>
<directory name="lib/public"/>