diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff97efcc..df5f3394 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: with: php-version: 8.3 coverage: none - tools: phpstan:1.10, cs2pr + tools: phpstan:2.1, cs2pr - name: Checkout code uses: actions/checkout@v4 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index fd71c4e8..7c7dd69f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 8 reportUnmatchedIgnoredErrors: false paths: - src diff --git a/src/Api/Issue/GithubIssueApi.php b/src/Api/Issue/GithubIssueApi.php index 424ca1fd..bf55f80c 100644 --- a/src/Api/Issue/GithubIssueApi.php +++ b/src/Api/Issue/GithubIssueApi.php @@ -3,8 +3,6 @@ namespace App\Api\Issue; use App\Model\Repository; -use App\Service\TaskHandler\CloseDraftHandler; -use App\Service\TaskHandler\CloseStaleIssuesHandler; use Github\Api\Issue; use Github\Api\Issue\Comments; use Github\Api\Search; @@ -21,7 +19,7 @@ public function __construct( ) { } - public function open(Repository $repository, string $title, string $body, array $labels) + public function open(Repository $repository, string $title, string $body, array $labels): void { $params = [ 'title' => $title, @@ -43,7 +41,7 @@ public function open(Repository $repository, string $title, string $body, array } } - public function lastCommentWasMadeByBot(Repository $repository, $number): bool + public function lastCommentWasMadeByBot(Repository $repository, int $number): bool { $allComments = $this->issueCommentApi->all($repository->getVendor(), $repository->getName(), $number, ['per_page' => 100]); $lastComment = $allComments[count($allComments) - 1] ?? []; @@ -51,18 +49,12 @@ public function lastCommentWasMadeByBot(Repository $repository, $number): bool return $this->botUsername === ($lastComment['user']['login'] ?? null); } - public function show(Repository $repository, $issueNumber): array + public function show(Repository $repository, int $issueNumber): array { return $this->issueApi->show($repository->getVendor(), $repository->getName(), $issueNumber); } - /** - * Close an issue and mark it as "not_planned". - * - * @see CloseDraftHandler - * @see CloseStaleIssuesHandler - */ - public function close(Repository $repository, $issueNumber): void + public function close(Repository $repository, int $issueNumber): void { $this->issueApi->update( $repository->getVendor(), @@ -78,7 +70,7 @@ public function close(Repository $repository, $issueNumber): void /** * This will comment on both Issues and Pull Requests. */ - public function commentOnIssue(Repository $repository, $issueNumber, string $commentBody) + public function commentOnIssue(Repository $repository, int $issueNumber, string $commentBody): void { $this->issueCommentApi->create( $repository->getVendor(), diff --git a/src/Api/Issue/IssueApi.php b/src/Api/Issue/IssueApi.php index 10eb751e..9e339b3b 100644 --- a/src/Api/Issue/IssueApi.php +++ b/src/Api/Issue/IssueApi.php @@ -14,19 +14,27 @@ interface IssueApi { /** * Open new issue or update existing issue. + * + * @param array $labels */ - public function open(Repository $repository, string $title, string $body, array $labels); + public function open(Repository $repository, string $title, string $body, array $labels): void; - public function show(Repository $repository, $issueNumber): array; + /** + * @return array + */ + public function show(Repository $repository, int $issueNumber): array; - public function commentOnIssue(Repository $repository, $issueNumber, string $commentBody); + public function commentOnIssue(Repository $repository, int $issueNumber, string $commentBody): void; - public function lastCommentWasMadeByBot(Repository $repository, $number): bool; + public function lastCommentWasMadeByBot(Repository $repository, int $number): bool; + /** + * @return iterable> + */ public function findStaleIssues(Repository $repository, \DateTimeImmutable $noUpdateAfter): iterable; /** * Close an issue and mark it as "not_planned". */ - public function close(Repository $repository, $issueNumber): void; + public function close(Repository $repository, int $issueNumber): void; } diff --git a/src/Api/Issue/NullIssueApi.php b/src/Api/Issue/NullIssueApi.php index e4517e2b..30b0c139 100644 --- a/src/Api/Issue/NullIssueApi.php +++ b/src/Api/Issue/NullIssueApi.php @@ -6,20 +6,20 @@ class NullIssueApi implements IssueApi { - public function open(Repository $repository, string $title, string $body, array $labels) + public function open(Repository $repository, string $title, string $body, array $labels): void { } - public function show(Repository $repository, $issueNumber): array + public function show(Repository $repository, int $issueNumber): array { return []; } - public function commentOnIssue(Repository $repository, $issueNumber, string $commentBody) + public function commentOnIssue(Repository $repository, int $issueNumber, string $commentBody): void { } - public function lastCommentWasMadeByBot(Repository $repository, $number): bool + public function lastCommentWasMadeByBot(Repository $repository, int $number): bool { return false; } @@ -29,7 +29,7 @@ public function findStaleIssues(Repository $repository, \DateTimeImmutable $noUp return []; } - public function close(Repository $repository, $issueNumber): void + public function close(Repository $repository, int $issueNumber): void { } } diff --git a/src/Api/Label/GithubLabelApi.php b/src/Api/Label/GithubLabelApi.php index e72b1959..bfa2ba45 100644 --- a/src/Api/Label/GithubLabelApi.php +++ b/src/Api/Label/GithubLabelApi.php @@ -18,7 +18,7 @@ class GithubLabelApi implements LabelApi /** * In memory cache for specific issues. * - * @var array> + * @var array> */ private array $labelCache = []; @@ -30,7 +30,7 @@ public function __construct( ) { } - public function getIssueLabels($issueNumber, Repository $repository): array + public function getIssueLabels(int $issueNumber, Repository $repository): array { $key = $this->getCacheKey($issueNumber, $repository); if (!isset($this->labelCache[$key])) { @@ -54,12 +54,12 @@ public function getIssueLabels($issueNumber, Repository $repository): array return $labels; } - public function addIssueLabel($issueNumber, string $label, Repository $repository) + public function addIssueLabel(int $issueNumber, string $label, Repository $repository): void { $this->addIssueLabels($issueNumber, [$label], $repository); } - public function removeIssueLabel($issueNumber, string $label, Repository $repository) + public function removeIssueLabel(int $issueNumber, string $label, Repository $repository): void { $key = $this->getCacheKey($issueNumber, $repository); if (isset($this->labelCache[$key]) && !isset($this->labelCache[$key][$label])) { @@ -81,7 +81,7 @@ public function removeIssueLabel($issueNumber, string $label, Repository $reposi } } - public function addIssueLabels($issueNumber, array $labels, Repository $repository) + public function addIssueLabels(int $issueNumber, array $labels, Repository $repository): void { $key = $this->getCacheKey($issueNumber, $repository); $labelsToAdd = []; @@ -115,31 +115,13 @@ public function getAllLabelsForRepository(Repository $repository): array } /** - * @return string[] + * @return array */ - public function getComponentLabelsForRepository(Repository $repository): array - { - $key = 'component_labels_'.sha1($repository->getFullName()); - - return $this->cache->get($key, function (ItemInterface $item) use ($repository) { - $labels = $this->getAllLabels($repository); - $item->expiresAfter(86400); - $componentLabels = []; - foreach ($labels as $label) { - if ('dddddd' === strtolower($label['color'])) { - $componentLabels[] = $label['name']; - } - } - - return $componentLabels; - }); - } - private function getAllLabels(Repository $repository): array { $key = 'labels_'.sha1($repository->getFullName()); - return $this->cache->get($key, function (ItemInterface $item) use ($repository) { + return $this->cache->get($key, function (ItemInterface $item) use ($repository): array { $labels = $this->resultPager->fetchAll($this->labelsApi, 'all', [$repository->getVendor(), $repository->getName()]); $item->expiresAfter(604800); @@ -147,7 +129,7 @@ private function getAllLabels(Repository $repository): array }); } - private function getCacheKey($issueNumber, Repository $repository) + private function getCacheKey(int $issueNumber, Repository $repository): string { return sprintf('%s_%s_%s', $issueNumber, $repository->getVendor(), $repository->getName()); } diff --git a/src/Api/Label/LabelApi.php b/src/Api/Label/LabelApi.php index 42cf6f2d..b21ba242 100644 --- a/src/Api/Label/LabelApi.php +++ b/src/Api/Label/LabelApi.php @@ -9,21 +9,22 @@ */ interface LabelApi { - public function getIssueLabels($issueNumber, Repository $repository): array; - - public function addIssueLabel($issueNumber, string $label, Repository $repository); + /** + * @return string[] + */ + public function getIssueLabels(int $issueNumber, Repository $repository): array; - public function removeIssueLabel($issueNumber, string $label, Repository $repository); + public function addIssueLabel(int $issueNumber, string $label, Repository $repository): void; - public function addIssueLabels($issueNumber, array $labels, Repository $repository); + public function removeIssueLabel(int $issueNumber, string $label, Repository $repository): void; /** - * @return string[] + * @param string[] $labels */ - public function getAllLabelsForRepository(Repository $repository): array; + public function addIssueLabels(int $issueNumber, array $labels, Repository $repository): void; /** * @return string[] */ - public function getComponentLabelsForRepository(Repository $repository): array; + public function getAllLabelsForRepository(Repository $repository): array; } diff --git a/src/Api/Label/NullLabelApi.php b/src/Api/Label/NullLabelApi.php index c0e34e85..1c3cf466 100644 --- a/src/Api/Label/NullLabelApi.php +++ b/src/Api/Label/NullLabelApi.php @@ -8,20 +8,20 @@ class NullLabelApi implements LabelApi { - public function getIssueLabels($issueNumber, Repository $repository): array + public function getIssueLabels(int $issueNumber, Repository $repository): array { return []; } - public function addIssueLabel($issueNumber, string $label, Repository $repository) + public function addIssueLabel(int $issueNumber, string $label, Repository $repository): void { } - public function removeIssueLabel($issueNumber, string $label, Repository $repository) + public function removeIssueLabel(int $issueNumber, string $label, Repository $repository): void { } - public function addIssueLabels($issueNumber, array $labels, Repository $repository) + public function addIssueLabels(int $issueNumber, array $labels, Repository $repository): void { } @@ -29,9 +29,4 @@ public function getAllLabelsForRepository(Repository $repository): array { return []; } - - public function getComponentLabelsForRepository(Repository $repository): array - { - return []; - } } diff --git a/src/Api/Label/StaticLabelApi.php b/src/Api/Label/StaticLabelApi.php index 9b7f283d..bd4cbb15 100644 --- a/src/Api/Label/StaticLabelApi.php +++ b/src/Api/Label/StaticLabelApi.php @@ -13,28 +13,25 @@ */ class StaticLabelApi extends NullLabelApi { - public function getComponentLabelsForRepository(Repository $repository): array - { - return [ - 'Asset', 'AssetMapper', 'BrowserKit', 'Cache', 'Config', 'Console', - 'Contracts', 'CssSelector', 'Debug', 'DebugBundle', 'DependencyInjection', - 'Doctrine', 'DoctrineBridge', 'DomCrawler', 'Dotenv', 'Emoji', - 'Enhancement', 'ErrorHandler', 'EventDispatcher', 'ExpressionLanguage', - 'Feature', 'Filesystem', 'Finder', 'Form', 'FrameworkBundle', - 'HttpClient', 'HttpFoundation', 'HttpKernel', 'Inflector', 'Intl', 'JsonPath', 'JsonStreamer', 'Ldap', - 'Locale', 'Lock', 'Mailer', 'Messenger', 'Mime', 'MonologBridge', 'Notifier', 'ObjectMapper', - 'OptionsResolver', 'PasswordHasher', 'PhpUnitBridge', 'Process', 'PropertyAccess', - 'PropertyInfo', 'ProxyManagerBridge', 'PsrHttpMessageBridge', 'RemoteEvent', 'Routing', - 'Scheduler', 'Security', 'SecurityBundle', 'Serializer', 'Stopwatch', 'String', - 'Templating', 'Translation', 'TwigBridge', 'TwigBundle', 'TypeInfo', 'Uid', 'Validator', 'VarDumper', - 'VarExporter', 'Webhook', 'WebLink', 'WebProfilerBundle', 'WebServerBundle', 'Workflow', - 'Yaml', - ]; - } + private const array LABELS = [ + 'Asset', 'AssetMapper', 'BrowserKit', 'Cache', 'Config', 'Console', + 'Contracts', 'CssSelector', 'Debug', 'DebugBundle', 'DependencyInjection', + 'Doctrine', 'DoctrineBridge', 'DomCrawler', 'Dotenv', 'Emoji', + 'Enhancement', 'ErrorHandler', 'EventDispatcher', 'ExpressionLanguage', + 'Feature', 'Filesystem', 'Finder', 'Form', 'FrameworkBundle', + 'HttpClient', 'HttpFoundation', 'HttpKernel', 'Inflector', 'Intl', 'JsonPath', 'JsonStreamer', 'Ldap', + 'Locale', 'Lock', 'Mailer', 'Messenger', 'Mime', 'MonologBridge', 'Notifier', 'ObjectMapper', + 'OptionsResolver', 'PasswordHasher', 'PhpUnitBridge', 'Process', 'PropertyAccess', + 'PropertyInfo', 'ProxyManagerBridge', 'PsrHttpMessageBridge', 'RemoteEvent', 'Routing', + 'Scheduler', 'Security', 'SecurityBundle', 'Serializer', 'Stopwatch', 'String', + 'Templating', 'Translation', 'TwigBridge', 'TwigBundle', 'TypeInfo', 'Uid', 'Validator', 'VarDumper', + 'VarExporter', 'Webhook', 'WebLink', 'WebProfilerBundle', 'WebServerBundle', 'Workflow', + 'Yaml', + ]; public function getAllLabelsForRepository(Repository $repository): array { - $labels = $this->getComponentLabelsForRepository($repository); + $labels = self::LABELS; $labels[] = 'BC Break'; $labels[] = 'Bug'; $labels[] = 'Critical'; @@ -47,7 +44,7 @@ public function getAllLabelsForRepository(Repository $repository): array return $labels; } - public function getIssueLabels($issueNumber, Repository $repository): array + public function getIssueLabels(int $issueNumber, Repository $repository): array { return []; } diff --git a/src/Api/Milestone/GithubMilestoneApi.php b/src/Api/Milestone/GithubMilestoneApi.php index efad194f..69f8ef04 100644 --- a/src/Api/Milestone/GithubMilestoneApi.php +++ b/src/Api/Milestone/GithubMilestoneApi.php @@ -12,7 +12,7 @@ class GithubMilestoneApi implements MilestoneApi { /** - * @var string[][] + * @var array> */ private array $cache = []; @@ -22,6 +22,9 @@ public function __construct( ) { } + /** + * @return array + */ private function getMilestones(Repository $repository): array { $key = $this->getCacheKey($repository); diff --git a/src/Api/PullRequest/GithubPullRequestApi.php b/src/Api/PullRequest/GithubPullRequestApi.php index 65789590..317e55df 100644 --- a/src/Api/PullRequest/GithubPullRequestApi.php +++ b/src/Api/PullRequest/GithubPullRequestApi.php @@ -17,12 +17,12 @@ public function __construct( ) { } - public function show(Repository $repository, $number): array + public function show(Repository $repository, int $number): array { return (array) $this->pullRequest->show($repository->getVendor(), $repository->getName(), $number); } - public function updateTitle(Repository $repository, $number, string $title, ?string $body = null): void + public function updateTitle(Repository $repository, int $number, string $title, ?string $body = null): void { $params = ['title' => $title]; diff --git a/src/Api/PullRequest/NullPullRequestApi.php b/src/Api/PullRequest/NullPullRequestApi.php index 31960242..c842a696 100644 --- a/src/Api/PullRequest/NullPullRequestApi.php +++ b/src/Api/PullRequest/NullPullRequestApi.php @@ -11,12 +11,12 @@ */ class NullPullRequestApi implements PullRequestApi { - public function show(Repository $repository, $number): array + public function show(Repository $repository, int $number): array { return []; } - public function updateTitle(Repository $repository, $number, string $title, ?string $body = null): void + public function updateTitle(Repository $repository, int $number, string $title, ?string $body = null): void { } diff --git a/src/Api/PullRequest/PullRequestApi.php b/src/Api/PullRequest/PullRequestApi.php index 8b566d74..d814d5c3 100644 --- a/src/Api/PullRequest/PullRequestApi.php +++ b/src/Api/PullRequest/PullRequestApi.php @@ -14,9 +14,12 @@ */ interface PullRequestApi { - public function show(Repository $repository, $number): array; + /** + * @return array + */ + public function show(Repository $repository, int $number): array; - public function updateTitle(Repository $repository, $number, string $title, ?string $body = null): void; + public function updateTitle(Repository $repository, int $number, string $title, ?string $body = null): void; public function getAuthorCount(Repository $repository, string $author): int; } diff --git a/src/Api/Status/GitHubStatusApi.php b/src/Api/Status/GitHubStatusApi.php index ac147031..a3dcd287 100644 --- a/src/Api/Status/GitHubStatusApi.php +++ b/src/Api/Status/GitHubStatusApi.php @@ -8,20 +8,26 @@ class GitHubStatusApi implements StatusApi { - private static array $statusToLabel = [ + /** + * @var array + */ + private const array STATUS_TO_LABEL = [ Status::NEEDS_REVIEW => 'Status: Needs Review', Status::NEEDS_WORK => 'Status: Needs Work', Status::WORKS_FOR_ME => 'Status: Works for me', Status::REVIEWED => 'Status: Reviewed', ]; + /** + * @var array + */ private array $labelToStatus; public function __construct( private readonly LabelApi $labelsApi, private readonly LoggerInterface $logger, ) { - $this->labelToStatus = array_flip(self::$statusToLabel); + $this->labelToStatus = array_flip(self::STATUS_TO_LABEL); } /** @@ -30,11 +36,11 @@ public function __construct( */ public function setIssueStatus(int $issueNumber, ?string $newStatus, Repository $repository): void { - if (null !== $newStatus && !isset(self::$statusToLabel[$newStatus])) { + if (null !== $newStatus && !isset(self::STATUS_TO_LABEL[$newStatus])) { throw new \InvalidArgumentException(sprintf('Invalid status "%s"', $newStatus)); } - $newLabel = null === $newStatus ? null : self::$statusToLabel[$newStatus]; + $newLabel = null === $newStatus ? null : self::STATUS_TO_LABEL[$newStatus]; $this->logger->info(sprintf('Fetching issue labels for issue %s, repository %s', $issueNumber, $repository->getFullName())); $currentLabels = $this->labelsApi->getIssueLabels($issueNumber, $repository); @@ -69,7 +75,7 @@ public function setIssueStatus(int $issueNumber, ?string $newStatus, Repository } } - public function getIssueStatus($issueNumber, Repository $repository): ?string + public function getIssueStatus(int $issueNumber, Repository $repository): ?string { $currentLabels = $this->labelsApi->getIssueLabels($issueNumber, $repository); @@ -85,6 +91,6 @@ public function getIssueStatus($issueNumber, Repository $repository): ?string public static function getNeedsReviewLabel(): string { - return self::$statusToLabel[Status::NEEDS_REVIEW]; + return self::STATUS_TO_LABEL[Status::NEEDS_REVIEW]; } } diff --git a/src/Command/ListTaskCommand.php b/src/Command/ListTaskCommand.php index 007e2b5b..ca605b10 100644 --- a/src/Command/ListTaskCommand.php +++ b/src/Command/ListTaskCommand.php @@ -41,6 +41,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $limit = 100; + /** @var Task[] $tasks */ $tasks = $this->repository->findBy($criteria, ['verifyAfter' => 'ASC'], $limit); $rows = []; foreach ($tasks as $task) { diff --git a/src/Command/PingStaleIssuesCommand.php b/src/Command/PingStaleIssuesCommand.php index f09b0f44..54bb1376 100644 --- a/src/Command/PingStaleIssuesCommand.php +++ b/src/Command/PingStaleIssuesCommand.php @@ -22,8 +22,8 @@ */ class PingStaleIssuesCommand extends Command { - public const MESSAGE_TWO_AFTER = '+2weeks'; - public const MESSAGE_THREE_AND_CLOSE_AFTER = '+2weeks'; + public const string MESSAGE_TWO_AFTER = '+2weeks'; + public const string MESSAGE_THREE_AND_CLOSE_AFTER = '+2weeks'; protected static $defaultName = 'app:issue:ping-stale'; @@ -69,12 +69,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int } foreach ($issues as $issue) { + /** + * @var array{number: int, name: string, labels: array} $issue + */ $comment = $this->commentGenerator->getComment($this->extractType($issue)); $this->issueApi->commentOnIssue($repository, $issue['number'], $comment); $this->labelApi->addIssueLabel($issue['number'], 'Stalled', $repository); // add a scheduled task to process this issue again after 2 weeks - $this->scheduler->runLater($repository, (int) $issue['number'], Task::ACTION_INFORM_CLOSE_STALE, new \DateTimeImmutable(self::MESSAGE_TWO_AFTER)); + $this->scheduler->runLater($repository, $issue['number'], Task::ACTION_INFORM_CLOSE_STALE, new \DateTimeImmutable(self::MESSAGE_TWO_AFTER)); } return Command::SUCCESS; @@ -83,6 +86,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * Extract type from issue array. Make sure we prioritize labels if there are * more than one type defined. + * + * @param array{number: int, name: string, labels: array} $issue */ private function extractType(array $issue): string { diff --git a/src/Entity/Task.php b/src/Entity/Task.php index 800aa973..48b969ff 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -15,9 +15,9 @@ #[ORM\Table] class Task { - public const ACTION_CLOSE_STALE = 1; - public const ACTION_CLOSE_DRAFT = 2; - public const ACTION_INFORM_CLOSE_STALE = 3; + public const int ACTION_CLOSE_STALE = 1; + public const int ACTION_CLOSE_DRAFT = 2; + public const int ACTION_INFORM_CLOSE_STALE = 3; /** * @var int @@ -30,11 +30,8 @@ class Task #[ORM\Column(type: 'datetime_immutable')] private \DateTimeImmutable $createdAt; - /** - * @var \DateTimeImmutable - */ #[ORM\Column(type: 'datetime_immutable')] - private $updatedAt; + private \DateTimeImmutable $updatedAt; public function __construct( #[ORM\Column(type: 'string')] @@ -44,9 +41,10 @@ public function __construct( #[ORM\Column(type: 'integer')] private readonly int $action, #[ORM\Column(type: 'datetime_immutable')] - private \DateTimeImmutable $verifyAfter) - { + private \DateTimeImmutable $verifyAfter, + ) { $this->createdAt = new \DateTimeImmutable(); + $this->updatedAt = new \DateTimeImmutable(); } public function getId(): int @@ -81,7 +79,7 @@ public function setVerifyAfter(\DateTimeImmutable $verifyAfter): void #[ORM\PrePersist] #[ORM\PreUpdate] - public function updateUpdatedAt() + public function updateUpdatedAt(): void { $this->updatedAt = new \DateTimeImmutable(); } diff --git a/src/Event/GitHubEvent.php b/src/Event/GitHubEvent.php index 95d32523..956aac17 100644 --- a/src/Event/GitHubEvent.php +++ b/src/Event/GitHubEvent.php @@ -10,14 +10,21 @@ */ class GitHubEvent extends Event { + /** @var array */ protected array $responseData = []; + /** + * @param array $data + */ public function __construct( private readonly array $data, private readonly Repository $repository, ) { } + /** + * @return array + */ public function getData(): array { return $this->data; @@ -28,11 +35,17 @@ public function getRepository(): Repository return $this->repository; } + /** + * @return array + */ public function getResponseData(): array { return $this->responseData; } + /** + * @param array $responseData + */ public function setResponseData(array $responseData): void { foreach ($responseData as $k => $v) { diff --git a/src/GitHubEvents.php b/src/GitHubEvents.php index 16fe372d..779a8ea7 100644 --- a/src/GitHubEvents.php +++ b/src/GitHubEvents.php @@ -11,69 +11,69 @@ */ final class GitHubEvents { - /** @Event('\App\Event\GitHubEvent') */ - public const COMMIT_COMMENT = 'github.commit_comment'; + /** @Event("\App\Event\GitHubEvent") */ + public const string COMMIT_COMMENT = 'github.commit_comment'; - /** @Event('\App\Event\GitHubEvent') */ - public const CREATE = 'github.create'; + /** @Event("\App\Event\GitHubEvent") */ + public const string CREATE = 'github.create'; - /** @Event('\App\Event\GitHubEvent') */ - public const DELETE = 'github.delete'; + /** @Event("\App\Event\GitHubEvent") */ + public const string DELETE = 'github.delete'; - /** @Event('\App\Event\GitHubEvent') */ - public const DEPLOYMENT = 'github.deployment'; + /** @Event("\App\Event\GitHubEvent") */ + public const string DEPLOYMENT = 'github.deployment'; - /** @Event('\App\Event\GitHubEvent') */ - public const DEPLOYMENT_STATUS = 'github.deployment_status'; + /** @Event("\App\Event\GitHubEvent") */ + public const string DEPLOYMENT_STATUS = 'github.deployment_status'; - /** @Event('\App\Event\GitHubEvent') */ - public const FORK = 'github.fork'; + /** @Event("\App\Event\GitHubEvent") */ + public const string FORK = 'github.fork'; - /** @Event('\App\Event\GitHubEvent') */ - public const GOLLUM = 'github.gollum'; + /** @Event("\App\Event\GitHubEvent") */ + public const string GOLLUM = 'github.gollum'; - /** @Event('\App\Event\GitHubEvent') */ - public const ISSUE_COMMENT = 'github.issue_comment'; + /** @Event("\App\Event\GitHubEvent") */ + public const string ISSUE_COMMENT = 'github.issue_comment'; - /** @Event('\App\Event\GitHubEvent') */ - public const ISSUES = 'github.issues'; + /** @Event("\App\Event\GitHubEvent") */ + public const string ISSUES = 'github.issues'; - /** @Event('\App\Event\GitHubEvent') */ - public const MEMBER = 'github.member'; + /** @Event("\App\Event\GitHubEvent") */ + public const string MEMBER = 'github.member'; - /** @Event('\App\Event\GitHubEvent') */ - public const MEMBERSHIP = 'github.membership'; + /** @Event("\App\Event\GitHubEvent") */ + public const string MEMBERSHIP = 'github.membership'; - /** @Event('\App\Event\GitHubEvent') */ - public const PAGE_BUILD = 'github.page_build'; + /** @Event("\App\Event\GitHubEvent") */ + public const string PAGE_BUILD = 'github.page_build'; - /** @Event('\App\Event\GitHubEvent') */ - public const IS_PUBLIC = 'github.public'; + /** @Event("\App\Event\GitHubEvent") */ + public const string IS_PUBLIC = 'github.public'; - /** @Event('\App\Event\GitHubEvent') */ - public const PR_REVIEW_COMMENT = 'github.pull_request_review_comment'; + /** @Event("\App\Event\GitHubEvent") */ + public const string PR_REVIEW_COMMENT = 'github.pull_request_review_comment'; - /** @Event('\App\Event\GithubEvent') */ - public const PULL_REQUEST_REVIEW = 'github.pull_request_review'; + /** @Event("\App\Event\GitHubEvent") */ + public const string PULL_REQUEST_REVIEW = 'github.pull_request_review'; - /** @Event('\App\Event\GitHubEvent') */ - public const PULL_REQUEST = 'github.pull_request'; + /** @Event("\App\Event\GitHubEvent") */ + public const string PULL_REQUEST = 'github.pull_request'; - /** @Event('\App\Event\GitHubEvent') */ - public const PUSH = 'github.push'; + /** @Event("\App\Event\GitHubEvent") */ + public const string PUSH = 'github.push'; - /** @Event('\App\Event\GitHubEvent') */ - public const REPOSITORY = 'github.repository'; + /** @Event("\App\Event\GitHubEvent") */ + public const string REPOSITORY = 'github.repository'; - /** @Event('\App\Event\GitHubEvent') */ - public const RELEASE = 'github.release'; + /** @Event("\App\Event\GitHubEvent") */ + public const string RELEASE = 'github.release'; - /** @Event('\App\Event\GitHubEvent') */ - public const STATUS = 'github.status'; + /** @Event("\App\Event\GitHubEvent") */ + public const string STATUS = 'github.status'; - /** @Event('\App\Event\GitHubEvent') */ - public const TEAM_ADD = 'github.team_add'; + /** @Event("\App\Event\GitHubEvent") */ + public const string TEAM_ADD = 'github.team_add'; - /** @Event('\App\Event\GitHubEvent') */ - public const WATCH = 'github.watch'; + /** @Event("\App\Event\GitHubEvent") */ + public const string WATCH = 'github.watch'; } diff --git a/src/Kernel.php b/src/Kernel.php index 1803c8e9..f0af986d 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -17,7 +17,9 @@ class Kernel extends BaseKernel implements CompilerPassInterface public function process(ContainerBuilder $container) { - /** @var array $repositories */ + /** + * @var array $repositories + */ $repositories = $container->getParameter('repositories'); $dispatcherCollection = $container->getDefinition(EventDispatcher::class); diff --git a/src/Repository/TaskRepository.php b/src/Repository/TaskRepository.php index 778b899e..4c4c5a4f 100644 --- a/src/Repository/TaskRepository.php +++ b/src/Repository/TaskRepository.php @@ -8,6 +8,8 @@ /** * @author Tobias Nyholm + * + * @extends ServiceEntityRepository */ class TaskRepository extends ServiceEntityRepository { diff --git a/src/Service/GitHubRequestHandler.php b/src/Service/GitHubRequestHandler.php index cfcecd71..7318eefd 100644 --- a/src/Service/GitHubRequestHandler.php +++ b/src/Service/GitHubRequestHandler.php @@ -25,7 +25,7 @@ public function __construct( } /** - * @return array The response data + * @return array The response data */ public function handle(Request $request): array { @@ -53,12 +53,12 @@ public function handle(Request $request): array } $content = $request->getContent(); - if (!is_string($content)) { + if (!$content) { throw new BadRequestHttpException('Empty request body!'); } $signature = $request->headers->get('X-Hub-Signature'); - if (!is_string($signature)) { + if (!$signature) { throw new BadRequestHttpException('Invalid signature!'); } diff --git a/src/Service/LabelNameExtractor.php b/src/Service/LabelNameExtractor.php index d1c0ed31..246c68d2 100644 --- a/src/Service/LabelNameExtractor.php +++ b/src/Service/LabelNameExtractor.php @@ -13,16 +13,11 @@ */ class LabelNameExtractor { - private static array $labelAliases = [ - 'bridge\doctrine' => 'DoctrineBridge', + private const array LABEL_ALIASES = [ 'bridge/doctrine' => 'DoctrineBridge', - 'bridge\monolog' => 'MonologBridge', 'bridge/monolog' => 'MonologBridge', - 'bridge\phpunit' => 'PhpUnitBridge', 'bridge/phpunit' => 'PhpUnitBridge', - 'bridge\proxymanager' => 'ProxyManagerBridge', 'bridge/proxymanager' => 'ProxyManagerBridge', - 'bridge\twig' => 'TwigBridge', 'bridge/twig' => 'TwigBridge', 'di' => 'DependencyInjection', 'fwb' => 'FrameworkBundle', @@ -41,6 +36,8 @@ public function __construct( /** * Get labels from title string. * Example title: "[PropertyAccess] [RFC] [WIP] Allow custom methods on property accesses". + * + * @return string[] */ public function extractLabels(string $title, Repository $repository): array { @@ -57,14 +54,17 @@ public function extractLabels(string $title, Repository $repository): array } } - $this->logger->debug('Searched for labels in title', ['title' => $title, 'labels' => json_encode($labels)]); + $this->logger->debug('Searched for labels in title', ['title' => $title, 'labels' => \json_encode($labels, \JSON_THROW_ON_ERROR)]); return $labels; } - public function getAliasesForLabel($label) + /** + * @return \Generator + */ + public function getAliasesForLabel(string $label): \Generator { - foreach (self::$labelAliases as $alias => $name) { + foreach (self::LABEL_ALIASES as $alias => $name) { if ($name === $label) { yield $alias; } @@ -79,9 +79,8 @@ public function getAliasesForLabel($label) private function getLabels(Repository $repository): array { $allLabels = $this->labelsApi->getAllLabelsForRepository($repository); - $closure = fn ($s) => strtolower($s); - return array_combine(array_map($closure, $allLabels), $allLabels); + return array_combine(array_map('strtolower', $allLabels), $allLabels); } /** @@ -90,8 +89,6 @@ private function getLabels(Repository $repository): array */ private function fixLabelName(string $label): string { - $labelAliases = self::$labelAliases; - - return $labelAliases[strtolower($label)] ?? $label; + return self::LABEL_ALIASES[strtr($label, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\\', 'abcdefghijklmnopqrstuvwxyz/')] ?? $label; } } diff --git a/src/Service/RepositoryProvider.php b/src/Service/RepositoryProvider.php index 36bb2a89..2b091bee 100644 --- a/src/Service/RepositoryProvider.php +++ b/src/Service/RepositoryProvider.php @@ -14,6 +14,9 @@ class RepositoryProvider */ private array $repositories = []; + /** + * @param array $repositories + */ public function __construct(array $repositories) { foreach ($repositories as $repositoryFullName => $repositoryData) { diff --git a/src/Service/SymfonyVersionProvider.php b/src/Service/SymfonyVersionProvider.php index 15b8ffdb..9e8e93de 100644 --- a/src/Service/SymfonyVersionProvider.php +++ b/src/Service/SymfonyVersionProvider.php @@ -37,6 +37,8 @@ public function getCurrentVersion(): string } /** + * @return array + * * @throws \RuntimeException */ public function getMaintainedVersions(): array diff --git a/src/Service/TaskRunner.php b/src/Service/TaskRunner.php index 956d1f5c..f12867fa 100644 --- a/src/Service/TaskRunner.php +++ b/src/Service/TaskRunner.php @@ -22,7 +22,7 @@ public function __construct( ) { } - public function run(Task $task) + public function run(Task $task): void { try { $this->doRun($task); diff --git a/src/Service/TaskScheduler.php b/src/Service/TaskScheduler.php index 5bb94f0f..4f03a995 100644 --- a/src/Service/TaskScheduler.php +++ b/src/Service/TaskScheduler.php @@ -20,7 +20,7 @@ public function __construct( ) { } - public function runLater(Repository $repository, int $number, int $action, \DateTimeImmutable $checkAt) + public function runLater(Repository $repository, int $number, int $action, \DateTimeImmutable $checkAt): void { $task = new Task($repository->getFullName(), $number, $action, $checkAt); $this->taskRepo->persist($task); diff --git a/src/Subscriber/AbstractStatusChangeSubscriber.php b/src/Subscriber/AbstractStatusChangeSubscriber.php index 11d9a90e..500b9696 100644 --- a/src/Subscriber/AbstractStatusChangeSubscriber.php +++ b/src/Subscriber/AbstractStatusChangeSubscriber.php @@ -8,6 +8,9 @@ abstract class AbstractStatusChangeSubscriber implements EventSubscriberInterface { + /** + * @var array + */ protected static array $triggerWordToStatus = [ 'needs review' => Status::NEEDS_REVIEW, 'needs work' => Status::NEEDS_WORK, @@ -34,7 +37,7 @@ protected function parseStatusFromText(string $body): ?string if (preg_match_all($pattern, $body, $matches)) { // Second subpattern = first status character - return static::$triggerWordToStatus[strtolower(end($matches[1]))]; + return static::$triggerWordToStatus[strtolower(end($matches[1]) ?: '')]; } return null; diff --git a/src/Subscriber/AutoUpdateTitleWithLabelSubscriber.php b/src/Subscriber/AutoUpdateTitleWithLabelSubscriber.php index 0d352360..9965d8a5 100644 --- a/src/Subscriber/AutoUpdateTitleWithLabelSubscriber.php +++ b/src/Subscriber/AutoUpdateTitleWithLabelSubscriber.php @@ -73,7 +73,7 @@ public function onPullRequest(GitHubEvent $event): void } // Clean string from all HTML chars and remove whitespace at the beginning - $prTitle = preg_replace('@^[\h\s]+@u', '', html_entity_decode($prTitle)); + $prTitle = (string) preg_replace('@^[\h\s]+@u', '', html_entity_decode($prTitle)); // Add back labels $prTitle = trim($prPrefix.' '.trim($prTitle)); diff --git a/src/Subscriber/MilestoneNewPRSubscriber.php b/src/Subscriber/MilestoneNewPRSubscriber.php index 032b731f..ccc3bb0c 100644 --- a/src/Subscriber/MilestoneNewPRSubscriber.php +++ b/src/Subscriber/MilestoneNewPRSubscriber.php @@ -16,8 +16,8 @@ class MilestoneNewPRSubscriber implements EventSubscriberInterface public function __construct( private readonly MilestoneApi $milestonesApi, private readonly SymfonyVersionProvider $symfonyVersionProvider, - private $ignoreCurrentVersion = false, - private $ignoreDefaultBranch = false, + private bool $ignoreCurrentVersion = false, + private bool $ignoreDefaultBranch = false, ) { } diff --git a/src/Subscriber/RewriteUnwantedPhrasesSubscriber.php b/src/Subscriber/RewriteUnwantedPhrasesSubscriber.php index 9a66e3e1..04e67629 100644 --- a/src/Subscriber/RewriteUnwantedPhrasesSubscriber.php +++ b/src/Subscriber/RewriteUnwantedPhrasesSubscriber.php @@ -59,7 +59,12 @@ public function onPullRequest(GitHubEvent $event): void ]); } - private function replaceUnwantedPhrases(string $text, &$count) + /** + * @param int<0, max>|null &$count + * + * @param-out int $count + */ + private function replaceUnwantedPhrases(string $text, &$count): string { $replace = [ 'dead code' => 'unused code', diff --git a/src/Subscriber/StatusChangeByCommentSubscriber.php b/src/Subscriber/StatusChangeByCommentSubscriber.php index bbf1bc3b..f240ef13 100644 --- a/src/Subscriber/StatusChangeByCommentSubscriber.php +++ b/src/Subscriber/StatusChangeByCommentSubscriber.php @@ -28,7 +28,9 @@ public function onIssueComment(GitHubEvent $event): void $issueNumber = $data['issue']['number']; $newStatus = $this->parseStatusFromText($data['comment']['body']); - if (Status::REVIEWED === $newStatus && false === $this->isUserAllowedToReview($data)) { + $isUserAllowedToReview = ($data['issue']['user']['login'] !== $data['comment']['user']['login']); + + if (Status::REVIEWED === $newStatus && !$isUserAllowedToReview) { $newStatus = null; } @@ -54,9 +56,4 @@ public static function getSubscribedEvents(): array GitHubEvents::ISSUE_COMMENT => 'onIssueComment', ]; } - - private function isUserAllowedToReview(array $data): bool - { - return $data['issue']['user']['login'] !== $data['comment']['user']['login']; - } } diff --git a/src/Subscriber/StatusChangeByReviewSubscriber.php b/src/Subscriber/StatusChangeByReviewSubscriber.php index 67691500..87b7f694 100644 --- a/src/Subscriber/StatusChangeByReviewSubscriber.php +++ b/src/Subscriber/StatusChangeByReviewSubscriber.php @@ -35,7 +35,6 @@ public function onReview(GitHubEvent $event): void $repository = $event->getRepository(); $pullRequestNumber = $data['pull_request']['number']; - $newStatus = null; // Set status based on review state $newStatus = match (strtolower($data['review']['state'])) { @@ -44,7 +43,10 @@ public function onReview(GitHubEvent $event): void default => $this->parseStatusFromText($data['review']['body']), }; - if (Status::REVIEWED === $newStatus && false === $this->isUserAllowedToReview($data)) { + if ( + Status::REVIEWED === $newStatus + && ($data['pull_request']['user']['login'] === $data['review']['user']['login']) + ) { $newStatus = null; } @@ -94,9 +96,4 @@ public static function getSubscribedEvents(): array GitHubEvents::PULL_REQUEST => 'onReviewRequested', ]; } - - private function isUserAllowedToReview(array $data): bool - { - return $data['pull_request']['user']['login'] !== $data['review']['user']['login']; - } } diff --git a/tests/Subscriber/AutoLabelFromContentSubscriberTest.php b/tests/Subscriber/AutoLabelFromContentSubscriberTest.php index 0e9e070a..46679ca6 100644 --- a/tests/Subscriber/AutoLabelFromContentSubscriberTest.php +++ b/tests/Subscriber/AutoLabelFromContentSubscriberTest.php @@ -2,6 +2,7 @@ namespace App\Tests\Subscriber; +use App\Api\Label\LabelApi; use App\Api\Label\StaticLabelApi; use App\Event\GitHubEvent; use App\GitHubEvents; @@ -14,16 +15,11 @@ class AutoLabelFromContentSubscriberTest extends TestCase { - private $autoLabelSubscriber; + private LabelApi $labelsApi; - private $labelsApi; + private Repository $repository; - private $repository; - - /** - * @var EventDispatcher - */ - private $dispatcher; + private EventDispatcher $dispatcher; protected function setUp(): void { @@ -31,19 +27,19 @@ protected function setUp(): void ->disableOriginalConstructor() ->setMethods(['addIssueLabels']) ->getMock(); - $this->autoLabelSubscriber = new AutoLabelFromContentSubscriber($this->labelsApi, new LabelNameExtractor($this->labelsApi, new NullLogger())); + + $autoLabelSubscriber = new AutoLabelFromContentSubscriber($this->labelsApi, new LabelNameExtractor($this->labelsApi, new NullLogger())); $this->repository = new Repository('weaverryan', 'symfony', null); $this->dispatcher = new EventDispatcher(); - $this->dispatcher->addSubscriber($this->autoLabelSubscriber); + $this->dispatcher->addSubscriber($autoLabelSubscriber); } public function testAutoLabelIssue() { $this->labelsApi->expects($this->once()) ->method('addIssueLabels') - ->with(1234, ['Messenger'], $this->repository) - ->willReturn(null); + ->with(1234, ['Messenger'], $this->repository); $event = new GitHubEvent([ 'action' => 'opened', @@ -70,8 +66,7 @@ public function testAutoLabelPR($prTitle, $prBody, array $expectedNewLabels) { $this->labelsApi->expects($this->once()) ->method('addIssueLabels') - ->with(1234, $expectedNewLabels, $this->repository) - ->willReturn(null); + ->with(1234, $expectedNewLabels, $this->repository); $event = new GitHubEvent([ 'action' => 'opened',