Skip to content

Commit

Permalink
Support insertion of nullable nodes
Browse files Browse the repository at this point in the history
Still incomplete in some places and the formatting is not always
ideal.
  • Loading branch information
nikic committed Jan 21, 2017
1 parent b9b6aee commit 5e565e8
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 6 deletions.
95 changes: 89 additions & 6 deletions lib/PhpParser/PrettyPrinterAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ abstract class PrettyPrinterAbstract
* this node.
*/
protected $removalMap;
protected $insertionMap;

/**
* Creates a pretty printer instance using the given options.
Expand Down Expand Up @@ -363,6 +364,7 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori
$this->initializeLabelCharMap();
$this->initializeFixupMap();
$this->initializeRemovalMap();
$this->initializeInsertionMap();

$this->origTokens = $origTokens;
$this->indentLevel = 0;
Expand Down Expand Up @@ -436,7 +438,9 @@ protected function p(Node $node) {
$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;
Expand All @@ -462,11 +466,34 @@ protected function p(Node $node) {
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) {
Expand All @@ -489,6 +516,8 @@ protected function p(Node $node) {
$result .= $this->getTokenCode($pos, $subStartPos, $indentAdjustment);

if (null !== $subNode) {
$result .= $extraLeft;

$origIndentLevel = $this->indentLevel;
$this->indentLevel = $this->getIndentationBefore($subStartPos) + $indentAdjustment;

Expand All @@ -506,6 +535,8 @@ protected function p(Node $node) {

$this->safeAppend($result, $res);
$this->indentLevel = $origIndentLevel;

$result .= $extraRight;
}

$pos = $subEndPos + 1;
Expand Down Expand Up @@ -848,6 +879,17 @@ protected function skipRightWhitespace($pos) {
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.
*
Expand Down Expand Up @@ -988,6 +1030,12 @@ protected function initializeFixupMap() {
}
}

/**
* 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;

Expand Down Expand Up @@ -1026,4 +1074,39 @@ protected function initializeRemovalMap() {
// '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
];
}
}
166 changes: 166 additions & 0 deletions test/code/formatPreservation/insertionOfNullable.test
Original file line number Diff line number Diff line change
@@ -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; }

0 comments on commit 5e565e8

Please sign in to comment.