Browse Source
ci: migrate ocp since checker to psalm
ci: migrate ocp since checker to psalm
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>pull/36187/head
No known key found for this signature in database
GPG Key ID: 36E3664E099D0614
8 changed files with 126 additions and 126 deletions
-
2autotest-checkers.sh
-
124build/OCPSinceChecker.php
-
115build/psalm/OcpSinceChecker.php
-
4lib/public/Dashboard/RegisterWidgetEvent.php
-
4lib/public/Files/Storage.php
-
1lib/public/Group/Backend/ABackend.php
-
1lib/public/User/Backend/ABackend.php
-
1psalm-ocp.xml
@ -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); |
|||
} |
@ -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) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue