diff --git a/docs/annotating_code/supported_annotations.md b/docs/annotating_code/supported_annotations.md index 3e23bd429..08ef20dd0 100644 --- a/docs/annotating_code/supported_annotations.md +++ b/docs/annotating_code/supported_annotations.md @@ -7,7 +7,7 @@ Psalm supports a wide range of docblock annotations. Psalm uses the following PHPDoc tags to understand your code: - [`@var`](https://docs.phpdoc.org/latest/references/phpdoc/tags/var.html) - Used for specifying the types of properties and variables + Used for specifying the types of properties and variables@ - [`@return`](https://docs.phpdoc.org/latest/references/phpdoc/tags/return.html) Used for specifying the return types of functions, methods and closures - [`@param`](https://docs.phpdoc.org/latest/references/phpdoc/tags/param.html) @@ -68,7 +68,7 @@ function addFoo(?string &$s) : void { } ``` -### `@psalm-var`, `@psalm-param`, `@psalm-return`, `@psalm-property`, `@psalm-property-read`, `@psalm-property-write` +### `@psalm-var`, `@psalm-param`, `@psalm-return`, `@psalm-property`, `@psalm-property-read`, `@psalm-property-write`, `@psalm-method` When specifying types in a format not supported by phpDocumentor ([but supported by Psalm](#type-syntax)) you may wish to prepend `@psalm-` to the PHPDoc tag, so as to avoid confusing your IDE. If a `@psalm`-prefixed tag is given, Psalm will use it in place of its non-prefixed counterpart. @@ -475,6 +475,38 @@ class User { } ``` +### `@psalm-require-extends` + +The @psalm-require-extends-annotation allows you to define a requirements that a trait imposes on the using class. + +```php +abstract class DatabaseModel { + // methods, properties, etc. +} + +/** + * @psalm-require-extends DatabaseModel + */ +trait SoftDeletingTrait { + // useful but scoped functionality, that depends on methods/properties from DatabaseModel +} + + +class MyModel extends DatabaseModel { + // valid + use SoftDeletingTrait; +} + +class NormalClass { + // triggers an error + use SoftDeletingTrait; +} +``` + +### `@psalm-require-implements` + +Behaves the same way as `@psalm-require-extends`, but for interfaces. + ## Type Syntax Psalm supports PHPDoc’s [type syntax](https://docs.phpdoc.org/latest/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types). diff --git a/docs/annotating_code/templated_annotations.md b/docs/annotating_code/templated_annotations.md index bb5940335..fac681b3b 100644 --- a/docs/annotating_code/templated_annotations.md +++ b/docs/annotating_code/templated_annotations.md @@ -68,9 +68,9 @@ class One_off_instance_of_MyContainer { This pattern can be used in large number of different situations like mocking, collections, iterators and loading arbitrary objects. Psalm has a large number of annotations to make it easy to use templated types in your codebase. -## `@template` +## `@template`, `@psalm-template` -The `@template` tag allows classes and functions to declare a generic type parameter. +The `@template`/`@psalm-template` tag allows classes and functions to declare a generic type parameter. As a very simple example, this function returns whatever is passed in: @@ -380,7 +380,7 @@ function takesDogList(Collection $dog_collection) : void { Here we're not doing anything bad – we're just iterating over an array of objects. But Psalm still gives that same basic error – "getNoises expects a `Collection`, but `Collection` was passed". -We can tell Psalm that it's safe to pass subtypes for the templated param `T` by using the annotation `@template-covariant T`: +We can tell Psalm that it's safe to pass subtypes for the templated param `T` by using the annotation `@template-covariant T` (or `@psalm-template-covariant T`): ```php > */ @@ -295,4 +340,68 @@ class DocumentationTest extends TestCase "Duplicate shortcodes found: \n" . var_export($duplicate_shortcodes, true) ); } + + /** @dataProvider knownAnnotations */ + public function testAllAnnotationsAreDocumented(string $annotation): void + { + if ('' === self::$docContents) { + foreach (self::ANNOTATION_DOCS as $file) { + self::$docContents .= file_get_contents(__DIR__ . '/../' . $file); + } + } + + $this->assertThat( + self::$docContents, + $this->conciseExpected($this->stringContains('@psalm-' . $annotation)), + "'@psalm-$annotation' is not present in the docs" + ); + } + + /** @return iterable */ + public function knownAnnotations(): iterable + { + foreach (DocComment::PSALM_ANNOTATIONS as $annotation) { + if (in_array('@psalm-' . $annotation, self::INTENTIONALLY_UNDOCUMENTED_ANNOTATIONS, true)) { + continue; + } + + if (in_array('@psalm-' . $annotation, self::WALL_OF_SHAME, true)) { + continue; + } + + yield $annotation => [$annotation]; + } + } + + /** + * Creates a constraint wrapper that displays the expected value in a concise form + */ + public function conciseExpected(Constraint $inner): Constraint + { + return new class ($inner) extends Constraint + { + /** @var Constraint */ + private $inner; + + public function __construct(Constraint $inner) + { + $this->inner = $inner; + } + + public function toString(): string + { + return $this->inner->toString(); + } + + protected function matches($other): bool + { + return $this->inner->matches($other); + } + + protected function failureDescription($other): string + { + return $this->exporter()->shortenedExport($other) . ' ' . $this->toString(); + } + }; + } }