Support insertion of nullable nodes

Still incomplete in some places and the formatting is not always
ideal.
This commit is contained in:
Nikita Popov 2017-01-21 20:40:05 +01:00
parent b9b6aeeed9
commit 5e565e8046
2 changed files with 255 additions and 6 deletions

View File

@ -110,6 +110,7 @@ abstract class PrettyPrinterAbstract
* this node.
*/
protected $removalMap;
protected $insertionMap;
/**
* Creates a pretty printer instance using the given options.
@ -363,6 +364,7 @@ abstract class PrettyPrinterAbstract
$this->initializeLabelCharMap();
$this->initializeFixupMap();
$this->initializeRemovalMap();
$this->initializeInsertionMap();
$this->origTokens = $origTokens;
$this->indentLevel = 0;
@ -436,7 +438,9 @@ abstract class PrettyPrinterAbstract
$subNode = $node->$subNodeName;
$origSubNode = $origNode->$subNodeName;
if (!$origSubNode instanceof Node || (!$subNode instanceof Node && $subNode !== null)) {
if ((!$subNode instanceof Node && $subNode !== null)
|| (!$origSubNode instanceof Node && $origSubNode !== null)
) {
if ($subNode === $origSubNode) {
// Unchanged, can reuse old code
continue;
@ -462,11 +466,34 @@ abstract class PrettyPrinterAbstract
return $this->pFallback($node);
}
$subStartPos = $origSubNode->getAttribute('startTokenPos', -1);
$subEndPos = $origSubNode->getAttribute('endTokenPos', -1);
if ($subStartPos < 0 || $subEndPos < 0) {
// Shouldn't happen
return $this->pFallback($node);
$extraLeft = '';
$extraRight = '';
if ($origSubNode !== null) {
$subStartPos = $origSubNode->getAttribute('startTokenPos', -1);
$subEndPos = $origSubNode->getAttribute('endTokenPos', -1);
if ($subStartPos < 0 || $subEndPos < 0) {
// Shouldn't happen
return $this->pFallback($node);
}
} else {
if ($subNode === null) {
// Both null, nothing to do
continue;
}
// 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);
}
list($findToken, $extraLeft, $extraRight) = $this->insertionMap[$key];
if (null !== $findToken) {
$subStartPos = $this->findRight($pos, $findToken) + 1;
} else {
$subStartPos = $pos;
}
$subEndPos = $subStartPos - 1;
}
if (null === $subNode) {
@ -489,6 +516,8 @@ abstract class PrettyPrinterAbstract
$result .= $this->getTokenCode($pos, $subStartPos, $indentAdjustment);
if (null !== $subNode) {
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->indentLevel = $this->getIndentationBefore($subStartPos) + $indentAdjustment;
@ -506,6 +535,8 @@ abstract class PrettyPrinterAbstract
$this->safeAppend($result, $res);
$this->indentLevel = $origIndentLevel;
$result .= $extraRight;
}
$pos = $subEndPos + 1;
@ -848,6 +879,17 @@ abstract class PrettyPrinterAbstract
return $pos;
}
protected function findRight($pos, $findTokenType) {
$tokens = $this->origTokens;
for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0];
if ($type === $findTokenType) {
return $pos;
}
}
return -1;
}
/**
* Determines whether the LHS of a call must be wrapped in parenthesis.
*
@ -988,6 +1030,12 @@ abstract class PrettyPrinterAbstract
}
}
/**
* Lazily initializes the removal map.
*
* The removal map is used to determine which additional tokens should be returned when a
* certain node is replaced by null.
*/
protected function initializeRemovalMap() {
if ($this->removalMap) return;
@ -1026,4 +1074,39 @@ abstract class PrettyPrinterAbstract
// 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a plain node
];
}
protected function initializeInsertionMap() {
if ($this->insertionMap) return;
// TODO: "yield" where both key and value are inserted doesn't work
$this->insertionMap = [
'Expr_ArrayDimFetch->dim' => ['[', null, null],
'Expr_ArrayItem->key' => [null, null, ' => '],
'Expr_Closure->returnType' => [')', ' : ', null],
'Expr_Ternary->if' => ['?', ' ', ' '],
'Expr_Yield->key' => [T_YIELD, ' ', ' => '],
'Expr_Yield->value' => [T_YIELD, ' ', null],
'Param->type' => [null, null, ' '],
'Param->default' => [null, ' = ', null],
'Stmt_Break->num' => [T_BREAK, ' ', null],
'Stmt_ClassMethod->returnType' => [')', ' : ', null],
'Stmt_Class->extends' => [null, ' extends ', null],
'Stmt_Continue->num' => [T_CONTINUE, ' ', null],
'Stmt_Foreach->keyVar' => [T_AS, ' ', ' => '],
'Stmt_Function->returnType' => [')', ' : ', null],
//'Stmt_If->else' => [null, ' ', null], // TODO
'Stmt_Namespace->name' => [T_NAMESPACE, ' ', null],
'Stmt_PropertyProperty->default' => [null, ' = ', null],
'Stmt_Return->expr' => [T_RETURN, ' ', null],
'Stmt_StaticVar->default' => [null, ' = ', null],
//'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, ' ', null], // TODO
'Stmt_TryCatch->finally' => [null, ' ', null],
// 'Expr_Exit->expr': Complicated due to optional ()
// 'Stmt_Case->cond': Conversion from default to case
// 'Stmt_Class->name': Unclear
// 'Stmt_Declare->stmts': Not a proper node
// 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a proper node
];
}
}

View File

@ -0,0 +1,166 @@
Insertion of a nullable node
-----
<?php
// TODO: The result spacing isn't always optimal. We may want to skip whitespace in some cases.
function
foo(
$x,
&$y
)
{}
$foo
[
];
[
$value
];
function
()
{};
$x
?
:
$y;
yield
$v ;
yield ;
break
;
continue
;
return
;
class
X
{
public
function y()
{}
private
$x
;
}
foreach (
$x
as
$y
) {}
static
$var
;
try {
} catch (X
$y) {
}
-----
$stmts[0]->returnType = new Node\Name('Foo');
$stmts[0]->params[0]->type = new Node\Identifier('int');
$stmts[0]->params[1]->type = new Node\Identifier('array');
$stmts[0]->params[1]->default = new Expr\ConstFetch(new Node\Name('null'));
$stmts[1]->expr->dim = new Expr\Variable('a');
$stmts[2]->expr->items[0]->key = new Scalar\String_('X');
$stmts[3]->expr->returnType = new Node\Name('Bar');
$stmts[4]->expr->if = new Expr\Variable('z');
$stmts[5]->expr->key = new Expr\Variable('k');
$stmts[6]->expr->value = new Expr\Variable('v');
$stmts[7]->num = new Scalar\LNumber(2);
$stmts[8]->num = new Scalar\LNumber(2);
$stmts[9]->expr = new Expr\Variable('x');
$stmts[10]->extends = new Node\Name\FullyQualified('Bar');
$stmts[10]->stmts[0]->returnType = new Node\Name('Y');
$stmts[10]->stmts[1]->props[0]->default = new Scalar\DNumber(42.0);
$stmts[11]->keyVar = new Expr\Variable('z');
$stmts[12]->vars[0]->default = new Scalar\String_('abc');
$stmts[13]->finally = new Stmt\Finally_([]);
-----
<?php
// TODO: The result spacing isn't always optimal. We may want to skip whitespace in some cases.
function
foo(
int $x,
array &$y = null
) : Foo
{}
$foo
[$a
];
[
'X' => $value
];
function
() : Bar
{};
$x
? $z
:
$y;
yield $k =>
$v ;
yield $v ;
break 2
;
continue 2
;
return $x
;
class
X extends \Bar
{
public
function y() : Y
{}
private
$x = 42.0
;
}
foreach (
$x
as $z =>
$y
) {}
static
$var = 'abc'
;
try {
} catch (X
$y) {
} finally {
}
-----
<?php
namespace
{ echo 42; }
-----
$stmts[0]->name = new Node\Name('Foo');
-----
<?php
namespace Foo
{ echo 42; }