Skip to content
Open
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: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"symfony/finder": "^6.1|^7.0",
"symfony/console": "^6.1|^7.0",
"symfony/event-dispatcher": "^6.1|^7.0",
"webmozart/assert": "^1.11"
"webmozart/assert": "^1.11",
"texthtml/maybe": "^0.6.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0|^11.0|^12.0",
Expand Down
7 changes: 7 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<exclude name="SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment" />
<exclude name="SlevomatCodingStandard.Commenting.RequireOneLineDocComment" />
<exclude name="SlevomatCodingStandard.Commenting.RequireOneLinePropertyDocComment" />
<exclude name="SlevomatCodingStandard.ControlStructures.DisallowShortTernaryOperator" />
<exclude name="SlevomatCodingStandard.ControlStructures.NewWithoutParentheses" />
<exclude name="SlevomatCodingStandard.ControlStructures.RequireYodaComparison" />
<exclude name="SlevomatCodingStandard.Exceptions.DisallowNonCapturingCatch" />
Expand Down Expand Up @@ -51,6 +52,12 @@
</properties>
</rule>

<rule ref="SlevomatCodingStandard.Functions.ArrowFunctionDeclaration">
<properties>
<property name="allowMultiLine" value="true" />
</properties>
</rule>

<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
<properties>
<property name="searchAnnotations" value="true" />
Expand Down
11 changes: 5 additions & 6 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Symfony\Component\Console\Input;
use Symfony\Component\Console\Output;
use Symfony\Component\Console\SingleCommandApplication;
use TH\Maybe\Option;

#[AsCommand("doctest")]
final class Application extends SingleCommandApplication
Expand Down Expand Up @@ -61,22 +62,20 @@
}

/**
* @return list<string>|null
* @return Option<array<string>>
*/
private function getLanguages(Input\InputInterface $input): ?array
private function getLanguages(Input\InputInterface $input): Option
{
$languages = [];

foreach ($input->getOption("languages") as $lang) {
if ($lang === '*') {
$languages = null;

break;
return Option\none();
}

$languages[] = $lang;
}

return $languages;
return Option\some($languages);

Check failure on line 79 in src/Application.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Method TH\DocTest\Application::getLanguages() should return TH\Maybe\Option<array<string>> but returns TH\Maybe\Option\Some<list<string>>.

Check failure on line 79 in src/Application.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Method TH\DocTest\Application::getLanguages() should return TH\Maybe\Option<array<string>> but returns TH\Maybe\Option\Some<list<string>>.

Check failure on line 79 in src/Application.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Method TH\DocTest\Application::getLanguages() should return TH\Maybe\Option<array<string>> but returns TH\Maybe\Option\Some<list<string>>.

Check failure on line 79 in src/Application.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Method TH\DocTest\Application::getLanguages() should return TH\Maybe\Option<array<string>> but returns TH\Maybe\Option\Some<list<string>>.
}
}
80 changes: 36 additions & 44 deletions src/Iterator/AllExamples.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@

use TH\DocTest\Example;
use TH\DocTest\Location;
use TH\Maybe\Option;

final class AllExamples implements Examples
{
/**
* @param list<string>|null $acceptedLanguages Use empty string for unspecified language, and null for any languages
* @param Option<array<string>> $languageFilter Use empty string for unspecified language
*/
public function __construct(
private readonly Comments $comments,
private readonly ?array $acceptedLanguages,
private readonly Option $languageFilter,
) {}

/**
Expand All @@ -27,15 +28,15 @@

/**
* @param array<string> $paths paths to files and folder to look for PHP comments code examples in
* @param list<string>|null $acceptedLanguages Use empty string for unspecified language, and null for any languages
* @param Option<array<string>> $languageFilter Use empty string for unspecified language
*/
public static function fromPaths(
array $paths,
?array $acceptedLanguages,
Option $languageFilter,
): self {
return new self(
SourceComments::fromPaths($paths),
$acceptedLanguages,
$languageFilter,
);
}

Expand All @@ -49,62 +50,51 @@
$lines = new \ArrayIterator(\explode(PHP_EOL, $comment));
$index = 1;

while ($example = $this->nextExample($lines, $location, $index++)) {
yield $example;
while (($example = $this->nextExample($lines, $location, $index++))->isSome()) {
yield $example->unwrap();

Check failure on line 54 in src/Iterator/AllExamples.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Generator expects value type TH\DocTest\Example, mixed given.

Check failure on line 54 in src/Iterator/AllExamples.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Generator expects value type TH\DocTest\Example, mixed given.

Check failure on line 54 in src/Iterator/AllExamples.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Generator expects value type TH\DocTest\Example, mixed given.

Check failure on line 54 in src/Iterator/AllExamples.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Generator expects value type TH\DocTest\Example, mixed given.
}
}

/**
* @param \ArrayIterator<int,string> $lines
* @return Option<Example>
*/
private function nextExample(
\ArrayIterator $lines,
Location $location,
int $index,
): ?Example {
$codeblockStartedAt = $this->findFencedPHPCodeBlockStart($lines);

if ($codeblockStartedAt === null) {
return null;
}

return $this->readExample(
$lines,
$location->startingAt($codeblockStartedAt, $index),
private function nextExample(\ArrayIterator $lines, Location $location, int $index): Option
{
return $this->findFencedCodeBlockStart($lines)->andThen(
fn (int $codeblockStartedAt)
=> $this->readExample($lines, $location->startingAt($codeblockStartedAt, $index)),
);
}

/**
* @param \ArrayIterator<int,string> $lines
* @return Option<Example>
*/
private function readExample(
\ArrayIterator $lines,
Location $location,
): ?Example {
private function readExample(\ArrayIterator $lines, Location $location): Option
{
$buffer = [];

while ($lines->valid()) {
$line = $lines->current();
$lines->next();

if ($this->endOfAFencedCodeBlock($line)) {
return new Example(
\implode(PHP_EOL, $buffer),
$location->ofLength($lines->key()),
);
return Option\some(new Example(\implode(PHP_EOL, $buffer), $location->ofLength($lines->key())));
}

$buffer[] = \preg_replace("/^\s*\*( ?)/", "", $line);
}

return null;
return Option\none();
}

/**
* @param \ArrayIterator<int,string> $lines
* phpcs:disable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
* @return Option<int>
*/
private function findFencedPHPCodeBlockStart(\ArrayIterator $lines): ?int
private function findFencedCodeBlockStart(\ArrayIterator $lines): Option
{
$insideAFencedCodeBlock = false;

Expand All @@ -119,43 +109,45 @@
} else {
$lang = $this->startOfAFencedCodeBlock($line);

if ($lang === false) {
continue;
if ($lang->mapOr($this->isAcceptedLanguage(...), default: false)) {
return Option\some($lines->key());
}

if ($this->isAcceptedLanguage($lang)) {
return $lines->key();
if ($lang->isNone()) {
continue;
}

$insideAFencedCodeBlock = true;
}
}

return null;
return Option\none();
}

private function isAcceptedLanguage(string $lang): bool
{
if ($this->acceptedLanguages === null) {
return true;
}

return \in_array(needle: $lang, haystack: $this->acceptedLanguages, strict: true);
return $this->languageFilter->mapOr(
callback: static fn (array $languages) => \in_array(needle: $lang, haystack: $languages, strict: true),
default: true,
);
}

private function endOfAFencedCodeBlock(string $line): bool
{
return \ltrim($line) === "* ```";
}

private function startOfAFencedCodeBlock(string $line): false|string
/**
* @return Option<string>
*/
private function startOfAFencedCodeBlock(string $line): Option
{
$line = \trim($line);

if (!\str_starts_with($line, "* ```")) {
return false;
return Option\none();
}

return \substr($line, 5);
return Option\some(\substr($line, 5));
}
}
12 changes: 11 additions & 1 deletion src/Iterator/Files.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ public function iteratePaths(): \Traversable
* @return \Traversable<\SplFileInfo>
*/
private function iterate(string $pattern): \Traversable
{
foreach (self::glob($pattern) as $path) {
yield from $this->iteratePath($path);
}
}

/**
* @return \Traversable<string>
*/
private static function glob(string $pattern): \Traversable
{
$paths = \glob($pattern);

Expand All @@ -52,7 +62,7 @@ private function iterate(string $pattern): \Traversable
}

foreach ($paths as $path) {
yield from $this->iteratePath($path);
yield $path;
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/Iterator/FilteredExamples.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace TH\DocTest\Iterator;

use TH\DocTest\Example;
use TH\Maybe\Option;

final class FilteredExamples implements Examples
{
Expand Down Expand Up @@ -30,15 +31,15 @@ public static function filter(Examples $examples, string $filter): self

/**
* @param array<string> $paths paths to files and folder to look for PHP comments code examples in
* @param list<string>|null $acceptedLanguages Use empty string for unspecified language, and null for any languages
* @param Option<array<string>> $languageFilter Use empty string for unspecified language
*/
public static function fromPaths(
array $paths,
string $filter,
?array $acceptedLanguages,
Option $languageFilter,
): self {
return self::filter(
AllExamples::fromPaths($paths, $acceptedLanguages),
AllExamples::fromPaths($paths, $languageFilter),
$filter,
);
}
Expand Down
Loading
Loading