@@ -12,35 +12,35 @@ |
||
| 12 | 12 | |
| 13 | 13 | #[Implementable(since: '9.0.0')] |
| 14 | 14 | interface INotifier { |
| 15 | - /** |
|
| 16 | - * Identifier of the notifier, only use [a-z0-9_] |
|
| 17 | - * |
|
| 18 | - * @return string |
|
| 19 | - * @since 17.0.0 |
|
| 20 | - */ |
|
| 21 | - public function getID(): string; |
|
| 15 | + /** |
|
| 16 | + * Identifier of the notifier, only use [a-z0-9_] |
|
| 17 | + * |
|
| 18 | + * @return string |
|
| 19 | + * @since 17.0.0 |
|
| 20 | + */ |
|
| 21 | + public function getID(): string; |
|
| 22 | 22 | |
| 23 | - /** |
|
| 24 | - * Human-readable name describing the notifier |
|
| 25 | - * |
|
| 26 | - * @return string |
|
| 27 | - * @since 17.0.0 |
|
| 28 | - */ |
|
| 29 | - public function getName(): string; |
|
| 23 | + /** |
|
| 24 | + * Human-readable name describing the notifier |
|
| 25 | + * |
|
| 26 | + * @return string |
|
| 27 | + * @since 17.0.0 |
|
| 28 | + */ |
|
| 29 | + public function getName(): string; |
|
| 30 | 30 | |
| 31 | - /** |
|
| 32 | - * @param INotification $notification |
|
| 33 | - * @param string $languageCode The code of the language that should be used to prepare the notification |
|
| 34 | - * @return INotification |
|
| 35 | - * @throws UnknownNotificationException When the notification was not prepared by a notifier |
|
| 36 | - * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted |
|
| 37 | - * @throws IncompleteParsedNotificationException Only to be thrown by the {@see IManager} |
|
| 38 | - * @since 9.0.0 |
|
| 39 | - * @since 30.0.0 Notifiers should throw {@see UnknownNotificationException} instead of \InvalidArgumentException |
|
| 40 | - * when they did not handle the notification. Throwing \InvalidArgumentException directly is deprecated and will |
|
| 41 | - * be logged as an error in Nextcloud 39. |
|
| 42 | - * @since 30.0.0 Throws {@see IncompleteParsedNotificationException} when not all required fields |
|
| 43 | - * are set at the end of the manager or after a INotifier that claimed to have parsed the notification. |
|
| 44 | - */ |
|
| 45 | - public function prepare(INotification $notification, string $languageCode): INotification; |
|
| 31 | + /** |
|
| 32 | + * @param INotification $notification |
|
| 33 | + * @param string $languageCode The code of the language that should be used to prepare the notification |
|
| 34 | + * @return INotification |
|
| 35 | + * @throws UnknownNotificationException When the notification was not prepared by a notifier |
|
| 36 | + * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted |
|
| 37 | + * @throws IncompleteParsedNotificationException Only to be thrown by the {@see IManager} |
|
| 38 | + * @since 9.0.0 |
|
| 39 | + * @since 30.0.0 Notifiers should throw {@see UnknownNotificationException} instead of \InvalidArgumentException |
|
| 40 | + * when they did not handle the notification. Throwing \InvalidArgumentException directly is deprecated and will |
|
| 41 | + * be logged as an error in Nextcloud 39. |
|
| 42 | + * @since 30.0.0 Throws {@see IncompleteParsedNotificationException} when not all required fields |
|
| 43 | + * are set at the end of the manager or after a INotifier that claimed to have parsed the notification. |
|
| 44 | + */ |
|
| 45 | + public function prepare(INotification $notification, string $languageCode): INotification; |
|
| 46 | 46 | } |
@@ -11,7 +11,7 @@ |
||
| 11 | 11 | |
| 12 | 12 | #[Listenable(since: '32.0.0')] |
| 13 | 13 | enum CalendarEventStatus: string { |
| 14 | - case TENTATIVE = 'TENTATIVE'; |
|
| 15 | - case CONFIRMED = 'CONFIRMED'; |
|
| 16 | - case CANCELLED = 'CANCELLED'; |
|
| 14 | + case TENTATIVE = 'TENTATIVE'; |
|
| 15 | + case CONFIRMED = 'CONFIRMED'; |
|
| 16 | + case CANCELLED = 'CANCELLED'; |
|
| 17 | 17 | }; |
@@ -22,17 +22,17 @@ |
||
| 22 | 22 | #[Consumable(since: '32.0.0')] |
| 23 | 23 | #[Implementable(since: '32.0.0')] |
| 24 | 24 | class ExceptionalImplementable { |
| 25 | - public function __construct( |
|
| 26 | - protected string $app, |
|
| 27 | - protected ?string $class = null, |
|
| 28 | - ) { |
|
| 29 | - } |
|
| 25 | + public function __construct( |
|
| 26 | + protected string $app, |
|
| 27 | + protected ?string $class = null, |
|
| 28 | + ) { |
|
| 29 | + } |
|
| 30 | 30 | |
| 31 | - public function getApp(): string { |
|
| 32 | - return $this->app; |
|
| 33 | - } |
|
| 31 | + public function getApp(): string { |
|
| 32 | + return $this->app; |
|
| 33 | + } |
|
| 34 | 34 | |
| 35 | - public function getClass(): ?string { |
|
| 36 | - return $this->class; |
|
| 37 | - } |
|
| 35 | + public function getClass(): ?string { |
|
| 36 | + return $this->class; |
|
| 37 | + } |
|
| 38 | 38 | } |
@@ -18,17 +18,17 @@ |
||
| 18 | 18 | */ |
| 19 | 19 | #[Consumable(since: '32.0.0')] |
| 20 | 20 | abstract class ASince { |
| 21 | - /** |
|
| 22 | - * @param string $since For shipped apps and server code such as core/ and lib/, |
|
| 23 | - * this should be the server version. For other apps it |
|
| 24 | - * should be the semantic app version. |
|
| 25 | - */ |
|
| 26 | - public function __construct( |
|
| 27 | - protected string $since, |
|
| 28 | - ) { |
|
| 29 | - } |
|
| 21 | + /** |
|
| 22 | + * @param string $since For shipped apps and server code such as core/ and lib/, |
|
| 23 | + * this should be the server version. For other apps it |
|
| 24 | + * should be the semantic app version. |
|
| 25 | + */ |
|
| 26 | + public function __construct( |
|
| 27 | + protected string $since, |
|
| 28 | + ) { |
|
| 29 | + } |
|
| 30 | 30 | |
| 31 | - public function getSince(): string { |
|
| 32 | - return $this->since; |
|
| 33 | - } |
|
| 31 | + public function getSince(): string { |
|
| 32 | + return $this->since; |
|
| 33 | + } |
|
| 34 | 34 | } |
@@ -16,200 +16,200 @@ |
||
| 16 | 16 | use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent; |
| 17 | 17 | |
| 18 | 18 | class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface { |
| 19 | - public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void { |
|
| 20 | - $classLike = $event->getStmt(); |
|
| 21 | - $statementsSource = $event->getStatementsSource(); |
|
| 22 | - |
|
| 23 | - if (!str_contains($statementsSource->getFilePath(), '/lib/public/')) { |
|
| 24 | - return; |
|
| 25 | - } |
|
| 26 | - |
|
| 27 | - $isTesting = str_contains($statementsSource->getFilePath(), '/lib/public/Notification/') |
|
| 28 | - || str_contains($statementsSource->getFilePath(), 'CalendarEventStatus'); |
|
| 29 | - |
|
| 30 | - if ($isTesting) { |
|
| 31 | - self::checkStatementAttributes($classLike, $statementsSource); |
|
| 32 | - } else { |
|
| 33 | - self::checkClassComment($classLike, $statementsSource); |
|
| 34 | - } |
|
| 35 | - |
|
| 36 | - foreach ($classLike->stmts as $stmt) { |
|
| 37 | - if ($stmt instanceof ClassConst) { |
|
| 38 | - self::checkStatementComment($stmt, $statementsSource, 'constant'); |
|
| 39 | - } |
|
| 40 | - |
|
| 41 | - if ($stmt instanceof ClassMethod) { |
|
| 42 | - self::checkStatementComment($stmt, $statementsSource, 'method'); |
|
| 43 | - } |
|
| 44 | - |
|
| 45 | - if ($stmt instanceof EnumCase) { |
|
| 46 | - if ($isTesting) { |
|
| 47 | - self::checkStatementAttributes($classLike, $statementsSource); |
|
| 48 | - } else { |
|
| 49 | - self::checkStatementComment($stmt, $statementsSource, 'enum'); |
|
| 50 | - } |
|
| 51 | - } |
|
| 52 | - } |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - private static function checkStatementAttributes(ClassLike $stmt, FileSource $statementsSource): void { |
|
| 56 | - $hasAppFrameworkAttribute = false; |
|
| 57 | - $mustBeConsumable = false; |
|
| 58 | - $isConsumable = false; |
|
| 59 | - foreach ($stmt->attrGroups as $attrGroup) { |
|
| 60 | - foreach ($attrGroup->attrs as $attr) { |
|
| 61 | - if (in_array($attr->name->getLast(), [ |
|
| 62 | - 'Catchable', |
|
| 63 | - 'Consumable', |
|
| 64 | - 'Dispatchable', |
|
| 65 | - 'Implementable', |
|
| 66 | - 'Listenable', |
|
| 67 | - 'Throwable', |
|
| 68 | - ], true)) { |
|
| 69 | - $hasAppFrameworkAttribute = true; |
|
| 70 | - self::checkAttributeHasValidSinceVersion($attr, $statementsSource); |
|
| 71 | - } |
|
| 72 | - if (in_array($attr->name->getLast(), [ |
|
| 73 | - 'Catchable', |
|
| 74 | - 'Consumable', |
|
| 75 | - 'Listenable', |
|
| 76 | - ], true)) { |
|
| 77 | - $isConsumable = true; |
|
| 78 | - } |
|
| 79 | - if ($attr->name->getLast() === 'ExceptionalImplementable') { |
|
| 80 | - $mustBeConsumable = true; |
|
| 81 | - } |
|
| 82 | - } |
|
| 83 | - } |
|
| 84 | - |
|
| 85 | - if ($mustBeConsumable && !$isConsumable) { |
|
| 86 | - IssueBuffer::maybeAdd( |
|
| 87 | - new InvalidDocblock( |
|
| 88 | - 'Attribute OCP\\AppFramework\\Attribute\\ExceptionalImplementable is only valid on classes that also have OCP\\AppFramework\\Attribute\\Consumable', |
|
| 89 | - new CodeLocation($statementsSource, $stmt) |
|
| 90 | - ) |
|
| 91 | - ); |
|
| 92 | - } |
|
| 93 | - |
|
| 94 | - if (!$hasAppFrameworkAttribute) { |
|
| 95 | - IssueBuffer::maybeAdd( |
|
| 96 | - new InvalidDocblock( |
|
| 97 | - 'At least one of the OCP\\AppFramework\\Attribute attributes is required', |
|
| 98 | - new CodeLocation($statementsSource, $stmt) |
|
| 99 | - ) |
|
| 100 | - ); |
|
| 101 | - } |
|
| 102 | - } |
|
| 103 | - |
|
| 104 | - private static function checkClassComment(ClassLike $stmt, FileSource $statementsSource): void { |
|
| 105 | - $docblock = $stmt->getDocComment(); |
|
| 106 | - |
|
| 107 | - if ($docblock === null) { |
|
| 108 | - IssueBuffer::maybeAdd( |
|
| 109 | - new InvalidDocblock( |
|
| 110 | - 'PHPDoc is required for classes/interfaces in OCP.', |
|
| 111 | - new CodeLocation($statementsSource, $stmt) |
|
| 112 | - ) |
|
| 113 | - ); |
|
| 114 | - return; |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - try { |
|
| 118 | - $parsedDocblock = DocComment::parsePreservingLength($docblock); |
|
| 119 | - } catch (DocblockParseException $e) { |
|
| 120 | - IssueBuffer::maybeAdd( |
|
| 121 | - new InvalidDocblock( |
|
| 122 | - $e->getMessage(), |
|
| 123 | - new CodeLocation($statementsSource, $stmt) |
|
| 124 | - ) |
|
| 125 | - ); |
|
| 126 | - return; |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - if (!isset($parsedDocblock->tags['since'])) { |
|
| 130 | - IssueBuffer::maybeAdd( |
|
| 131 | - new InvalidDocblock( |
|
| 132 | - '@since is required for classes/interfaces in OCP.', |
|
| 133 | - new CodeLocation($statementsSource, $stmt) |
|
| 134 | - ) |
|
| 135 | - ); |
|
| 136 | - } |
|
| 137 | - |
|
| 138 | - if (isset($parsedDocblock->tags['depreacted'])) { |
|
| 139 | - IssueBuffer::maybeAdd( |
|
| 140 | - new InvalidDocblock( |
|
| 141 | - 'Typo in @deprecated for classes/interfaces in OCP.', |
|
| 142 | - new CodeLocation($statementsSource, $stmt) |
|
| 143 | - ) |
|
| 144 | - ); |
|
| 145 | - } |
|
| 146 | - } |
|
| 147 | - |
|
| 148 | - private static function checkStatementComment(Stmt $stmt, FileSource $statementsSource, string $type): void { |
|
| 149 | - $docblock = $stmt->getDocComment(); |
|
| 150 | - |
|
| 151 | - if ($docblock === null) { |
|
| 152 | - IssueBuffer::maybeAdd( |
|
| 153 | - new InvalidDocblock( |
|
| 154 | - 'PHPDoc is required for ' . $type . 's in OCP.', |
|
| 155 | - new CodeLocation($statementsSource, $stmt) |
|
| 156 | - ), |
|
| 157 | - ); |
|
| 158 | - return; |
|
| 159 | - } |
|
| 160 | - |
|
| 161 | - try { |
|
| 162 | - $parsedDocblock = DocComment::parsePreservingLength($docblock); |
|
| 163 | - } catch (DocblockParseException $e) { |
|
| 164 | - IssueBuffer::maybeAdd( |
|
| 165 | - new InvalidDocblock( |
|
| 166 | - $e->getMessage(), |
|
| 167 | - new CodeLocation($statementsSource, $stmt) |
|
| 168 | - ) |
|
| 169 | - ); |
|
| 170 | - return; |
|
| 171 | - } |
|
| 172 | - |
|
| 173 | - if (!isset($parsedDocblock->tags['since'])) { |
|
| 174 | - IssueBuffer::maybeAdd( |
|
| 175 | - new InvalidDocblock( |
|
| 176 | - '@since is required for ' . $type . 's in OCP.', |
|
| 177 | - new CodeLocation($statementsSource, $stmt) |
|
| 178 | - ) |
|
| 179 | - ); |
|
| 180 | - } |
|
| 181 | - |
|
| 182 | - if (isset($parsedDocblock->tags['depreacted'])) { |
|
| 183 | - IssueBuffer::maybeAdd( |
|
| 184 | - new InvalidDocblock( |
|
| 185 | - 'Typo in @deprecated for ' . $type . ' in OCP.', |
|
| 186 | - new CodeLocation($statementsSource, $stmt) |
|
| 187 | - ) |
|
| 188 | - ); |
|
| 189 | - } |
|
| 190 | - } |
|
| 191 | - |
|
| 192 | - private static function checkAttributeHasValidSinceVersion(\PhpParser\Node\Attribute $stmt, FileSource $statementsSource): void { |
|
| 193 | - foreach ($stmt->args as $arg) { |
|
| 194 | - if ($arg->name?->name === 'since') { |
|
| 195 | - if (!$arg->value instanceof \PhpParser\Node\Scalar\String_) { |
|
| 196 | - IssueBuffer::maybeAdd( |
|
| 197 | - new InvalidDocblock( |
|
| 198 | - 'Attribute since argument is not a valid version string', |
|
| 199 | - new CodeLocation($statementsSource, $stmt) |
|
| 200 | - ) |
|
| 201 | - ); |
|
| 202 | - } else { |
|
| 203 | - if (!preg_match('/^[1-9][0-9]*(\.[0-9]+){0,3}$/', $arg->value->value)) { |
|
| 204 | - IssueBuffer::maybeAdd( |
|
| 205 | - new InvalidDocblock( |
|
| 206 | - 'Attribute since argument is not a valid version string', |
|
| 207 | - new CodeLocation($statementsSource, $stmt) |
|
| 208 | - ) |
|
| 209 | - ); |
|
| 210 | - } |
|
| 211 | - } |
|
| 212 | - } |
|
| 213 | - } |
|
| 214 | - } |
|
| 19 | + public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event): void { |
|
| 20 | + $classLike = $event->getStmt(); |
|
| 21 | + $statementsSource = $event->getStatementsSource(); |
|
| 22 | + |
|
| 23 | + if (!str_contains($statementsSource->getFilePath(), '/lib/public/')) { |
|
| 24 | + return; |
|
| 25 | + } |
|
| 26 | + |
|
| 27 | + $isTesting = str_contains($statementsSource->getFilePath(), '/lib/public/Notification/') |
|
| 28 | + || str_contains($statementsSource->getFilePath(), 'CalendarEventStatus'); |
|
| 29 | + |
|
| 30 | + if ($isTesting) { |
|
| 31 | + self::checkStatementAttributes($classLike, $statementsSource); |
|
| 32 | + } else { |
|
| 33 | + self::checkClassComment($classLike, $statementsSource); |
|
| 34 | + } |
|
| 35 | + |
|
| 36 | + foreach ($classLike->stmts as $stmt) { |
|
| 37 | + if ($stmt instanceof ClassConst) { |
|
| 38 | + self::checkStatementComment($stmt, $statementsSource, 'constant'); |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + if ($stmt instanceof ClassMethod) { |
|
| 42 | + self::checkStatementComment($stmt, $statementsSource, 'method'); |
|
| 43 | + } |
|
| 44 | + |
|
| 45 | + if ($stmt instanceof EnumCase) { |
|
| 46 | + if ($isTesting) { |
|
| 47 | + self::checkStatementAttributes($classLike, $statementsSource); |
|
| 48 | + } else { |
|
| 49 | + self::checkStatementComment($stmt, $statementsSource, 'enum'); |
|
| 50 | + } |
|
| 51 | + } |
|
| 52 | + } |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + private static function checkStatementAttributes(ClassLike $stmt, FileSource $statementsSource): void { |
|
| 56 | + $hasAppFrameworkAttribute = false; |
|
| 57 | + $mustBeConsumable = false; |
|
| 58 | + $isConsumable = false; |
|
| 59 | + foreach ($stmt->attrGroups as $attrGroup) { |
|
| 60 | + foreach ($attrGroup->attrs as $attr) { |
|
| 61 | + if (in_array($attr->name->getLast(), [ |
|
| 62 | + 'Catchable', |
|
| 63 | + 'Consumable', |
|
| 64 | + 'Dispatchable', |
|
| 65 | + 'Implementable', |
|
| 66 | + 'Listenable', |
|
| 67 | + 'Throwable', |
|
| 68 | + ], true)) { |
|
| 69 | + $hasAppFrameworkAttribute = true; |
|
| 70 | + self::checkAttributeHasValidSinceVersion($attr, $statementsSource); |
|
| 71 | + } |
|
| 72 | + if (in_array($attr->name->getLast(), [ |
|
| 73 | + 'Catchable', |
|
| 74 | + 'Consumable', |
|
| 75 | + 'Listenable', |
|
| 76 | + ], true)) { |
|
| 77 | + $isConsumable = true; |
|
| 78 | + } |
|
| 79 | + if ($attr->name->getLast() === 'ExceptionalImplementable') { |
|
| 80 | + $mustBeConsumable = true; |
|
| 81 | + } |
|
| 82 | + } |
|
| 83 | + } |
|
| 84 | + |
|
| 85 | + if ($mustBeConsumable && !$isConsumable) { |
|
| 86 | + IssueBuffer::maybeAdd( |
|
| 87 | + new InvalidDocblock( |
|
| 88 | + 'Attribute OCP\\AppFramework\\Attribute\\ExceptionalImplementable is only valid on classes that also have OCP\\AppFramework\\Attribute\\Consumable', |
|
| 89 | + new CodeLocation($statementsSource, $stmt) |
|
| 90 | + ) |
|
| 91 | + ); |
|
| 92 | + } |
|
| 93 | + |
|
| 94 | + if (!$hasAppFrameworkAttribute) { |
|
| 95 | + IssueBuffer::maybeAdd( |
|
| 96 | + new InvalidDocblock( |
|
| 97 | + 'At least one of the OCP\\AppFramework\\Attribute attributes is required', |
|
| 98 | + new CodeLocation($statementsSource, $stmt) |
|
| 99 | + ) |
|
| 100 | + ); |
|
| 101 | + } |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + private static function checkClassComment(ClassLike $stmt, FileSource $statementsSource): void { |
|
| 105 | + $docblock = $stmt->getDocComment(); |
|
| 106 | + |
|
| 107 | + if ($docblock === null) { |
|
| 108 | + IssueBuffer::maybeAdd( |
|
| 109 | + new InvalidDocblock( |
|
| 110 | + 'PHPDoc is required for classes/interfaces in OCP.', |
|
| 111 | + new CodeLocation($statementsSource, $stmt) |
|
| 112 | + ) |
|
| 113 | + ); |
|
| 114 | + return; |
|
| 115 | + } |
|
| 116 | + |
|
| 117 | + try { |
|
| 118 | + $parsedDocblock = DocComment::parsePreservingLength($docblock); |
|
| 119 | + } catch (DocblockParseException $e) { |
|
| 120 | + IssueBuffer::maybeAdd( |
|
| 121 | + new InvalidDocblock( |
|
| 122 | + $e->getMessage(), |
|
| 123 | + new CodeLocation($statementsSource, $stmt) |
|
| 124 | + ) |
|
| 125 | + ); |
|
| 126 | + return; |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + if (!isset($parsedDocblock->tags['since'])) { |
|
| 130 | + IssueBuffer::maybeAdd( |
|
| 131 | + new InvalidDocblock( |
|
| 132 | + '@since is required for classes/interfaces in OCP.', |
|
| 133 | + new CodeLocation($statementsSource, $stmt) |
|
| 134 | + ) |
|
| 135 | + ); |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + if (isset($parsedDocblock->tags['depreacted'])) { |
|
| 139 | + IssueBuffer::maybeAdd( |
|
| 140 | + new InvalidDocblock( |
|
| 141 | + 'Typo in @deprecated for classes/interfaces in OCP.', |
|
| 142 | + new CodeLocation($statementsSource, $stmt) |
|
| 143 | + ) |
|
| 144 | + ); |
|
| 145 | + } |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + private static function checkStatementComment(Stmt $stmt, FileSource $statementsSource, string $type): void { |
|
| 149 | + $docblock = $stmt->getDocComment(); |
|
| 150 | + |
|
| 151 | + if ($docblock === null) { |
|
| 152 | + IssueBuffer::maybeAdd( |
|
| 153 | + new InvalidDocblock( |
|
| 154 | + 'PHPDoc is required for ' . $type . 's in OCP.', |
|
| 155 | + new CodeLocation($statementsSource, $stmt) |
|
| 156 | + ), |
|
| 157 | + ); |
|
| 158 | + return; |
|
| 159 | + } |
|
| 160 | + |
|
| 161 | + try { |
|
| 162 | + $parsedDocblock = DocComment::parsePreservingLength($docblock); |
|
| 163 | + } catch (DocblockParseException $e) { |
|
| 164 | + IssueBuffer::maybeAdd( |
|
| 165 | + new InvalidDocblock( |
|
| 166 | + $e->getMessage(), |
|
| 167 | + new CodeLocation($statementsSource, $stmt) |
|
| 168 | + ) |
|
| 169 | + ); |
|
| 170 | + return; |
|
| 171 | + } |
|
| 172 | + |
|
| 173 | + if (!isset($parsedDocblock->tags['since'])) { |
|
| 174 | + IssueBuffer::maybeAdd( |
|
| 175 | + new InvalidDocblock( |
|
| 176 | + '@since is required for ' . $type . 's in OCP.', |
|
| 177 | + new CodeLocation($statementsSource, $stmt) |
|
| 178 | + ) |
|
| 179 | + ); |
|
| 180 | + } |
|
| 181 | + |
|
| 182 | + if (isset($parsedDocblock->tags['depreacted'])) { |
|
| 183 | + IssueBuffer::maybeAdd( |
|
| 184 | + new InvalidDocblock( |
|
| 185 | + 'Typo in @deprecated for ' . $type . ' in OCP.', |
|
| 186 | + new CodeLocation($statementsSource, $stmt) |
|
| 187 | + ) |
|
| 188 | + ); |
|
| 189 | + } |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + private static function checkAttributeHasValidSinceVersion(\PhpParser\Node\Attribute $stmt, FileSource $statementsSource): void { |
|
| 193 | + foreach ($stmt->args as $arg) { |
|
| 194 | + if ($arg->name?->name === 'since') { |
|
| 195 | + if (!$arg->value instanceof \PhpParser\Node\Scalar\String_) { |
|
| 196 | + IssueBuffer::maybeAdd( |
|
| 197 | + new InvalidDocblock( |
|
| 198 | + 'Attribute since argument is not a valid version string', |
|
| 199 | + new CodeLocation($statementsSource, $stmt) |
|
| 200 | + ) |
|
| 201 | + ); |
|
| 202 | + } else { |
|
| 203 | + if (!preg_match('/^[1-9][0-9]*(\.[0-9]+){0,3}$/', $arg->value->value)) { |
|
| 204 | + IssueBuffer::maybeAdd( |
|
| 205 | + new InvalidDocblock( |
|
| 206 | + 'Attribute since argument is not a valid version string', |
|
| 207 | + new CodeLocation($statementsSource, $stmt) |
|
| 208 | + ) |
|
| 209 | + ); |
|
| 210 | + } |
|
| 211 | + } |
|
| 212 | + } |
|
| 213 | + } |
|
| 214 | + } |
|
| 215 | 215 | } |