Skip to content

Commit 20b92d2

Browse files
committed
Merge branch 'fix'
* fix: fix - invalid visibility when nesting fix - invalid visibility
2 parents 3eab1e9 + 5e684f8 commit 20b92d2

9 files changed

Lines changed: 178 additions & 61 deletions

fmt.stub.php

Lines changed: 103 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6577,7 +6577,9 @@ public function format($source) {
65776577
$this->tkns = token_get_all($source);
65786578
$this->code = '';
65796579

6580-
$found = [];
6580+
$curlyDepth = 0;
6581+
$classCurlyDepthStack = [];
6582+
$awaitingClassCurly = false;
65816583
$visibility = null;
65826584
$finalOrAbstract = null;
65836585
$static = null;
@@ -6635,7 +6637,7 @@ public function format($source) {
66356637
$this->appendCode($text);
66366638
break;
66376639
}
6638-
$found[] = T_CLASS;
6640+
$awaitingClassCurly = true;
66396641
$touchedClassInterfaceTrait = true;
66406642
if ($static !== null) {
66416643
$this->appendCode($static. ' ');
@@ -6648,59 +6650,82 @@ public function format($source) {
66486650
$skipWhitespaces = false;
66496651
}
66506652
$this->appendCode($text);
6651-
$this->printUntilAny([T_EXTENDS, T_IMPLEMENTS, ST_CURLY_OPEN]);
6653+
while (list($index, $token) = $this->each($this->tkns)) {
6654+
list($id, $text) = $this->getToken($token);
6655+
$this->ptr = $index;
6656+
if (ST_CURLY_OPEN === $id) {
6657+
++$curlyDepth;
6658+
if ($awaitingClassCurly) {
6659+
$classCurlyDepthStack[] = $curlyDepth;
6660+
$awaitingClassCurly = false;
6661+
}
6662+
$this->appendCode($text);
6663+
$touchedClassInterfaceTrait = false;
6664+
break;
6665+
}
6666+
$this->appendCode($text);
6667+
}
66526668
break;
66536669
case T_NAMESPACE:
6654-
$found[] = T_NAMESPACE;
66556670
$touchedClassInterfaceTrait = true;
66566671
$this->appendCode($text);
66576672
break;
66586673
case T_INTERFACE:
6659-
// $found[] = T_INTERFACE;
6674+
$awaitingClassCurly = true;
66606675
$touchedClassInterfaceTrait = true;
66616676
$this->appendCode($text);
66626677
break;
66636678
case T_TRAIT:
6664-
$found[] = T_TRAIT;
6679+
$awaitingClassCurly = true;
66656680
$touchedClassInterfaceTrait = true;
66666681
$this->appendCode($text);
66676682
break;
66686683
case T_ENUM:
6669-
$found[] = T_ENUM;
6684+
$awaitingClassCurly = true;
66706685
$touchedClassInterfaceTrait = true;
66716686
$this->appendCode($text);
66726687
break;
66736688
case ST_CURLY_OPEN:
6674-
case ST_PARENTHESES_OPEN:
6675-
if ($touchedClassInterfaceTrait) {
6676-
$found[] = $text;
6689+
++$curlyDepth;
6690+
if ($awaitingClassCurly) {
6691+
$classCurlyDepthStack[] = $curlyDepth;
6692+
$awaitingClassCurly = false;
66776693
}
66786694
$this->appendCode($text);
66796695
$touchedClassInterfaceTrait = false;
66806696
break;
66816697
case ST_CURLY_CLOSE:
6682-
case ST_PARENTHESES_CLOSE:
6683-
array_pop($found);
6684-
if (1 === sizeof($found)) {
6685-
array_pop($found);
6698+
if (!empty($classCurlyDepthStack) && end($classCurlyDepthStack) === $curlyDepth) {
6699+
array_pop($classCurlyDepthStack);
66866700
}
6687-
if (count($found) === 0) {
6688-
$touchedClassInterfaceTrait = false;
6689-
}
6701+
$curlyDepth = max(0, $curlyDepth - 1);
6702+
if (count($classCurlyDepthStack) === 0) {
6703+
$touchedClassInterfaceTrait = false;
6704+
}
6705+
$this->appendCode($text);
6706+
break;
6707+
case ST_PARENTHESES_OPEN:
6708+
case ST_PARENTHESES_CLOSE:
66906709
$this->appendCode($text);
6710+
$touchedClassInterfaceTrait = false;
66916711
break;
66926712
case T_WHITESPACE:
66936713
if (!$skipWhitespaces) {
66946714
$this->appendCode($text);
66956715
}
66966716
break;
66976717
case '?':
6698-
$tidx = $this->rightUsefulTokenIdx();
6699-
if (in_array($this->tkns[$tidx][0], [T_STRING, T_ARRAY, T_NAME_RELATIVE, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]) && (! isset($this->tkns[$tidx + 1]) || $this->tkns[$tidx + 1][0] !== '(')) {
6700-
$this->tkns[$tidx][1] = $text . $this->tkns[$tidx][1];
6701-
} else {
6702-
$this->appendCode($text);
6703-
}
6718+
// Only merge ? with following type for nullable types (e.g., ?string)
6719+
// Skip this optimization inside function bodies (ternary operator)
6720+
$insideFunctionBody = !empty($classCurlyDepthStack) && $curlyDepth > end($classCurlyDepthStack);
6721+
if (!$insideFunctionBody) {
6722+
$tidx = $this->rightUsefulTokenIdx();
6723+
if (in_array($this->tkns[$tidx][0], [T_STRING, T_ARRAY, T_NAME_RELATIVE, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]) && (! isset($this->tkns[$tidx + 1]) || $this->tkns[$tidx + 1][0] !== '(')) {
6724+
$this->tkns[$tidx][1] = $text . $this->tkns[$tidx][1];
6725+
break;
6726+
}
6727+
}
6728+
$this->appendCode($text);
67046729
break;
67056730
case '|':
67066731
case T_NAME_RELATIVE:
@@ -6819,45 +6844,49 @@ public function format($source) {
68196844

68206845
case T_CONST:
68216846
case T_FUNCTION:
6822-
if (count($found) === 0 || (count($found) === 1 && $found[0] === T_NAMESPACE)) {
6823-
$this->appendCode($text);
6824-
break;
6825-
}
6826-
$hasFoundClassOrInterface = isset($found[0]) && (ST_CURLY_OPEN == $found[0] || T_CLASS === $found[0] || T_INTERFACE === $found[0] || T_TRAIT === $found[0] || T_ENUM === $found[0] || T_NAMESPACE === $found[0]) && ! $this->rightUsefulTokenIs([ST_PARENTHESES_OPEN]);
6847+
if (empty($classCurlyDepthStack)) {
6848+
// Global scope (incl. namespace blocks). Do not inject visibility.
6849+
// Preserve `static function () {}` for closures: if we captured a preceding static, flush it here.
6850+
if ($id === T_FUNCTION && null !== $static && $this->rightUsefulTokenIs([ST_PARENTHESES_OPEN])) {
6851+
$this->appendCode($static . $this->getSpace());
6852+
$static = null;
6853+
$skipWhitespaces = false;
6854+
}
6855+
$this->appendCode($text);
6856+
break;
6857+
}
6858+
$classDepth = end($classCurlyDepthStack);
6859+
$hasFoundClassOrInterface = (
6860+
$curlyDepth === $classDepth &&
6861+
! $this->rightUsefulTokenIs([ST_PARENTHESES_OPEN])
6862+
);
6863+
6864+
// Inside class but not at top-level (e.g. closure inside method): do not inject visibility.
6865+
if (!$hasFoundClassOrInterface) {
6866+
if ($id === T_FUNCTION && null !== $static && $this->rightUsefulTokenIs([ST_PARENTHESES_OPEN])) {
6867+
$this->appendCode($static . $this->getSpace());
6868+
}
6869+
$this->appendCode($text);
6870+
$visibility = null;
6871+
$static = null;
6872+
$readonly = null;
6873+
$skipWhitespaces = false;
6874+
break;
6875+
}
6876+
6877+
if ($id === T_FUNCTION && null === $visibility) {
6878+
$visibility = 'public';
6879+
}
68276880

68286881
if ($hasFoundClassOrInterface && null !== $finalOrAbstract) {
68296882
$this->appendCode($finalOrAbstract . $this->getSpace());
68306883
}
68316884
if ($hasFoundClassOrInterface && null !== $visibility) {
68326885
$this->appendCode($visibility . $this->getSpace());
6833-
} elseif (
6834-
$hasFoundClassOrInterface &&
6835-
!$this->leftTokenIs([T_DOUBLE_ARROW, T_RETURN, ST_EQUAL, ST_COMMA, ST_PARENTHESES_OPEN])
6836-
) {
6837-
if (count($found) - 2 >= 0 && $found[count($found)-2] !== T_NAMESPACE) {
6838-
if ($id === T_FUNCTION) {
6839-
$visibility = 'public';
6840-
$this->appendCode('public' . $this->getSpace());
6841-
}
6842-
}
68436886
}
6844-
if ($visibility === null && $static !== null) {
6845-
if ($id === T_FUNCTION) {
6846-
$visibility = 'public';
6847-
$this->appendCode('public' . $this->getSpace());
6848-
}
6849-
}
68506887
if ($hasFoundClassOrInterface && null !== $static) {
68516888
$this->appendCode($static . $this->getSpace());
68526889
}
6853-
if ($visibility === null && $static === null) {
6854-
if (count($found) === 1 || (count($found) > 1 && $found[count($found) - 2] !== T_NAMESPACE)) {
6855-
if ($id === T_FUNCTION) {
6856-
$visibility = 'public';
6857-
$this->appendCode('public' . $this->getSpace());
6858-
}
6859-
}
6860-
}
68616890
$this->appendCode($text);
68626891
$visibility = null;
68636892
$static = null;
@@ -6869,10 +6898,28 @@ public function format($source) {
68696898
break;
68706899
}
68716900
$finalOrAbstract = null;
6872-
if ($id === T_FUNCTION) {
6873-
$this->printUntil(ST_CURLY_OPEN);
6874-
$this->printCurlyBlock();
6875-
} else {
6901+
if ($id === T_FUNCTION) {
6902+
// Skip to the opening curly or semicolon (for abstract/interface methods)
6903+
// but do NOT consume the curly block - let the main loop handle it
6904+
// so that $curlyDepth stays synchronized for nested anonymous classes
6905+
$parenCount = 0;
6906+
while (list($index, $token) = $this->each($this->tkns)) {
6907+
list($subId, $subText) = $this->getToken($token);
6908+
$this->ptr = $index;
6909+
$this->appendCode($subText);
6910+
if (ST_PARENTHESES_OPEN === $subId) {
6911+
++$parenCount;
6912+
} elseif (ST_PARENTHESES_CLOSE === $subId) {
6913+
$parenCount = max(0, $parenCount - 1);
6914+
} elseif (0 === $parenCount && ST_SEMI_COLON === $subId) {
6915+
break;
6916+
} elseif (0 === $parenCount && ST_CURLY_OPEN === $subId) {
6917+
// Increment curlyDepth and continue - main loop will handle the rest
6918+
++$curlyDepth;
6919+
break;
6920+
}
6921+
}
6922+
} else {
68766923
$this->printUntil(ST_SEMI_COLON);
68776924
}
68786925
break;

tests/Original/463-attribute-parsing-error.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ class User {
1717
}
1818
protected function test4() {
1919
}
20-
function test5() {
20+
public function test5() {
2121
}
2222

23-
function testOut(): ?string {
23+
public function testOut(): ?string {
2424
return $this->testP;
2525
}
2626
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
//passes:PSR2ModifierVisibilityStaticOrder
3+
namespace {
4+
abstract class A {
5+
abstract function m();
6+
}
7+
8+
if (true) {
9+
function cofunc(callable $fn) {
10+
return $fn();
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
//passes:PSR2ModifierVisibilityStaticOrder
3+
namespace {
4+
abstract class A {
5+
abstract public function m();
6+
}
7+
8+
if (true) {
9+
function cofunc(callable $fn) {
10+
return $fn();
11+
}
12+
}
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
//passes:PSR2ModifierVisibilityStaticOrder
3+
class Outer {
4+
function outerMethod() {
5+
return new class {
6+
function innerMethod() {}
7+
private function alreadyPrivate() {}
8+
};
9+
}
10+
}
11+
12+
class DeepNesting {
13+
function level1() {
14+
return new class {
15+
function level2() {
16+
return new class {
17+
function level3() {}
18+
};
19+
}
20+
};
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
//passes:PSR2ModifierVisibilityStaticOrder
3+
class Outer {
4+
public function outerMethod() {
5+
return new class {
6+
public function innerMethod() {}
7+
private function alreadyPrivate() {}
8+
};
9+
}
10+
}
11+
12+
class DeepNesting {
13+
public function level1() {
14+
return new class {
15+
public function level2() {
16+
return new class {
17+
public function level3() {}
18+
};
19+
}
20+
};
21+
}
22+
}

tests/PSR/003-ClassName.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class StdObject
3131

3232
}
3333

34-
function methodName()
34+
public function methodName()
3535
{
3636
new B();
3737
new C();

tests/PSR/004-last-empty-line.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class StdObject
3131

3232
}
3333

34-
function methodName()
34+
public function methodName()
3535
{
3636
new B();
3737
new C();

tests/PSR/005-lower-case-keywords.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class StdObject
3131

3232
}
3333

34-
function methodName()
34+
public function methodName()
3535
{
3636
new B();
3737
new C();

0 commit comments

Comments
 (0)