Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>tags/v26.0.0beta1
RESULT=$(($RESULT+$?)) | RESULT=$(($RESULT+$?)) | ||||
php ./build/htaccess-checker.php | php ./build/htaccess-checker.php | ||||
RESULT=$(($RESULT+$?)) | RESULT=$(($RESULT+$?)) | ||||
php ./build/OCPSinceChecker.php | |||||
RESULT=$(($RESULT+$?)) | |||||
php ./build/files-checker.php | php ./build/files-checker.php | ||||
RESULT=$(($RESULT+$?)) | RESULT=$(($RESULT+$?)) | ||||
<?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); | |||||
} |
<?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) | |||||
) | |||||
); | |||||
} | |||||
} | |||||
} |
class RegisterWidgetEvent extends Event { | class RegisterWidgetEvent extends Event { | ||||
private $manager; | private $manager; | ||||
/** | |||||
* @param IManager $manager | |||||
* @since 20.0.0 | |||||
*/ | |||||
public function __construct(IManager $manager) { | public function __construct(IManager $manager) { | ||||
parent::__construct(); | parent::__construct(); | ||||
*/ | */ | ||||
public function setAvailability($isAvailable); | public function setAvailability($isAvailable); | ||||
/** | |||||
* @since 12.0.0 | |||||
* @return mixed | |||||
*/ | |||||
public function needsPartFile(); | public function needsPartFile(); | ||||
} | } |
abstract class ABackend implements GroupInterface { | abstract class ABackend implements GroupInterface { | ||||
/** | /** | ||||
* @deprecated 14.0.0 | * @deprecated 14.0.0 | ||||
* @since 14.0.0 | |||||
* | * | ||||
* @param int $actions The action to check for | * @param int $actions The action to check for | ||||
* @return bool | * @return bool |
abstract class ABackend implements IUserBackend, UserInterface { | abstract class ABackend implements IUserBackend, UserInterface { | ||||
/** | /** | ||||
* @deprecated 14.0.0 | * @deprecated 14.0.0 | ||||
* @since 14.0.0 | |||||
* | * | ||||
* @param int $actions The action to check for | * @param int $actions The action to check for | ||||
* @return bool | * @return bool |
> | > | ||||
<plugins> | <plugins> | ||||
<plugin filename="build/psalm/AppFrameworkTainter.php" /> | <plugin filename="build/psalm/AppFrameworkTainter.php" /> | ||||
<plugin filename="build/psalm/OcpSinceChecker.php" /> | |||||
</plugins> | </plugins> | ||||
<projectFiles> | <projectFiles> | ||||
<directory name="lib/public"/> | <directory name="lib/public"/> |