phpautomationrefactoringcommentsphpdoc

Is there any way to automate refactoring old style comments to phpDoc format?


I'm refactoring a huge amount of code which has all function documented in simple comments like this:

//This is foo funtion
function foo($foo)
{
}

And my goal is to make it looks like this:

  /**
     * This is foo funtion
     * @param $foo - foo param
     * @return mixed
     */
    function foo($foo)
    {
    }

Is there any automation tools which can do this? Or it's should be done only manually?


Solution

  • I think the only way of such refactoring is to create your custom code. It doesn't seem to difficulte to create one, but obviously, it can cause more problems than it will solve. If the project doesn't have a quite inclusive test suite, I think it will be way more safer to make it manually. Also, it will be a great idea to test your automation code before you mess with your real code. This means long time to spend. Nevertheless, the code down below is an idead of how it can be done. Finally, I think implementing typehint instead of PHPDocs will make your app nicer and safer, but of course, your choise.

    class Refactor
    {
        public function handle(string $path)
        {
            $this->refactorFiles($this->listAllFiles($path));
        }
    
        public function listAllFiles(string $path): array
        {
            $files = [];
    
            foreach ($this->listFolderContent($path) as $item) {
                $pointer = $path . DIRECTORY_SEPARATOR .  $item;
    
                if (is_dir($pointer)) {
                    $files = [...$files, ...$this->listAllFiles($pointer)];
                } else {
                    $files[] = $pointer;
                }
            }
    
            return $files;
        }
    
        private function listFolderContent(string $path)
        {
            return array_diff(scandir($path), array_merge(['.', '..']));
        }
    
        private function refactorFiles(array $files): void
        {
            array_map(fn ($file) => $this->refactorFile($file), $files);
        }
    
        private function refactorFile(string $file): void
        {
            $content = file($file);
            $newContent = [];
    
            foreach ($content as $i => $line) {
                if (!str_contains($line, 'function')) {
                    $newContent[] = $line;
                    continue;
                };
    
                $firstIndex = $this->indexOfTheLineWhereTheCommentStarts($content, $i);
                $comments = $this->getComment($content, $firstIndex, $i - 1);
                $newContent = [...$newContent, ...$this->buildDocBlock($comments, $line)];
            }
    
            file_put_contents($file, implode('', $newContent));
        }
    
        private function indexOfTheLineWhereTheCommentStarts(array $content, int $index)
        {
            $prevIndex = $index - 1;
            $prevLine = $content[$prevIndex];
    
            while (str_contains($prevLine, '//')) {
                $prevIndex = $prevIndex - 1;
                $prevLine = $content[$prevIndex];
            }
    
            return $prevIndex;
        }
    
        private function getComment(array $content, int $firstIndex, int $lastIndex): array
        {
            return array_map(
                fn ($commentLine) => trim(str_replace('//', '', $commentLine)),
                array_slice($content, $firstIndex, $lastIndex - $firstIndex + 1)
            );
        }
    
        private function buildDocBlock(array $comments, string $line): array
        {
            return array_map(
                fn ($line) => "    $line",
                [
                    '/**',
                    ...$this->convertCommentLines($comments),
                    ' *',
                    ...$this->setParameters($line),
                    ' * @return mixed',
                    ' */'
                ]
            );
        }
    
        private function convertCommentLines(array $comments): array
        {
            return array_map(fn ($line) => ' * ' . trim(str_replace('//', '', $line)), $comments);
        }
    
        private function setParameters(string $line): array
        {
            return array_map(
                fn ($param) => ' * @param ' . trim(explode('=', $param)[0]),
                $this->isolateParams($line)
            );
        }
    
        private function isolateParams(string $line): array
        {
            return explode(',', explode(')', explode('(', $line)[1] ?? '')[0]);
        }
    }