From 5900d78cc91f7b4089e83f7f673c22ce71c57d3f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sun, 29 Oct 2017 12:25:13 +0100 Subject: [PATCH] FPPP: Support anonymous classes (#432) This is a huge hack... We temporarily create a new node with the correct structure and use that for printing. I think it would be better to always use a separate node type for NewAnonClass, rather than using a combination of New and Class, but this would require some larger changes, as this node type would have to be both Expr and ClassLike, which is not possible right now, as the latter is a class rather than an interface... --- CHANGELOG.md | 3 +- .../Internal/PrintableNewAnonClassNode.php | 55 +++++++++++++++++++ lib/PhpParser/PrettyPrinterAbstract.php | 26 ++++++--- test/PhpParser/PrettyPrinterTest.php | 4 -- test/code/formatPreservation/anonClasses.test | 16 ++++++ 5 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 lib/PhpParser/Internal/PrintableNewAnonClassNode.php create mode 100644 test/code/formatPreservation/anonClasses.test diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9ef700..8bd32ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Version 4.0.0-dev ### Fixed -* Added support for changing modifiers to formating-preserving pretty printer. +* Added support for changing modifiers to formatting-preserving pretty printer. +* Added support for anonymous classes to formatting-preserving pretty printer. Version 4.0.0-alpha1 (2017-10-18) --------------------------------- diff --git a/lib/PhpParser/Internal/PrintableNewAnonClassNode.php b/lib/PhpParser/Internal/PrintableNewAnonClassNode.php new file mode 100644 index 00000000..920916ec --- /dev/null +++ b/lib/PhpParser/Internal/PrintableNewAnonClassNode.php @@ -0,0 +1,55 @@ +args = $args; + $this->extends = $extends; + $this->implements = $implements; + $this->stmts = $stmts; + } + + public static function fromNewNode(Expr\New_ $newNode) { + $class = $newNode->class; + assert($class instanceof Node\Stmt\Class_); + assert($class->name === null); + return new self( + $newNode->args, $class->extends, $class->implements, + $class->stmts, $newNode->getAttributes() + ); + } + + public function getType(): string { + return 'Expr_PrintableNewAnonClass'; + } + + public function getSubNodeNames() : array { + return ['args', 'extends', 'implements', 'stmts']; + } +} \ No newline at end of file diff --git a/lib/PhpParser/PrettyPrinterAbstract.php b/lib/PhpParser/PrettyPrinterAbstract.php index 1fce1f83..8923c7f1 100644 --- a/lib/PhpParser/PrettyPrinterAbstract.php +++ b/lib/PhpParser/PrettyPrinterAbstract.php @@ -3,7 +3,9 @@ namespace PhpParser; use PhpParser\Internal\DiffElem; +use PhpParser\Internal\PrintableNewAnonClassNode; use PhpParser\Node\Expr; +use PhpParser\Node\Name; use PhpParser\Node\Scalar; use PhpParser\Node\Stmt; @@ -529,10 +531,11 @@ abstract class PrettyPrinterAbstract return $this->pFallback($node); } + $fallbackNode = $node; if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) { - // For anonymous classes the new and class nodes are intermixed, this would require - // special handling (or maybe a new node type just for this?) - return $this->pFallback($node); + // Normalize node structure of anonymous classes + $node = PrintableNewAnonClassNode::fromNewNode($node); + $origNode = PrintableNewAnonClassNode::fromNewNode($origNode); } $indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos); @@ -562,7 +565,7 @@ abstract class PrettyPrinterAbstract $this->listInsertionMap[$type . '->' . $subNodeName] ?? null ); if (null === $listResult) { - return $this->pFallback($node); + return $this->pFallback($fallbackNode); } $result .= $listResult; @@ -573,7 +576,7 @@ abstract class PrettyPrinterAbstract // Check if this is a modifier change $key = $type . '->' . $subNodeName; if (!isset($this->modifierChangeMap[$key])) { - return $this->pFallback($node); + return $this->pFallback($fallbackNode); } $findToken = $this->modifierChangeMap[$key]; @@ -585,7 +588,7 @@ abstract class PrettyPrinterAbstract // If a non-node, non-array subnode changed, we don't be able to do a partial // reconstructions, as we don't have enough offset information. Pretty print the // whole node instead. - return $this->pFallback($node); + return $this->pFallback($fallbackNode); } $extraLeft = ''; @@ -595,7 +598,7 @@ abstract class PrettyPrinterAbstract $subEndPos = $origSubNode->getEndTokenPos(); if ($subStartPos < 0 || $subEndPos < 0) { // Shouldn't happen - return $this->pFallback($node); + return $this->pFallback($fallbackNode); } } else { if ($subNode === null) { @@ -606,7 +609,7 @@ abstract class PrettyPrinterAbstract // A node has been inserted, check if we have insertion information for it $key = $type . '->' . $subNodeName; if (!isset($this->insertionMap[$key])) { - return $this->pFallback($node); + return $this->pFallback($fallbackNode); } list($findToken, $extraLeft, $extraRight) = $this->insertionMap[$key]; @@ -626,7 +629,7 @@ abstract class PrettyPrinterAbstract // A node has been removed, check if we have removal information for it $key = $type . '->' . $subNodeName; if (!isset($this->removalMap[$key])) { - return $this->pFallback($node); + return $this->pFallback($fallbackNode); } // Adjust positions to account for additional tokens that must be skipped @@ -1069,6 +1072,7 @@ abstract class PrettyPrinterAbstract 'Stmt_Break->num' => $stripBoth, 'Stmt_ClassMethod->returnType' => $stripColon, 'Stmt_Class->extends' => ['left' => T_EXTENDS], + 'Expr_PrintableNewAnonClass->extends' => ['left' => T_EXTENDS], 'Stmt_Continue->num' => $stripBoth, 'Stmt_Foreach->keyVar' => $stripDoubleArrow, 'Stmt_Function->returnType' => $stripColon, @@ -1102,6 +1106,7 @@ abstract class PrettyPrinterAbstract 'Stmt_Break->num' => [T_BREAK, ' ', null], 'Stmt_ClassMethod->returnType' => [')', ' : ', null], 'Stmt_Class->extends' => [null, ' extends ', null], + 'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null], 'Stmt_Continue->num' => [T_CONTINUE, ' ', null], 'Stmt_Foreach->keyVar' => [T_AS, null, ' => '], 'Stmt_Function->returnType' => [')', ' : ', null], @@ -1141,10 +1146,12 @@ abstract class PrettyPrinterAbstract 'Expr_List->items' => ', ', 'Expr_MethodCall->args' => ', ', 'Expr_New->args' => ', ', + 'Expr_PrintableNewAnonClass->args' => ', ', 'Expr_StaticCall->args' => ', ', 'Stmt_ClassConst->consts' => ', ', 'Stmt_ClassMethod->params' => ', ', 'Stmt_Class->implements' => ', ', + 'Expr_PrintableNewAnonClass->implements' => ', ', 'Stmt_Const->consts' => ', ', 'Stmt_Declare->declares' => ', ', 'Stmt_Echo->exprs' => ', ', @@ -1167,6 +1174,7 @@ abstract class PrettyPrinterAbstract 'Stmt_Case->stmts' => "\n", 'Stmt_Catch->stmts' => "\n", 'Stmt_Class->stmts' => "\n", + 'Expr_PrintableNewAnonClass->stmts' => "\n", 'Stmt_Interface->stmts' => "\n", 'Stmt_Trait->stmts' => "\n", 'Stmt_ClassMethod->stmts' => "\n", diff --git a/test/PhpParser/PrettyPrinterTest.php b/test/PhpParser/PrettyPrinterTest.php index 041adc7e..8b79225e 100644 --- a/test/PhpParser/PrettyPrinterTest.php +++ b/test/PhpParser/PrettyPrinterTest.php @@ -261,10 +261,6 @@ CODE * This test makes sure that the format-preserving pretty printer round-trips for all * the pretty printer tests (i.e. returns the input if no changes occurred). */ - if (false !== strpos($code, 'new class')) { - // Can't preserve formatting on anon classes for now - return; - } list($version) = $this->parseModeLine($modeLine); diff --git a/test/code/formatPreservation/anonClasses.test b/test/code/formatPreservation/anonClasses.test new file mode 100644 index 00000000..7551059f --- /dev/null +++ b/test/code/formatPreservation/anonClasses.test @@ -0,0 +1,16 @@ +Anonymous classes +----- +expr; +$new->class->extends = null; +$new->args[] = new Expr\Variable('y'); +----- +