OcpSinceChecker.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. use PhpParser\Node\Stmt;
  8. use PhpParser\Node\Stmt\ClassLike;
  9. use Psalm\CodeLocation;
  10. use Psalm\DocComment;
  11. use Psalm\Exception\DocblockParseException;
  12. use Psalm\FileSource;
  13. use Psalm\Issue\InvalidDocblock;
  14. use Psalm\IssueBuffer;
  15. use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
  16. class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface {
  17. public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void {
  18. $classLike = $event->getStmt();
  19. $statementsSource = $event->getStatementsSource();
  20. self::checkClassComment($classLike, $statementsSource);
  21. foreach ($classLike->stmts as $stmt) {
  22. if ($stmt instanceof ClassConst) {
  23. self::checkStatementComment($stmt, $statementsSource, 'constant');
  24. }
  25. if ($stmt instanceof ClassMethod) {
  26. self::checkStatementComment($stmt, $statementsSource, 'method');
  27. }
  28. if ($stmt instanceof EnumCase) {
  29. self::checkStatementComment($stmt, $statementsSource, 'enum');
  30. }
  31. }
  32. }
  33. private static function checkClassComment(ClassLike $stmt, FileSource $statementsSource): void {
  34. $docblock = $stmt->getDocComment();
  35. if ($docblock === null) {
  36. IssueBuffer::maybeAdd(
  37. new InvalidDocblock(
  38. 'PHPDoc is required for classes/interfaces in OCP.',
  39. new CodeLocation($statementsSource, $stmt)
  40. )
  41. );
  42. return;
  43. }
  44. try {
  45. $parsedDocblock = DocComment::parsePreservingLength($docblock);
  46. } catch (DocblockParseException $e) {
  47. IssueBuffer::maybeAdd(
  48. new InvalidDocblock(
  49. $e->getMessage(),
  50. new CodeLocation($statementsSource, $stmt)
  51. )
  52. );
  53. return;
  54. }
  55. if (!isset($parsedDocblock->tags['since'])) {
  56. IssueBuffer::maybeAdd(
  57. new InvalidDocblock(
  58. '@since is required for classes/interfaces in OCP.',
  59. new CodeLocation($statementsSource, $stmt)
  60. )
  61. );
  62. }
  63. if (isset($parsedDocblock->tags['depreacted'])) {
  64. IssueBuffer::maybeAdd(
  65. new InvalidDocblock(
  66. 'Typo in @deprecated for classes/interfaces in OCP.',
  67. new CodeLocation($statementsSource, $stmt)
  68. )
  69. );
  70. }
  71. }
  72. private static function checkStatementComment(Stmt $stmt, FileSource $statementsSource, string $type): void {
  73. $docblock = $stmt->getDocComment();
  74. if ($docblock === null) {
  75. IssueBuffer::maybeAdd(
  76. new InvalidDocblock(
  77. 'PHPDoc is required for ' . $type . 's in OCP.',
  78. new CodeLocation($statementsSource, $stmt)
  79. ),
  80. );
  81. return;
  82. }
  83. try {
  84. $parsedDocblock = DocComment::parsePreservingLength($docblock);
  85. } catch (DocblockParseException $e) {
  86. IssueBuffer::maybeAdd(
  87. new InvalidDocblock(
  88. $e->getMessage(),
  89. new CodeLocation($statementsSource, $stmt)
  90. )
  91. );
  92. return;
  93. }
  94. if (!isset($parsedDocblock->tags['since'])) {
  95. IssueBuffer::maybeAdd(
  96. new InvalidDocblock(
  97. '@since is required for ' . $type . 's in OCP.',
  98. new CodeLocation($statementsSource, $stmt)
  99. )
  100. );
  101. }
  102. if (isset($parsedDocblock->tags['depreacted'])) {
  103. IssueBuffer::maybeAdd(
  104. new InvalidDocblock(
  105. 'Typo in @deprecated for ' . $type . ' in OCP.',
  106. new CodeLocation($statementsSource, $stmt)
  107. )
  108. );
  109. }
  110. }
  111. }