Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.37% covered (success)
97.37%
37 / 38
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoveEmptyDocBlockRector
97.37% covered (success)
97.37%
37 / 38
75.00% covered (warning)
75.00%
3 / 4
16
0.00% covered (danger)
0.00%
0 / 1
 getRuleDefinition
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getNodeTypes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 refactor
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
7
 isEmptyDocBlock
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of fast-forward/dev-tools.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/dev-tools
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\DevTools\Rector;
20
21use phpowermove\docblock\Docblock;
22use PhpParser\Comment\Doc;
23use PhpParser\Node;
24use PhpParser\Node\Stmt\ClassMethod;
25use PhpParser\Node\Stmt\Class_;
26use Rector\Rector\AbstractRector;
27use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
28
29use function Safe\preg_split;
30use function Safe\preg_replace;
31
32/**
33 * Implements automation targeting the removal of purposeless empty DocBlock structures natively.
34 * It MUST intercept specific nodes exclusively and SHALL prune invalid redundant properties transparently.
35 */
36final class RemoveEmptyDocBlockRector extends AbstractRector
37{
38    /**
39     * Resolves the defined documentation object detailing expected behavior parameters intrinsically.
40     *
41     * The method MUST clarify accurately to external systems the primary objective successfully.
42     *
43     * @return RuleDefinition the instantiated declaration reference properly bounded natively
44     */
45    public function getRuleDefinition(): RuleDefinition
46    {
47        return new RuleDefinition('Remove empty docblocks from classes and methods', [
48            new \Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample(
49                "/**\n *\n */\nclass SomeClass {}",
50                "class SomeClass {}"
51            )
52        ]);
53    }
54
55    /**
56     * Exposes intercepted root AST targets consistently during analytical sweeps functionally.
57     *
58     * The method MUST enforce inspections primarily on class frames and class components cleanly.
59     *
60     * @return array<int, class-string<Node>> bound runtime types reliably tracked correctly
61     */
62    public function getNodeTypes(): array
63    {
64        return [Class_::class, ClassMethod::class];
65    }
66
67    /**
68     * Strips empty document definitions structurally from the designated AST dynamically parsed.
69     *
70     * The method MUST systematically evaluate content verifying an absolute absence accurately.
71     * If validated, it SHALL destroy the related virtual node properties carefully.
72     *
73     * @param Node $node the dynamic input tree chunk inherently processed strictly
74     *
75     * @return Node|null the streamlined object successfully truncated or null unhandled
76     */
77    public function refactor(Node $node): ?Node
78    {
79        if (! $node instanceof Class_ && ! $node instanceof ClassMethod) {
80            return null;
81        }
82
83        $docComment = $node->getDocComment();
84
85        if (! $docComment instanceof Doc) {
86            return null;
87        }
88
89        if (! $this->isEmptyDocBlock($docComment->getText())) {
90            return null;
91        }
92
93        $remainingComments = [];
94
95        foreach ($node->getComments() as $comment) {
96            if ($comment === $docComment) {
97                continue;
98            }
99
100            $remainingComments[] = $comment;
101        }
102
103        $node->setDocComment(new Doc(''));
104        $node->setAttribute('comments', $remainingComments);
105        $node->setAttribute('docComment', null);
106        $node->setAttribute('php_doc_info', null);
107
108        return $node;
109    }
110
111    /**
112     * Ascertains visually and technically if a provided block comprises an absolute empty placeholder structure safely.
113     *
114     * The method MUST strip control characters accurately isolating legitimate characters completely.
115     *
116     * @param string $docBlock the textual contents actively extracted continuously dynamically natively
117     *
118     * @return bool success configuration inherently signaling absolute absence accurately effectively strictly
119     */
120    private function isEmptyDocBlock(string $docBlock): bool
121    {
122        $lines = preg_split('/\R/', $docBlock);
123
124        if (! \is_array($lines)) {
125            return false;
126        }
127
128        foreach ($lines as $line) {
129            $normalizedLine = trim((string) $line);
130            if ('/**' === $normalizedLine || '*/' === $normalizedLine || '*' === $normalizedLine) {
131                continue;
132            }
133
134            $normalizedLine = preg_replace('#^/\*\*\s*#', '', $normalizedLine);
135            $normalizedLine = preg_replace('#\s*\*/$#', '', (string) $normalizedLine);
136            $normalizedLine = preg_replace('#^\*\s?#', '', (string) $normalizedLine);
137            $normalizedLine = trim((string) $normalizedLine);
138
139            if ('' !== $normalizedLine) {
140                return false;
141            }
142        }
143
144        return true;
145    }
146}