From fa8404434182d8b8b6a9d69a41eafdb402f54c4c Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:57:23 +0400 Subject: [PATCH 1/2] `Composite` mutation. --- .../documentator/src/Markdown/Document.php | 11 +++- .../src/Markdown/Mutations/Composite.php | 38 ++++++++++++++ .../src/Markdown/Mutations/CompositeTest.php | 50 +++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 packages/documentator/src/Markdown/Mutations/Composite.php create mode 100644 packages/documentator/src/Markdown/Mutations/CompositeTest.php diff --git a/packages/documentator/src/Markdown/Document.php b/packages/documentator/src/Markdown/Document.php index 42479141..4474b751 100644 --- a/packages/documentator/src/Markdown/Document.php +++ b/packages/documentator/src/Markdown/Document.php @@ -9,6 +9,7 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Data\Lines; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Coordinate; use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; +use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\Composite; use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\FootnotesPrefix; use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\FootnotesRemove; use LastDragon_ru\LaraASP\Documentator\Markdown\Mutations\ReferencesInline; @@ -140,7 +141,10 @@ public function mutate(Mutation ...$mutations): static { * @return new */ public function toInlinable(string $seed): static { - return $this->mutate(new FootnotesPrefix($seed), new ReferencesPrefix($seed)); + return $this->mutate(new Composite( + new FootnotesPrefix($seed), + new ReferencesPrefix($seed), + )); } /** @@ -151,7 +155,10 @@ public function toInlinable(string $seed): static { * @return new */ public function toSplittable(): static { - return $this->mutate(new FootnotesRemove(), new ReferencesInline()); + return $this->mutate(new Composite( + new FootnotesRemove(), + new ReferencesInline(), + )); } protected function setContent(string $content): static { diff --git a/packages/documentator/src/Markdown/Mutations/Composite.php b/packages/documentator/src/Markdown/Mutations/Composite.php new file mode 100644 index 00000000..7cf52a50 --- /dev/null +++ b/packages/documentator/src/Markdown/Mutations/Composite.php @@ -0,0 +1,38 @@ + + */ + protected array $mutations; + + public function __construct(Mutation ...$mutations) { + $this->mutations = $mutations; + } + + /** + * @inheritDoc + */ + #[Override] + public function __invoke(Document $document, DocumentNode $node): array { + $changes = []; + + foreach ($this->mutations as $mutation) { + $changes = array_merge($changes, $mutation($document, $node)); + } + + return $changes; + } +} diff --git a/packages/documentator/src/Markdown/Mutations/CompositeTest.php b/packages/documentator/src/Markdown/Mutations/CompositeTest.php new file mode 100644 index 00000000..6854e651 --- /dev/null +++ b/packages/documentator/src/Markdown/Mutations/CompositeTest.php @@ -0,0 +1,50 @@ +getValue($document)); + $aChanges = [ + [new Location(1, 1, 10), 'a'], + ]; + $bChanges = [ + [new Location(2, 3, 0), 'b'], + ]; + $aMutation = Mockery::mock(Mutation::class); + $aMutation + ->shouldReceive('__invoke') + ->with($document, $node) + ->once() + ->andReturn($aChanges); + $bMutation = Mockery::mock(Mutation::class); + $bMutation + ->shouldReceive('__invoke') + ->with($document, $node) + ->once() + ->andReturn($bChanges); + + $mutation = new Composite($aMutation, $bMutation); + $actual = $mutation($document, $node); + $expected = array_merge($aChanges, $bChanges); + + self::assertEquals($expected, $actual); + } +} From 56ce04443ca0f228217edbe9e6f3d2311132cd2f Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:07:56 +0400 Subject: [PATCH 2/2] `Mutation::__invoke()` can return `iterable`. --- .../src/Markdown/Contracts/Mutation.php | 4 +- packages/documentator/src/Markdown/Editor.php | 48 ++++++++++------ .../documentator/src/Markdown/EditorTest.php | 56 ++++++++++++++----- .../src/Markdown/Mutations/Composite.php | 12 ++-- .../src/Markdown/Mutations/CompositeTest.php | 7 ++- .../Markdown/Mutations/FootnotesPrefix.php | 10 ++-- .../Markdown/Mutations/FootnotesRemove.php | 13 +++-- .../src/Markdown/Mutations/Move.php | 14 +++-- .../Markdown/Mutations/ReferencesInline.php | 12 ++-- .../Markdown/Mutations/ReferencesPrefix.php | 12 ++-- phpstan-baseline.neon | 30 ++++++++++ 11 files changed, 148 insertions(+), 70 deletions(-) diff --git a/packages/documentator/src/Markdown/Contracts/Mutation.php b/packages/documentator/src/Markdown/Contracts/Mutation.php index d2ebeb21..ec2b4097 100644 --- a/packages/documentator/src/Markdown/Contracts/Mutation.php +++ b/packages/documentator/src/Markdown/Contracts/Mutation.php @@ -8,7 +8,7 @@ interface Mutation { /** - * @return array + * @return iterable */ - public function __invoke(Document $document, DocumentNode $node): array; + public function __invoke(Document $document, DocumentNode $node): iterable; } diff --git a/packages/documentator/src/Markdown/Editor.php b/packages/documentator/src/Markdown/Editor.php index 76421abf..14031d94 100644 --- a/packages/documentator/src/Markdown/Editor.php +++ b/packages/documentator/src/Markdown/Editor.php @@ -72,13 +72,14 @@ public function getText(Location|Coordinate $location): ?string { } /** - * @param array $changes + * @param iterable $changes * * @return new */ - public function mutate(array $changes): static { + public function mutate(iterable $changes): static { // Modify $lines = $this->lines; + $changes = $this->prepare($changes); $changes = $this->removeOverlaps($changes); $changes = $this->expand($changes); $paddings = []; @@ -134,7 +135,23 @@ public function mutate(array $changes): static { } /** - * @param array $changes + * @param iterable $changes + * + * @return list, ?string}> + */ + protected function prepare(iterable $changes): array { + $prepared = []; + + foreach ($changes as $change) { + [$location, $text] = $change; + $prepared[] = [iterator_to_array($location), $text]; + } + + return array_reverse($prepared); + } + + /** + * @param array, ?string}> $changes * * @return list */ @@ -144,11 +161,10 @@ protected function expand(array $changes): array { return $a->line <=> $b->line ?: $a->offset <=> $b->offset; }; - foreach (array_reverse($changes, true) as $change) { - [$location, $text] = $change; - $coordinates = iterator_to_array($location); - $text = $text ? Text::getLines($text) : []; - $line = 0; + foreach ($changes as $change) { + [$coordinates, $text] = $change; + $text = $text ? Text::getLines($text) : []; + $line = 0; usort($coordinates, $sort); @@ -168,20 +184,20 @@ protected function expand(array $changes): array { } /** - * @param array $changes + * @param array, ?string}> $changes * - * @return array + * @return array, ?string}> */ protected function removeOverlaps(array $changes): array { $used = []; + $sort = static function (Coordinate $a, Coordinate $b): int { + return $b->line <=> $a->line; + }; - foreach (array_reverse($changes, true) as $key => $change) { - [$location] = $change; - $coordinates = iterator_to_array($location); + foreach ($changes as $key => $change) { + [$coordinates] = $change; - usort($coordinates, static function (Coordinate $a, Coordinate $b): int { - return $b->line <=> $a->line; - }); + usort($coordinates, $sort); foreach ($coordinates as $coordinate) { if ($this->isOverlapped($used, $coordinate)) { diff --git a/packages/documentator/src/Markdown/EditorTest.php b/packages/documentator/src/Markdown/EditorTest.php index 59799615..8ed296b3 100644 --- a/packages/documentator/src/Markdown/EditorTest.php +++ b/packages/documentator/src/Markdown/EditorTest.php @@ -8,6 +8,8 @@ use Override; use PHPUnit\Framework\Attributes\CoversClass; +use function iterator_to_array; + /** * @internal */ @@ -62,6 +64,30 @@ public function testMutate(): void { self::assertSame($expected, $actual->getLines()); } + public function testPrepare(): void { + $editor = new class([]) extends Editor { + /** + * @inheritDoc + */ + #[Override] + public function prepare(iterable $changes): array { + return parent::prepare($changes); + } + }; + $changes = [ + [new Location(10, 10, 15, 10), 'a'], + [new Location(10, 10, 10, null), 'b'], + [new Location(12, 15, 5, 10), 'c'], + ]; + $expected = [ + [iterator_to_array(new Location(12, 15, 5, 10)), 'c'], + [iterator_to_array(new Location(10, 10, 10, null)), 'b'], + [iterator_to_array(new Location(10, 10, 15, 10)), 'a'], + ]; + + self::assertEquals($expected, $editor->prepare($changes)); + } + public function testRemoveOverlaps(): void { $editor = new class([]) extends Editor { /** @@ -73,19 +99,19 @@ public function removeOverlaps(array $changes): array { } }; $changes = [ - 0 => [new Location(10, 10, 15, 10), 'a'], - 1 => [new Location(10, 10, 10, null), 'b'], - 2 => [new Location(12, 15, 5, 10), 'c'], - 3 => [new Location(14, 15, 5, 10), 'd'], - 4 => [new Location(17, 17, 5, 10), 'e'], - 5 => [new Location(17, 17, 11, 10), 'f'], - 6 => [new Location(18, 18, 5, 10), 'g'], + 0 => [iterator_to_array(new Location(18, 18, 5, 10)), 'g'], + 1 => [iterator_to_array(new Location(17, 17, 11, 10)), 'f'], + 2 => [iterator_to_array(new Location(17, 17, 5, 10)), 'e'], + 3 => [iterator_to_array(new Location(14, 15, 5, 10)), 'd'], + 4 => [iterator_to_array(new Location(12, 15, 5, 10)), 'c'], + 5 => [iterator_to_array(new Location(10, 10, 10, null)), 'b'], + 6 => [iterator_to_array(new Location(10, 10, 15, 10)), 'a'], ]; $expected = [ - 1 => [new Location(10, 10, 10, null), 'b'], - 3 => [new Location(14, 15, 5, 10), 'd'], - 5 => [new Location(17, 17, 11, 10), 'f'], - 6 => [new Location(18, 18, 5, 10), 'g'], + 0 => [iterator_to_array(new Location(18, 18, 5, 10)), 'g'], + 1 => [iterator_to_array(new Location(17, 17, 11, 10)), 'f'], + 3 => [iterator_to_array(new Location(14, 15, 5, 10)), 'd'], + 5 => [iterator_to_array(new Location(10, 10, 10, null)), 'b'], ]; self::assertEquals($expected, $editor->removeOverlaps($changes)); @@ -102,10 +128,10 @@ public function expand(array $changes): array { } }; $changes = [ - [new Location(1, 1, 5, 10), 'text'], - [new Location(2, 3, 5, null), 'text'], - [new Location(4, 5, 5, 5, 1), "text a\ntext b"], - [new Location(6, 6, 5, 10, 2), "text a\ntext b"], + [iterator_to_array(new Location(6, 6, 5, 10, 2)), "text a\ntext b"], + [iterator_to_array(new Location(4, 5, 5, 5, 1)), "text a\ntext b"], + [iterator_to_array(new Location(2, 3, 5, null)), 'text'], + [iterator_to_array(new Location(1, 1, 5, 10)), 'text'], ]; $expected = [ [new Coordinate(6, 7, 10, 2), 'text a'], diff --git a/packages/documentator/src/Markdown/Mutations/Composite.php b/packages/documentator/src/Markdown/Mutations/Composite.php index 7cf52a50..2c0460e9 100644 --- a/packages/documentator/src/Markdown/Mutations/Composite.php +++ b/packages/documentator/src/Markdown/Mutations/Composite.php @@ -7,8 +7,6 @@ use League\CommonMark\Node\Block\Document as DocumentNode; use Override; -use function array_merge; - /** * Merges all mutations into one. */ @@ -26,13 +24,13 @@ public function __construct(Mutation ...$mutations) { * @inheritDoc */ #[Override] - public function __invoke(Document $document, DocumentNode $node): array { - $changes = []; + public function __invoke(Document $document, DocumentNode $node): iterable { + // Just in case + yield from []; + // Process all foreach ($this->mutations as $mutation) { - $changes = array_merge($changes, $mutation($document, $node)); + yield from $mutation($document, $node); } - - return $changes; } } diff --git a/packages/documentator/src/Markdown/Mutations/CompositeTest.php b/packages/documentator/src/Markdown/Mutations/CompositeTest.php index 6854e651..7075de92 100644 --- a/packages/documentator/src/Markdown/Mutations/CompositeTest.php +++ b/packages/documentator/src/Markdown/Mutations/CompositeTest.php @@ -42,8 +42,13 @@ public function testInvoke(): void { ->andReturn($bChanges); $mutation = new Composite($aMutation, $bMutation); - $actual = $mutation($document, $node); + $changes = $mutation($document, $node); $expected = array_merge($aChanges, $bChanges); + $actual = []; + + foreach ($changes as $change) { + $actual[] = $change; + } self::assertEquals($expected, $actual); } diff --git a/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php b/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php index ce7d607f..cc03c98d 100644 --- a/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php +++ b/packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php @@ -28,9 +28,11 @@ public function __construct( * @inheritDoc */ #[Override] - public function __invoke(Document $document, DocumentNode $node): array { - $changes = []; + public function __invoke(Document $document, DocumentNode $node): iterable { + // Just in case + yield from []; + // Process foreach ($node->iterator() as $child) { // Footnote? if (!($child instanceof FootnoteRef) && !($child instanceof Footnote)) { @@ -42,11 +44,9 @@ public function __invoke(Document $document, DocumentNode $node): array { $location = $label ? $this->getLabelLocation($child, $label) : null; if ($location) { - $changes[] = [$location, "{$this->prefix}-{$label}"]; + yield [$location, "{$this->prefix}-{$label}"]; } } - - return $changes; } private function getLabel(Document $document, Footnote|FootnoteRef $footnote): ?string { diff --git a/packages/documentator/src/Markdown/Mutations/FootnotesRemove.php b/packages/documentator/src/Markdown/Mutations/FootnotesRemove.php index 46c58d08..ca4b7627 100644 --- a/packages/documentator/src/Markdown/Mutations/FootnotesRemove.php +++ b/packages/documentator/src/Markdown/Mutations/FootnotesRemove.php @@ -4,6 +4,7 @@ use LastDragon_ru\LaraASP\Documentator\Markdown\Contracts\Mutation; use LastDragon_ru\LaraASP\Documentator\Markdown\Document; +use LastDragon_ru\LaraASP\Documentator\Markdown\Location\Location; use LastDragon_ru\LaraASP\Documentator\Markdown\Utils; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; @@ -19,12 +20,14 @@ public function __construct() { } /** - * @inheritDoc + * @return iterable */ #[Override] - public function __invoke(Document $document, DocumentNode $node): array { - $changes = []; + public function __invoke(Document $document, DocumentNode $node): iterable { + // Just in case + yield from []; + // Process foreach ($node->iterator() as $child) { $location = match (true) { $child instanceof FootnoteRef, $child instanceof Footnote => Utils::getLocation($child), @@ -32,10 +35,8 @@ public function __invoke(Document $document, DocumentNode $node): array { }; if ($location) { - $changes[] = [$location, null]; + yield [$location, null]; } } - - return $changes; } } diff --git a/packages/documentator/src/Markdown/Mutations/Move.php b/packages/documentator/src/Markdown/Mutations/Move.php index ac5bab3b..8a540275 100644 --- a/packages/documentator/src/Markdown/Mutations/Move.php +++ b/packages/documentator/src/Markdown/Mutations/Move.php @@ -46,14 +46,17 @@ public function __construct( * @inheritDoc */ #[Override] - public function __invoke(Document $document, DocumentNode $node): array { + public function __invoke(Document $document, DocumentNode $node): iterable { + // Just in case + yield from []; + // No path? $docPath = $document->getPath(); if ($docPath === null) { $document->setPath(Path::normalize($this->path)); - return []; + return false; } // Same? @@ -61,11 +64,10 @@ public function __invoke(Document $document, DocumentNode $node): array { $newPath = Path::getPath($docDirectory, $this->path); if ($docPath === $newPath) { - return []; + return false; } // Update - $changes = []; $resources = $this->getRelativeResources($node); $newDirectory = dirname($newPath); @@ -113,7 +115,7 @@ public function __invoke(Document $document, DocumentNode $node): array { } if ($location !== null && $text !== null) { - $changes[] = [$location, $text]; + yield [$location, $text]; } } @@ -121,7 +123,7 @@ public function __invoke(Document $document, DocumentNode $node): array { $document->setPath($newPath); // Return - return $changes; + return true; } /** diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesInline.php b/packages/documentator/src/Markdown/Mutations/ReferencesInline.php index 507d99fe..a260b518 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesInline.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesInline.php @@ -28,8 +28,11 @@ public function __construct() { * @inheritDoc */ #[Override] - public function __invoke(Document $document, DocumentNode $node): array { - $changes = []; + public function __invoke(Document $document, DocumentNode $node): iterable { + // Just in case + yield from []; + + // Process $references = $this->getReferences($node); foreach ($references as $reference) { @@ -59,12 +62,9 @@ public function __invoke(Document $document, DocumentNode $node): array { } if ($location !== null && $text !== null) { - $changes[] = [$location, $text ?: null]; + yield [$location, $text ?: null]; } } - - // Return - return $changes; } /** diff --git a/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php b/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php index eff48d2d..260c8304 100644 --- a/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php +++ b/packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php @@ -31,8 +31,11 @@ public function __construct( * @inheritDoc */ #[Override] - public function __invoke(Document $document, DocumentNode $node): array { - $changes = []; + public function __invoke(Document $document, DocumentNode $node): iterable { + // Just in case + yield from []; + + // Process $references = $this->getReferences($node); foreach ($references as $reference) { @@ -77,12 +80,9 @@ public function __invoke(Document $document, DocumentNode $node): array { } if ($location !== null && $text !== null) { - $changes[] = [$location, $text]; + yield [$location, $text]; } } - - // Return - return $changes; } /** diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7b4b7882..7d4b554d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,6 +5,36 @@ parameters: count: 1 path: packages/core/src/Observer/DispatcherTest.php + - + message: "#^Generator expects value type array\\{LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Markdown\\\\Location\\\\Location, string\\|null\\}, \\*NEVER\\* given\\.$#" + count: 1 + path: packages/documentator/src/Markdown/Mutations/Composite.php + + - + message: "#^Generator expects value type array\\{LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Markdown\\\\Location\\\\Location, string\\|null\\}, \\*NEVER\\* given\\.$#" + count: 1 + path: packages/documentator/src/Markdown/Mutations/FootnotesPrefix.php + + - + message: "#^Generator expects value type array\\{LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Markdown\\\\Location\\\\Location, string\\|null\\}, \\*NEVER\\* given\\.$#" + count: 1 + path: packages/documentator/src/Markdown/Mutations/FootnotesRemove.php + + - + message: "#^Generator expects value type array\\{LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Markdown\\\\Location\\\\Location, string\\|null\\}, \\*NEVER\\* given\\.$#" + count: 1 + path: packages/documentator/src/Markdown/Mutations/Move.php + + - + message: "#^Generator expects value type array\\{LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Markdown\\\\Location\\\\Location, string\\|null\\}, \\*NEVER\\* given\\.$#" + count: 1 + path: packages/documentator/src/Markdown/Mutations/ReferencesInline.php + + - + message: "#^Generator expects value type array\\{LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Markdown\\\\Location\\\\Location, string\\|null\\}, \\*NEVER\\* given\\.$#" + count: 1 + path: packages/documentator/src/Markdown/Mutations/ReferencesPrefix.php + - message: "#^Parameter \\#1 \\$dependency of class LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Processor\\\\ExecutorTraversable constructor expects LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Processor\\\\Contracts\\\\Dependency\\\\>, LastDragon_ru\\\\LaraASP\\\\Documentator\\\\Processor\\\\Contracts\\\\Dependency\\<\\*\\> given\\.$#" count: 1