nikic / PHP-Parser

A PHP parser written in PHP

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Maintain Existing Vertical Spacing Between Class Properties and Methods?

elvismdev opened this issue · comments

Hello maintainers and contributors,

First of all, thank you for this wonderful package. It's been an invaluable tool for parsing and manipulating PHP code.

I've been working on a project where I need to modify existing PHP files. While the parser is excellent for understanding and modifying the AST, I've noticed that it doesn't maintain the original vertical spacing between class properties and function methods in the source file.

Example Code
For instance, consider the following original PHP code:

class Example {
    public $property1;

    public $property2;


    public function method1() {
        // Some code
    }

    public function method2() {
        // Some code
    }
}

After parsing and pretty-printing the AST, the vertical spacing is lost:

class Example {
    public $property1;
    public $property2;
    public function method1() {
        // Some code
    }
    public function method2() {
        // Some code
    }
}

What I've Tried
I've tried looking into the PrettyPrinter configuration, but I couldn't find any options for maintaining vertical spacing.

$prettyPrinter = new PhpParser\PrettyPrinter\Standard();
echo $prettyPrinter->prettyPrintFile($stmts);

Question
Is there a way to maintain the existing vertical spacing from the original file? Or at least to maintain one newline space between class properties and function methods when pretty-printing the AST?

Thank you for your time and assistance.

Best regards,
Elvis

@nikic Thank you for pointing me to the section on formatting preservation in the pretty printer. It was exactly what I was looking for!

After reading through the documentation, I realized I was missing the NodeVisitor\CloningVisitor in my NodeTraverser. Adding it made all the difference.

$traverser->addVisitor(new NodeVisitor\CloningVisitor());

Once I included that line, my generated file began to output exactly as I wanted: it enforced Yoda conditions while also preserving the original file format.

For anyone else who comes across this issue, here's my working code:

<?php

require __DIR__ . '/../vendor/autoload.php';

use PhpParser\{Lexer, Node, NodeTraverser, NodeVisitor, NodeVisitorAbstract, Parser, PrettyPrinter};

// Custom node visitor for Yoda condition enforcement
class YodaConditionEnforcer extends NodeVisitorAbstract {
    private $insideForCondition = false;

    public function enterNode(Node $node) {
        // Check if we're entering a 'for' loop's condition
        if ($node instanceof Node\Stmt\For_) {
            $this->insideForCondition = true;
        }
    }

    public function leaveNode(Node $node) {
        // Check if the node is a binary comparison operation and we're NOT inside a 'for' loop's condition
        if (!$this->insideForCondition &&
            ($node instanceof Node\Expr\BinaryOp\Equal ||
             $node instanceof Node\Expr\BinaryOp\Identical ||
             $node instanceof Node\Expr\BinaryOp\NotEqual ||
             $node instanceof Node\Expr\BinaryOp\NotIdentical ||
             $node instanceof Node\Expr\BinaryOp\Smaller ||
             $node instanceof Node\Expr\BinaryOp\SmallerOrEqual ||
             $node instanceof Node\Expr\BinaryOp\Greater ||
             $node instanceof Node\Expr\BinaryOp\GreaterOrEqual)) {

            // Check if the left side is a variable and the right side is a constant (non-Yoda)
            if ($node->left instanceof Node\Expr\Variable &&
                $node->right instanceof Node\Scalar) {
                // Swap left and right nodes to enforce Yoda condition
                list($node->left, $node->right) = [$node->right, $node->left];
            }
        }

        // Check if we're leaving a 'for' loop's condition
        if ($node instanceof Node\Stmt\For_) {
            $this->insideForCondition = false;
        }
    }
}

$code = file_get_contents('php://stdin');

$lexer = new Lexer\Emulative([
    'usedAttributes' => [
        'comments',
        'startLine', 'endLine',
        'startTokenPos', 'endTokenPos',
    ],
]);
$parser = new Parser\Php7($lexer);

$traverser = new NodeTraverser();

$traverser->addVisitor(new YodaConditionEnforcer());

// Added missing NodeVisitor\CloningVisitor for correct file format preservation.
$traverser->addVisitor(new NodeVisitor\CloningVisitor());

$printer = new PrettyPrinter\Standard();

$oldStmts = $parser->parse($code);
$oldTokens = $lexer->getTokens();

$newStmts = $traverser->traverse($oldStmts);

$newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);

echo $newCode;

Thanks again!