Skip to content

Commit aa72c5d

Browse files
committed
FPPP: Support inserting into empty lists
1 parent 60d025a commit aa72c5d

File tree

3 files changed

+197
-12
lines changed

3 files changed

+197
-12
lines changed

lib/PhpParser/PrettyPrinterAbstract.php

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ abstract class PrettyPrinterAbstract
120120
*/
121121
protected $removalMap;
122122
/**
123-
* @var mixed[] Map from "{$node->getType()}->{$subNode}" to [$find, $extraLeft, $extraRight].
123+
* @var mixed[] Map from "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight].
124124
* $find is an optional token after which the insertion occurs. $extraLeft/Right
125125
* are optionally added before/after the main insertions.
126126
*/
@@ -130,6 +130,7 @@ abstract class PrettyPrinterAbstract
130130
* between elements of this list subnode.
131131
*/
132132
protected $listInsertionMap;
133+
protected $emptyListInsertionMap;
133134
/** @var int[] Map from "{$node->getType()}->{$subNode}" to token before which the modifiers
134135
* should be reprinted. */
135136
protected $modifierChangeMap;
@@ -479,6 +480,7 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori
479480
$this->initializeRemovalMap();
480481
$this->initializeInsertionMap();
481482
$this->initializeListInsertionMap();
483+
$this->initializeEmptyListInsertionMap();
482484
$this->initializeModifierChangeMap();
483485

484486
$this->resetState();
@@ -487,7 +489,7 @@ public function printFormatPreserving(array $stmts, array $origStmts, array $ori
487489
$this->preprocessNodes($stmts);
488490

489491
$pos = 0;
490-
$result = $this->pArray($stmts, $origStmts, $pos, 0, 'stmts', null, "\n");
492+
$result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null);
491493
if (null !== $result) {
492494
$result .= $this->origTokens->getTokenCode($pos, count($origTokens), 0);
493495
} else {
@@ -568,9 +570,8 @@ protected function p(Node $node, $parentFormatPreserved = false) : string {
568570
if (is_array($subNode) && is_array($origSubNode)) {
569571
// Array subnode changed, we might be able to reconstruct it
570572
$listResult = $this->pArray(
571-
$subNode, $origSubNode, $pos, $indentAdjustment, $subNodeName,
572-
$fixupInfo[$subNodeName] ?? null,
573-
$this->listInsertionMap[$type . '->' . $subNodeName] ?? null
573+
$subNode, $origSubNode, $pos, $indentAdjustment, $type, $subNodeName,
574+
$fixupInfo[$subNodeName] ?? null
574575
);
575576
if (null === $listResult) {
576577
return $this->pFallback($fallbackNode);
@@ -689,18 +690,21 @@ protected function p(Node $node, $parentFormatPreserved = false) : string {
689690
* @param array $origNodes Original nodes
690691
* @param int $pos Current token position (updated by reference)
691692
* @param int $indentAdjustment Adjustment for indentation
693+
* @param string $parentNodeType Type of the containing node.
692694
* @param string $subNodeName Name of array subnode.
693695
* @param null|int $fixup Fixup information for array item nodes
694-
* @param null|string $insertStr Separator string to use for insertions
695696
*
696697
* @return null|string Result of pretty print or null if cannot preserve formatting
697698
*/
698699
protected function pArray(
699700
array $nodes, array $origNodes, int &$pos, int $indentAdjustment,
700-
string $subNodeName, $fixup, $insertStr
701+
string $parentNodeType, string $subNodeName, $fixup
701702
) {
702703
$diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes);
703704

705+
$mapKey = $parentNodeType . '->' . $subNodeName;
706+
$insertStr = $this->listInsertionMap[$mapKey] ?? null;
707+
704708
$beforeFirstKeepOrReplace = true;
705709
$delayedAdd = [];
706710
$lastElemIndentLevel = $this->indentLevel;
@@ -874,7 +878,27 @@ protected function pArray(
874878

875879
if (!empty($delayedAdd)) {
876880
// TODO Handle insertion into empty list
877-
return null;
881+
if (!isset($this->emptyListInsertionMap[$mapKey])) {
882+
return null;
883+
}
884+
885+
list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey];
886+
if (null !== $findToken) {
887+
$insertPos = $this->origTokens->findRight($pos, $findToken) + 1;
888+
$result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment);
889+
$pos = $insertPos;
890+
}
891+
892+
$first = true;
893+
$result .= $extraLeft;
894+
foreach ($delayedAdd as $delayedAddNode) {
895+
if (!$first) {
896+
$result .= $insertStr;
897+
}
898+
$result .= $this->p($delayedAddNode, true);
899+
$first = false;
900+
}
901+
$result .= $extraRight;
878902
}
879903

880904
return $result;
@@ -1229,6 +1253,7 @@ protected function initializeInsertionMap() {
12291253
if ($this->insertionMap) return;
12301254

12311255
// TODO: "yield" where both key and value are inserted doesn't work
1256+
// [$find, $beforeToken, $extraLeft, $extraRight]
12321257
$this->insertionMap = [
12331258
'Expr_ArrayDimFetch->dim' => ['[', false, null, null],
12341259
'Expr_ArrayItem->key' => [null, false, null, ' => '],
@@ -1330,6 +1355,59 @@ protected function initializeListInsertionMap() {
13301355
'Stmt_TraitUse->adaptations' => "\n",
13311356
'Stmt_TryCatch->stmts' => "\n",
13321357
'Stmt_While->stmts' => "\n",
1358+
1359+
// dummy for top-level context
1360+
'File->stmts' => "\n",
1361+
];
1362+
}
1363+
1364+
protected function initializeEmptyListInsertionMap() {
1365+
if ($this->emptyListInsertionMap) return;
1366+
1367+
// TODO Insertion into empty statement lists.
1368+
1369+
// [$find, $extraLeft, $extraRight]
1370+
$this->emptyListInsertionMap = [
1371+
'Expr_ArrowFunction->params' => ['(', '', ''],
1372+
'Expr_Closure->uses' => [')', ' use(', ')'],
1373+
'Expr_Closure->params' => ['(', '', ''],
1374+
'Expr_FuncCall->args' => ['(', '', ''],
1375+
'Expr_MethodCall->args' => ['(', '', ''],
1376+
'Expr_New->args' => ['(', '', ''],
1377+
'Expr_PrintableNewAnonClass->args' => ['(', '', ''],
1378+
'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''],
1379+
'Expr_StaticCall->args' => ['(', '', ''],
1380+
'Stmt_Class->implements' => [null, ' implements ', ''],
1381+
'Stmt_ClassMethod->params' => ['(', '', ''],
1382+
'Stmt_Interface->extends' => [null, ' extends ', ''],
1383+
'Stmt_Function->params' => ['(', '', ''],
1384+
1385+
/* These cannot be empty to start with:
1386+
* Expr_Isset->vars
1387+
* Stmt_Catch->types
1388+
* Stmt_Const->consts
1389+
* Stmt_ClassConst->consts
1390+
* Stmt_Declare->declares
1391+
* Stmt_Echo->exprs
1392+
* Stmt_Global->vars
1393+
* Stmt_GroupUse->uses
1394+
* Stmt_Property->props
1395+
* Stmt_StaticVar->vars
1396+
* Stmt_TraitUse->traits
1397+
* Stmt_TraitUseAdaptation_Precedence->insteadof
1398+
* Stmt_Unset->vars
1399+
* Stmt_Use->uses
1400+
*/
1401+
1402+
/* TODO
1403+
* Stmt_If->elseifs
1404+
* Stmt_TryCatch->catches
1405+
* Expr_Array->items
1406+
* Expr_List->items
1407+
* Stmt_For->init
1408+
* Stmt_For->cond
1409+
* Stmt_For->loop
1410+
*/
13331411
];
13341412
}
13351413

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
Inserting into an empty list
2+
-----
3+
<?php
4+
class
5+
Test {}
6+
7+
interface
8+
Test {}
9+
-----
10+
$stmts[0]->implements[] = new Node\Name('Iface');
11+
$stmts[0]->implements[] = new Node\Name('Iface2');
12+
$stmts[1]->extends[] = new Node\Name('Iface');
13+
$stmts[1]->extends[] = new Node\Name('Iface2');
14+
-----
15+
<?php
16+
class
17+
Test implements Iface, Iface2 {}
18+
19+
interface
20+
Test extends Iface, Iface2 {}
21+
-----
22+
<?php
23+
function test
24+
() {}
25+
26+
class Test {
27+
public function
28+
test
29+
() {}
30+
}
31+
32+
function
33+
() {};
34+
35+
fn()
36+
=> 42;
37+
-----
38+
$stmts[0]->params[] = new Node\Param(new Node\Expr\Variable('a'));
39+
$stmts[0]->params[] = new Node\Param(new Node\Expr\Variable('b'));
40+
$stmts[1]->stmts[0]->params[] = new Node\Param(new Node\Expr\Variable('a'));
41+
$stmts[1]->stmts[0]->params[] = new Node\Param(new Node\Expr\Variable('b'));
42+
$stmts[2]->expr->params[] = new Node\Param(new Node\Expr\Variable('a'));
43+
$stmts[2]->expr->params[] = new Node\Param(new Node\Expr\Variable('b'));
44+
$stmts[2]->expr->uses[] = new Node\Expr\Variable('c');
45+
$stmts[2]->expr->uses[] = new Node\Expr\Variable('d');
46+
$stmts[3]->expr->params[] = new Node\Param(new Node\Expr\Variable('a'));
47+
$stmts[3]->expr->params[] = new Node\Param(new Node\Expr\Variable('b'));
48+
-----
49+
<?php
50+
function test
51+
($a, $b) {}
52+
53+
class Test {
54+
public function
55+
test
56+
($a, $b) {}
57+
}
58+
59+
function
60+
($a, $b) use($c, $d) {};
61+
62+
fn($a, $b)
63+
=> 42;
64+
-----
65+
<?php
66+
foo
67+
();
68+
69+
$foo->
70+
bar();
71+
72+
Foo
73+
::bar ();
74+
75+
new
76+
Foo
77+
();
78+
79+
new class
80+
()
81+
extends Foo {};
82+
-----
83+
$stmts[0]->expr->args[] = new Node\Expr\Variable('a');
84+
$stmts[0]->expr->args[] = new Node\Expr\Variable('b');
85+
$stmts[1]->expr->args[] = new Node\Expr\Variable('a');
86+
$stmts[1]->expr->args[] = new Node\Expr\Variable('b');
87+
$stmts[2]->expr->args[] = new Node\Expr\Variable('a');
88+
$stmts[2]->expr->args[] = new Node\Expr\Variable('b');
89+
$stmts[3]->expr->args[] = new Node\Expr\Variable('a');
90+
$stmts[3]->expr->args[] = new Node\Expr\Variable('b');
91+
$stmts[4]->expr->args[] = new Node\Expr\Variable('a');
92+
$stmts[4]->expr->args[] = new Node\Expr\Variable('b');
93+
-----
94+
<?php
95+
foo
96+
($a, $b);
97+
98+
$foo->
99+
bar($a, $b);
100+
101+
Foo
102+
::bar ($a, $b);
103+
104+
new
105+
Foo
106+
($a, $b);
107+
108+
new class
109+
($a, $b)
110+
extends Foo {};

test/code/formatPreservation/listInsertion.test

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,10 @@ function test($param0, Foo $param1) {}
7373
function test() {}
7474
-----
7575
$stmts[0]->params[] = new Node\Param(new Expr\Variable('param0'));
76-
/* Insertion into empty list not handled yet */
7776
-----
7877
<?php
7978

80-
function test($param0)
81-
{
82-
}
79+
function test($param0) {}
8380
-----
8481
<?php
8582

0 commit comments

Comments
 (0)