mirror of
https://github.com/phabelio/PHP-Parser.git
synced 2024-11-30 04:29:15 +01:00
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...
This commit is contained in:
parent
776275361a
commit
5900d78cc9
@ -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)
|
||||
---------------------------------
|
||||
|
55
lib/PhpParser/Internal/PrintableNewAnonClassNode.php
Normal file
55
lib/PhpParser/Internal/PrintableNewAnonClassNode.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
/**
|
||||
* This node is used internally by the format-preserving pretty printer to print anonymous classes.
|
||||
*
|
||||
* The normal anonymous class structure violates assumptions about the order of token offsets.
|
||||
* Namely, the constructor arguments are part of the Expr\New_ node and follow the class node, even
|
||||
* though they are actually interleaved with them. This special node type is used temporarily to
|
||||
* restore a sane token offset order.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PrintableNewAnonClassNode extends Expr {
|
||||
/** @var Node\Arg[] Arguments */
|
||||
public $args;
|
||||
/** @var null|Node\Name Name of extended class */
|
||||
public $extends;
|
||||
/** @var Node\Name[] Names of implemented interfaces */
|
||||
public $implements;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
|
||||
public function __construct(
|
||||
array $args, Node\Name $extends = null, array $implements, array $stmts, array $attributes
|
||||
) {
|
||||
parent::__construct($attributes);
|
||||
$this->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'];
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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);
|
||||
|
||||
|
16
test/code/formatPreservation/anonClasses.test
Normal file
16
test/code/formatPreservation/anonClasses.test
Normal file
@ -0,0 +1,16 @@
|
||||
Anonymous classes
|
||||
-----
|
||||
<?php
|
||||
new class
|
||||
($x)
|
||||
extends X
|
||||
{ };
|
||||
-----
|
||||
$new = $stmts[0]->expr;
|
||||
$new->class->extends = null;
|
||||
$new->args[] = new Expr\Variable('y');
|
||||
-----
|
||||
<?php
|
||||
new class
|
||||
($x, $y)
|
||||
{ };
|
Loading…
Reference in New Issue
Block a user