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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ return (new PhpCsFixer\Config())
],
'preserve_existing' => true,
'separate' => 'none',
'add_class_name' => true,
],
])
;
Expand All @@ -98,7 +99,8 @@ return (new PhpCsFixer\Config())
'package' => 'PhpDocBlockHeaderFixer',
],
preserveExisting: true,
separate: \KonradMichalik\PhpDocBlockHeaderFixer\Model\Separate::None
separate: \KonradMichalik\PhpDocBlockHeaderFixer\Enum\Separate::None,
addClassName: true
)->__toArray()
])
;
Expand Down
5 changes: 4 additions & 1 deletion src/Generators/DocBlockHeader.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ private function __construct(
public readonly array $annotations,
public readonly bool $preserveExisting,
public readonly Separate $separate,
public readonly bool $addClassName,
) {}

/**
Expand All @@ -44,10 +45,11 @@ public static function create(
array $annotations,
bool $preserveExisting = true,
Separate $separate = Separate::Both,
bool $addClassName = false,
): self {
self::validateAnnotations($annotations);

return new self($annotations, $preserveExisting, $separate);
return new self($annotations, $preserveExisting, $separate, $addClassName);
}

/**
Expand All @@ -60,6 +62,7 @@ public function __toArray(): array
'annotations' => $this->annotations,
'preserve_existing' => $this->preserveExisting,
'separate' => $this->separate->value,
'add_class_name' => $this->addClassName,
],
];
}
Expand Down
65 changes: 52 additions & 13 deletions src/Rules/DocBlockHeaderFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public function getConfigurationDefinition(): FixerConfigurationResolverInterfac
->setAllowedValues(Separate::getList())
->setDefault(Separate::Both->value)
->getOption(),
(new FixerOptionBuilder('add_class_name', 'Add class name before annotations'))
->setAllowedTypes(['bool'])
->setDefault(false)
->getOption(),
]);
}

Expand All @@ -103,27 +107,50 @@ protected function applyFix(SplFileInfo $file, Tokens $tokens): void
continue;
}

$this->processClassDocBlock($tokens, $index, $annotations);
$className = $this->getClassName($tokens, $index);
$this->processClassDocBlock($tokens, $index, $annotations, $className);
}
}

/**
* @param array<string, string|array<string>> $annotations
*/
private function processClassDocBlock(Tokens $tokens, int $classIndex, array $annotations): void
private function processClassDocBlock(Tokens $tokens, int $classIndex, array $annotations, string $className): void
{
$existingDocBlockIndex = $this->findExistingDocBlock($tokens, $classIndex);
$preserveExisting = $this->resolvedConfiguration['preserve_existing'] ?? true;

if (null !== $existingDocBlockIndex) {
if ($preserveExisting) {
$this->mergeWithExistingDocBlock($tokens, $existingDocBlockIndex, $annotations);
$this->mergeWithExistingDocBlock($tokens, $existingDocBlockIndex, $annotations, $className);
} else {
$this->replaceDocBlock($tokens, $existingDocBlockIndex, $annotations);
$this->replaceDocBlock($tokens, $existingDocBlockIndex, $annotations, $className);
}
} else {
$this->insertNewDocBlock($tokens, $classIndex, $annotations);
$this->insertNewDocBlock($tokens, $classIndex, $annotations, $className);
}
}

private function getClassName(Tokens $tokens, int $classIndex): string
{
// Look for the class name token after the 'class' keyword
for ($i = $classIndex + 1, $limit = $tokens->count(); $i < $limit; ++$i) {
$token = $tokens[$i];

if ($token->isWhitespace()) {
continue;
}

// The first non-whitespace token after 'class' should be the class name
if ($token->isGivenKind(T_STRING)) {
return $token->getContent();
}

// If we hit anything else, stop looking
break;
}

return '';
}

private function findExistingDocBlock(Tokens $tokens, int $classIndex): ?int
Expand Down Expand Up @@ -151,29 +178,29 @@ private function findExistingDocBlock(Tokens $tokens, int $classIndex): ?int
/**
* @param array<string, string|array<string>> $annotations
*/
private function mergeWithExistingDocBlock(Tokens $tokens, int $docBlockIndex, array $annotations): void
private function mergeWithExistingDocBlock(Tokens $tokens, int $docBlockIndex, array $annotations, string $className): void
{
$existingContent = $tokens[$docBlockIndex]->getContent();
$existingAnnotations = $this->parseExistingAnnotations($existingContent);
$mergedAnnotations = $this->mergeAnnotations($existingAnnotations, $annotations);

$newDocBlock = $this->buildDocBlock($mergedAnnotations);
$newDocBlock = $this->buildDocBlock($mergedAnnotations, $className);
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $newDocBlock]);
}

/**
* @param array<string, string|array<string>> $annotations
*/
private function replaceDocBlock(Tokens $tokens, int $docBlockIndex, array $annotations): void
private function replaceDocBlock(Tokens $tokens, int $docBlockIndex, array $annotations, string $className): void
{
$newDocBlock = $this->buildDocBlock($annotations);
$newDocBlock = $this->buildDocBlock($annotations, $className);
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $newDocBlock]);
}

/**
* @param array<string, string|array<string>> $annotations
*/
private function insertNewDocBlock(Tokens $tokens, int $classIndex, array $annotations): void
private function insertNewDocBlock(Tokens $tokens, int $classIndex, array $annotations, string $className): void
{
$separate = $this->resolvedConfiguration['separate'] ?? 'both';
$insertIndex = $this->findInsertPosition($tokens, $classIndex);
Expand All @@ -186,7 +213,7 @@ private function insertNewDocBlock(Tokens $tokens, int $classIndex, array $annot
}

// Add the DocBlock
$docBlock = $this->buildDocBlock($annotations);
$docBlock = $this->buildDocBlock($annotations, $className);
$tokensToInsert[] = new Token([T_DOC_COMMENT, $docBlock]);

// Add separation after comment if needed
Expand Down Expand Up @@ -255,14 +282,26 @@ private function mergeAnnotations(array $existing, array $new): array
/**
* @param array<string, string|array<string>> $annotations
*/
private function buildDocBlock(array $annotations): string
private function buildDocBlock(array $annotations, string $className): string
{
if (empty($annotations)) {
$addClassName = $this->resolvedConfiguration['add_class_name'] ?? false;

if (empty($annotations) && !$addClassName) {
return "/**\n */";
}

$docBlock = "/**\n";

// Add class name with dot if configured
if ($addClassName && !empty($className)) {
$docBlock .= " * {$className}.\n";

// Add empty line after class name if there are annotations
if (!empty($annotations)) {
$docBlock .= " *\n";
}
}

foreach ($annotations as $tag => $value) {
if (empty($value)) {
$docBlock .= " * @{$tag}\n";
Expand Down
42 changes: 42 additions & 0 deletions tests/src/Generators/DocBlockHeaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public function testToArrayReturnsCorrectStructure(): void
'annotations' => $annotations,
'preserve_existing' => false,
'separate' => 'top',
'add_class_name' => false,
],
];

Expand All @@ -109,6 +110,7 @@ public function testToArrayWithDefaultParameters(): void
'annotations' => $annotations,
'preserve_existing' => true,
'separate' => 'both',
'add_class_name' => false,
],
];

Expand Down Expand Up @@ -268,4 +270,44 @@ public function testClassIsFinal(): void

self::assertTrue($reflection->isFinal());
}

public function testCreateWithAddClassName(): void
{
$annotations = ['author' => 'John Doe'];
$docBlockHeader = DocBlockHeader::create(
$annotations,
true,
Separate::None,
true,
);

self::assertSame($annotations, $docBlockHeader->annotations);
self::assertTrue($docBlockHeader->preserveExisting);
self::assertSame(Separate::None, $docBlockHeader->separate);
self::assertTrue($docBlockHeader->addClassName);
}

public function testToArrayWithAddClassName(): void
{
$annotations = ['author' => 'John Doe'];
$docBlockHeader = DocBlockHeader::create(
$annotations,
false,
Separate::Top,
true,
);

$result = $docBlockHeader->__toArray();

$expected = [
'KonradMichalik/docblock_header_comment' => [
'annotations' => $annotations,
'preserve_existing' => false,
'separate' => 'top',
'add_class_name' => true,
],
];

self::assertSame($expected, $result);
}
}
Loading