Skip to content

Commit

Permalink
Added FileWriter for updating class docblocks
Browse files Browse the repository at this point in the history
  • Loading branch information
matt committed Feb 19, 2024
1 parent 15fe230 commit 32b0979
Show file tree
Hide file tree
Showing 8 changed files with 668 additions and 67 deletions.
10 changes: 10 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@
<code><![CDATA[visitProvider]]></code>
</PossiblyUnusedMethod>
</file>
<file src="test/Writer/EolTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[eolProvider]]></code>
</PossiblyUnusedMethod>
</file>
<file src="test/Writer/FileWriterTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[writeProvider]]></code>
</PossiblyUnusedMethod>
</file>
<file src="test/Writer/Tag/MethodTest.php">
<PossiblyUnusedMethod>
<code><![CDATA[isBeforeProvider]]></code>
Expand Down
45 changes: 19 additions & 26 deletions src/Writer/DocBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
use function substr;
use function trim;

use const PHP_EOL;

/**
* @internal
*
Expand All @@ -33,34 +31,32 @@
/**
* @param list<string|TagInterface> $sections
*/
private function __construct(private string $indent, private array $sections)
private function __construct(private array $sections)
{
}

public static function fromDocComment(string|false $docComment): self
{
/** @psalm-suppress RiskyTruthyFalsyComparison */
$docComment = $docComment ?: <<<EOD
/**
*/
/**
*/
EOD;

$lines = explode("\n", $docComment);
$first = $lines[0] ?? '';
$indent = substr($lines[0], 0, (int) strpos($first, '/'));
$lines = explode(Eol::getEol($docComment), trim($docComment));

$sections = $section = [];
$inTag = false;
foreach (array_slice($lines, 1, -1) as $line) {
$line = trim(substr($line, (int) strpos($line, '*') + 1));
if ($inTag && self::isEndOfTag($line)) {
$sections[] = new GenericTag(implode(PHP_EOL, $section));
$sections[] = new GenericTag(implode("\n", $section));
$section = [];
$inTag = false;
}
if (self::isStartOfTag($line)) {
if ($section !== []) {
$sections[] = implode(PHP_EOL, $section);
$sections[] = implode("\n", $section);
}
$section = [];
$inTag = true;
Expand All @@ -69,22 +65,19 @@ public static function fromDocComment(string|false $docComment): self
}

if ($inTag) {
$sections[] = new GenericTag(implode(PHP_EOL, $section));
$sections[] = new GenericTag(implode("\n", $section));
} elseif ($section !== []) {
$sections[] = implode(PHP_EOL, $section);
$sections[] = implode("\n", $section);
}

return new self(
$indent,
$sections
);
return new self($sections);
}

public function withTag(TagInterface $tag): self
{
$sections = $this->sections;
if ($sections === []) {
return new self($this->indent, [$tag]);
return new self([$tag]);
}

foreach ($sections as $i => $section) {
Expand All @@ -94,7 +87,7 @@ public function withTag(TagInterface $tag): self

if ($tag->matches($section)) {
$sections[$i] = $tag;
return new self($this->indent, $sections);
return new self($sections);
}
}

Expand All @@ -111,7 +104,7 @@ public function withTag(TagInterface $tag): self
}

array_splice($sections, $before, 0, [$tag]);
return new self($this->indent, $sections);
return new self($sections);
}

public function withoutTag(TagInterface $tag): self
Expand All @@ -127,7 +120,7 @@ public function withoutTag(TagInterface $tag): self
}
}

return new self($this->indent, array_values($sections));
return new self(array_values($sections));
}

public function __toString(): string
Expand All @@ -136,9 +129,9 @@ public function __toString(): string
return '';
}

return $this->indent . "/**" . PHP_EOL
. $this->formatSections() . PHP_EOL
. $this->indent . ' */';
return "/**\n"
. $this->formatSections() . "\n"
. " */";
}

private static function isStartOfTag(string $line): bool
Expand All @@ -153,8 +146,8 @@ private static function isEndOfTag(string $line): bool

private function formatSections(): string
{
return $this->indent . ' * ' . $this->implode(array_map(
fn (string|TagInterface $s): string => $this->implode(explode(PHP_EOL, (string) $s)),
return ' * ' . $this->implode(array_map(
fn (string|TagInterface $s): string => $this->implode(explode("\n", (string) $s)),
$this->sections
));
}
Expand All @@ -164,6 +157,6 @@ private function formatSections(): string
*/
private function implode(array $parts): string
{
return implode(PHP_EOL . $this->indent . ' * ', $parts);
return implode("\n * ", $parts);
}
}
27 changes: 27 additions & 0 deletions src/Writer/Eol.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Kynx\Laminas\FormShape\Writer;

use function str_contains;
use function substr_count;

use const PHP_EOL;

final readonly class Eol
{
/**
* @return non-empty-string
*/
public static function getEol(string $contents): string
{
if (! str_contains($contents, "\n")) {
return PHP_EOL;
}

return substr_count($contents, "\r\n") >= substr_count($contents, "\n")
? "\r\n"
: "\n";
}
}
131 changes: 131 additions & 0 deletions src/Writer/FileWriter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Kynx\Laminas\FormShape\Writer;

use ReflectionClass;
use ReflectionMethod;

use function array_map;
use function array_splice;
use function explode;
use function file_get_contents;
use function file_put_contents;
use function implode;
use function preg_match;
use function str_starts_with;
use function trim;

final readonly class FileWriter
{
public function write(ReflectionClass $reflection, DocBlock $classDocBlock, ?DocBlock $getDataDocBlock): void
{
$contents = file_get_contents($reflection->getFileName());

$contents = $this->updateClassDocBlock($reflection, $contents, $classDocBlock);
if ($getDataDocBlock !== null) {
$contents = $this->updateGetDataDocBlock($reflection, $contents, $getDataDocBlock);
}

file_put_contents($reflection->getFileName(), $contents);
}

private function updateClassDocBlock(ReflectionClass $reflection, string $contents, DocBlock $docBlock): string
{
if ($reflection->getDocComment() === false) {
return $this->addDocBlock($reflection, $contents, $docBlock);
}

return $this->replaceDocBlock($reflection, $contents, $docBlock);
}

private function updateGetDataDocBlock(ReflectionClass $reflection, string $contents, DocBlock $docBlock): string
{
if (! $reflection->hasMethod('getData')) {
return $contents;
}
$method = $reflection->getMethod('getData');
if ($method->getDeclaringClass()->getName() !== $reflection->getName()) {
return $contents;
}

if ($method->getDocComment() === false) {
return $this->addDocBlock($method, $contents, $docBlock);
}

return $this->replaceDocBlock($method, $contents, $docBlock);
}

private function addDocBlock(
ReflectionClass|ReflectionMethod $reflection,
string $contents,
DocBlock $docBlock
): string {
$startLine = $reflection->getStartLine() - 1;

$eol = Eol::getEol($contents);
$lines = explode($eol, $contents);
$indent = $this->getIndent($lines[$startLine]);

for ($start = $startLine; $start > 0; $start--) {
$line = trim($lines[$start - 1]);
if (! str_starts_with($line, '#')) {
break;
}
}

array_splice($lines, $start, 0, $this->formatDocBlock($docBlock, $indent));

return implode($eol, $lines);
}

private function replaceDocBlock(
ReflectionClass|ReflectionMethod $reflection,
string $contents,
DocBlock $docBlock
): string {
$startLine = $reflection->getStartLine() - 1;

$eol = Eol::getEol($contents);
$lines = explode($eol, $contents);
$indent = $this->getIndent($lines[$startLine]);

$end = $startLine;
for ($start = $startLine; $start > 0; $start--) {
$line = trim($lines[$start]);
if ($line === '*/') {
$end = $start + 1;
}
if ($line === '/**') {
break;
}
}

array_splice($lines, $start, $end - $start, $this->formatDocBlock($docBlock, $indent));

return implode($eol, $lines);
}

/**
* @return list<string>
*/
private function formatDocBlock(DocBlock $docBlock, string $indent): array
{
$lines = (string) $docBlock;
if ($lines === '') {
return [];
}

return array_map(
static fn (string $line): string => $indent . $line,
explode("\n", $lines)
);
}

private function getIndent(string $line): string
{
preg_match('/^\s*/', $line, $matches);
return $matches[0] ?? '';
}
}
5 changes: 2 additions & 3 deletions test/Psalm/TypeNamerFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
namespace KynxTest\Laminas\FormShape\Psalm;

use Kynx\Laminas\FormShape\Psalm\TypeNamerFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use ReflectionClass;

/**
* @covers \Kynx\Laminas\FormShape\Psalm\TypeNamerFactory
*/
#[CoversClass(TypeNamerFactory::class)]
final class TypeNamerFactoryTest extends TestCase
{
public function testInvokeReturnsConfiguredInstance(): void
Expand Down
Loading

0 comments on commit 32b0979

Please sign in to comment.