Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>tags/v26.0.0beta1
@@ -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+$?)) | |||
@@ -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) | |||
) | |||
); | |||
} | |||
} | |||
} |
@@ -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(); | |||
@@ -462,5 +462,9 @@ interface Storage extends IStorage { | |||
*/ | |||
public function setAvailability($isAvailable); | |||
/** | |||
* @since 12.0.0 | |||
* @return mixed | |||
*/ | |||
public function needsPartFile(); | |||
} |
@@ -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 |
@@ -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 |
@@ -9,6 +9,7 @@ | |||
> | |||
<plugins> | |||
<plugin filename="build/psalm/AppFrameworkTainter.php" /> | |||
<plugin filename="build/psalm/OcpSinceChecker.php" /> | |||
</plugins> | |||
<projectFiles> | |||
<directory name="lib/public"/> |