Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apigen/apigen.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ services:
renderer.filter:
factory: PHPStan\ApiGen\RendererFilter

analyze.phpDocResolver:
factory: PHPStan\ApiGen\PhpDocResolver

commonMark:
factory: PHPStan\ApiGen\MarkdownConverter
11 changes: 10 additions & 1 deletion apigen/src/MarkdownConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
use League\CommonMark\MarkdownConverter as BaseMarkdownConverter;
use League\CommonMark\Output\RenderedContentInterface;
use function str_replace;

/** GitHub-flavored Markdown converter that escapes generic types written in PHPDoc descriptions. */
/** GitHub-flavored Markdown converter for PHPDoc descriptions that escapes generic types and keeps paragraphs apart. */
final class MarkdownConverter extends BaseMarkdownConverter
{

Expand All @@ -22,4 +24,11 @@ public function __construct()
parent::__construct($environment);
}

public function convert(string $input): RenderedContentInterface
{
// ApiGen joins description nodes with a space, which would indent the paragraph
// after each separator and break code fences that start it.
return parent::convert(str_replace(' ' . PhpDocResolver::PARAGRAPH_SEPARATOR . ' ', PhpDocResolver::PARAGRAPH_SEPARATOR, $input));
}

}
35 changes: 35 additions & 0 deletions apigen/src/PhpDocResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace PHPStan\ApiGen;

use ApiGen\Analyzer\NodeVisitors\PhpDocResolver as BasePhpDocResolver;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use function array_merge;

/**
* phpdoc-parser puts each paragraph into its own text node with an empty text node
* between them. ApiGen drops the empty nodes, which merges all paragraphs into one,
* so they are kept as blank lines here.
*/
final class PhpDocResolver extends BasePhpDocResolver
{

public const PARAGRAPH_SEPARATOR = "\n\n";

protected function resolvePhpDoc(PhpDocNode $phpDoc): PhpDocNode
{
$children = [];
foreach ($phpDoc->children as $child) {
if ($child instanceof PhpDocTextNode && $child->text === '') {
$children[] = new PhpDocTextNode(self::PARAGRAPH_SEPARATOR);
continue;
}

$children = array_merge($children, parent::resolvePhpDoc(new PhpDocNode([$child]))->children);
}

return new PhpDocNode($children);
}

}
4 changes: 3 additions & 1 deletion src/DependencyInjection/ContainerExtensionsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
*
* Extending ExtensionsExtension is required, not cosmetic. Compiler::processExtensions() starts with
*
* $first = $this->getExtensions(ParametersExtension::class) + $this->getExtensions(ExtensionsExtension::class);
* ```php
* $first = $this->getExtensions(ParametersExtension::class) + $this->getExtensions(ExtensionsExtension::class);
* ```
*
* and only extensions matching those types by instanceof have their loadConfiguration() called before
* the compiler snapshots its extension list. Everything registered after that snapshot is rejected with
Expand Down
20 changes: 11 additions & 9 deletions src/TrinaryLogic.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
* depend on runtime values that can't be known statically. Extension developers
* encounter TrinaryLogic extensively when querying type properties:
*
* if ($type->isString()->yes()) {
* // Definitely a string
* }
* if ($type->isString()->maybe()) {
* // Could be a string (e.g. mixed)
* }
* if ($type->isString()->no()) {
* // Definitely not a string
* }
* ```php
* if ($type->isString()->yes()) {
* // Definitely a string
* }
* if ($type->isString()->maybe()) {
* // Could be a string (e.g. mixed)
* }
* if ($type->isString()->no()) {
* // Definitely not a string
* }
* ```
*
* TrinaryLogic supports logical operations (and, or, negate) that propagate
* uncertainty correctly. It is used as a flyweight — instances are cached and
Expand Down
8 changes: 6 additions & 2 deletions src/Type/Accessory/AccessoryType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
*
* For example, `non-empty-string` is represented as:
*
* IntersectionType([StringType, AccessoryNonEmptyStringType])
* ```
* IntersectionType([StringType, AccessoryNonEmptyStringType])
* ```
*
* And `non-empty-list<int>` is represented as:
*
* IntersectionType([ArrayType(int, int), NonEmptyArrayType, AccessoryArrayListType])
* ```
* IntersectionType([ArrayType(int, int), NonEmptyArrayType, AccessoryArrayListType])
* ```
*
* Each AccessoryType implementation has a corresponding query method on the `Type` interface
* that returns `TrinaryLogic`. This lets any code query the refinement without knowing
Expand Down
4 changes: 3 additions & 1 deletion src/Type/GeneralizePrecision.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
*
* Used as a parameter to Type::generalize():
*
* $type->generalize(GeneralizePrecision::lessSpecific())
* ```php
* $type->generalize(GeneralizePrecision::lessSpecific())
* ```
*/
#[ReferencedByTurboExtension(key: 'generalizePrecision')]
final class GeneralizePrecision
Expand Down
17 changes: 9 additions & 8 deletions src/Type/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@
* will miss union types, intersection types with accessory types, and other composite forms.
* Always use the `is*()` methods or `isSuperTypeOf()` instead:
*
* // Wrong:
* if ($type instanceof StringType) { ... }
* ```php
* // Wrong:
* if ($type instanceof StringType) { ... }
*
* // Correct:
* if ($type->isString()->yes()) { ... }
* // Correct:
* if ($type->isString()->yes()) { ... }
* ```
*
* @api
* @api-do-not-implement
Expand Down Expand Up @@ -563,14 +565,13 @@ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type;
/**
* Returns actual template type for a given object.
*
* Example:
*
* @-template T
* class Foo {}
* For example, with a class `Foo` that declares `@template T`:
*
* ```php
* // $fooType is Foo<int>
* $t = $fooType->getTemplateType(Foo::class, 'T');
* $t->isInteger(); // yes
* ```
*
* Returns ErrorType in case of a missing type.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Type/TypeTraverser.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ final class TypeTraverser
* The following example converts constant strings to objects, while
* preserving unions and intersections:
*
* ```php
* TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
* if ($type instanceof UnionType || $type instanceof IntersectionType) {
* // Traverse inner types
Expand All @@ -42,6 +43,7 @@ final class TypeTraverser
* // Replaces the current type, and don't traverse
* return new MixedType();
* });
* ```
*
* @api
* @param TypeTraverserCallable|callable(Type $type, callable(Type): Type $traverse): Type $cb
Expand Down
8 changes: 5 additions & 3 deletions src/Type/VerbosityLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
*
* Used as a parameter to Type::describe() to control output detail:
*
* $type->describe(VerbosityLevel::typeOnly()) // "string"
* $type->describe(VerbosityLevel::value()) // "'hello'"
* $type->describe(VerbosityLevel::precise()) // "non-empty-lowercase-string"
* ```php
* $type->describe(VerbosityLevel::typeOnly()) // "string"
* $type->describe(VerbosityLevel::value()) // "'hello'"
* $type->describe(VerbosityLevel::precise()) // "non-empty-lowercase-string"
* ```
*
* The getRecommendedLevelByType() factory method automatically chooses the right level
* for error messages based on what types are involved — it picks the minimum verbosity
Expand Down
Loading