Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>pull/36187/head
parent
f867a2d65e
commit
258c919b3c
@ -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) |
||||
) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue