Skip to content

Commit 027c0cb

Browse files
authored
Merge pull request #502 from PHPCSStandards/feature/110-437-generic-scopeindent-fix-undefined-array-index-notice
File::findStartOfStatement(): 3 bug fixes related to `match` expressions
2 parents 808dff8 + 28c376e commit 027c0cb

8 files changed

+553
-44
lines changed

src/Files/File.php

+77-40
Original file line numberDiff line numberDiff line change
@@ -2435,51 +2435,88 @@ public function findStartOfStatement($start, $ignore=null)
24352435
// If the start token is inside the case part of a match expression,
24362436
// find the start of the condition. If it's in the statement part, find
24372437
// the token that comes after the match arrow.
2438-
$matchExpression = $this->getCondition($start, T_MATCH);
2439-
if ($matchExpression !== false) {
2440-
for ($prevMatch = $start; $prevMatch > $this->tokens[$matchExpression]['scope_opener']; $prevMatch--) {
2441-
if ($prevMatch !== $start
2442-
&& ($this->tokens[$prevMatch]['code'] === T_MATCH_ARROW
2443-
|| $this->tokens[$prevMatch]['code'] === T_COMMA)
2444-
) {
2445-
break;
2446-
}
2438+
if (empty($this->tokens[$start]['conditions']) === false) {
2439+
$conditions = $this->tokens[$start]['conditions'];
2440+
$lastConditionOwner = end($conditions);
2441+
$matchExpression = key($conditions);
2442+
2443+
if ($lastConditionOwner === T_MATCH
2444+
// Check if the $start token is at the same parentheses nesting level as the match token.
2445+
&& ((empty($this->tokens[$matchExpression]['nested_parenthesis']) === true
2446+
&& empty($this->tokens[$start]['nested_parenthesis']) === true)
2447+
|| ((empty($this->tokens[$matchExpression]['nested_parenthesis']) === false
2448+
&& empty($this->tokens[$start]['nested_parenthesis']) === false)
2449+
&& $this->tokens[$matchExpression]['nested_parenthesis'] === $this->tokens[$start]['nested_parenthesis']))
2450+
) {
2451+
// Walk back to the previous match arrow (if it exists).
2452+
$lastComma = null;
2453+
$inNestedExpression = false;
2454+
for ($prevMatch = $start; $prevMatch > $this->tokens[$matchExpression]['scope_opener']; $prevMatch--) {
2455+
if ($prevMatch !== $start && $this->tokens[$prevMatch]['code'] === T_MATCH_ARROW) {
2456+
break;
2457+
}
24472458

2448-
// Skip nested statements.
2449-
if (isset($this->tokens[$prevMatch]['bracket_opener']) === true
2450-
&& $prevMatch === $this->tokens[$prevMatch]['bracket_closer']
2451-
) {
2452-
$prevMatch = $this->tokens[$prevMatch]['bracket_opener'];
2453-
} else if (isset($this->tokens[$prevMatch]['parenthesis_opener']) === true
2454-
&& $prevMatch === $this->tokens[$prevMatch]['parenthesis_closer']
2455-
) {
2456-
$prevMatch = $this->tokens[$prevMatch]['parenthesis_opener'];
2457-
}
2458-
}
2459+
if ($prevMatch !== $start && $this->tokens[$prevMatch]['code'] === T_COMMA) {
2460+
$lastComma = $prevMatch;
2461+
continue;
2462+
}
24592463

2460-
if ($prevMatch <= $this->tokens[$matchExpression]['scope_opener']) {
2461-
// We're before the arrow in the first case.
2462-
$next = $this->findNext(Tokens::$emptyTokens, ($this->tokens[$matchExpression]['scope_opener'] + 1), null, true);
2463-
if ($next === false) {
2464-
return $start;
2465-
}
2464+
// Skip nested statements.
2465+
if (isset($this->tokens[$prevMatch]['bracket_opener']) === true
2466+
&& $prevMatch === $this->tokens[$prevMatch]['bracket_closer']
2467+
) {
2468+
$prevMatch = $this->tokens[$prevMatch]['bracket_opener'];
2469+
continue;
2470+
}
24662471

2467-
return $next;
2468-
}
2472+
if (isset($this->tokens[$prevMatch]['parenthesis_opener']) === true
2473+
&& $prevMatch === $this->tokens[$prevMatch]['parenthesis_closer']
2474+
) {
2475+
$prevMatch = $this->tokens[$prevMatch]['parenthesis_opener'];
2476+
continue;
2477+
}
24692478

2470-
if ($this->tokens[$prevMatch]['code'] === T_COMMA) {
2471-
// We're before the arrow, but not in the first case.
2472-
$prevMatchArrow = $this->findPrevious(T_MATCH_ARROW, ($prevMatch - 1), $this->tokens[$matchExpression]['scope_opener']);
2473-
if ($prevMatchArrow === false) {
2474-
// We're before the arrow in the first case.
2475-
$next = $this->findNext(Tokens::$emptyTokens, ($this->tokens[$matchExpression]['scope_opener'] + 1), null, true);
2476-
return $next;
2477-
}
2479+
// Stop if we're _within_ a nested short array statement, which may contain comma's too.
2480+
// No need to deal with parentheses, those are handled above via the `nested_parenthesis` checks.
2481+
if (isset($this->tokens[$prevMatch]['bracket_opener']) === true
2482+
&& $this->tokens[$prevMatch]['bracket_closer'] > $start
2483+
) {
2484+
$inNestedExpression = true;
2485+
break;
2486+
}
2487+
}//end for
2488+
2489+
if ($inNestedExpression === false) {
2490+
// $prevMatch will now either be the scope opener or a match arrow.
2491+
// If it is the scope opener, go the first non-empty token after. $start will have been part of the first condition.
2492+
if ($prevMatch <= $this->tokens[$matchExpression]['scope_opener']) {
2493+
// We're before the arrow in the first case.
2494+
$next = $this->findNext(Tokens::$emptyTokens, ($this->tokens[$matchExpression]['scope_opener'] + 1), null, true);
2495+
if ($next === false) {
2496+
// Shouldn't be possible.
2497+
return $start;
2498+
}
24782499

2479-
$end = $this->findEndOfStatement($prevMatchArrow);
2480-
$next = $this->findNext(Tokens::$emptyTokens, ($end + 1), null, true);
2481-
return $next;
2482-
}
2500+
return $next;
2501+
}
2502+
2503+
// Okay, so we found a match arrow.
2504+
// If $start was part of the "next" condition, the last comma will be set.
2505+
// Otherwise, $start must have been part of a return expression.
2506+
if (isset($lastComma) === true && $lastComma > $prevMatch) {
2507+
$prevMatch = $lastComma;
2508+
}
2509+
2510+
// In both cases, go to the first non-empty token after.
2511+
$next = $this->findNext(Tokens::$emptyTokens, ($prevMatch + 1), null, true);
2512+
if ($next === false) {
2513+
// Shouldn't be possible.
2514+
return $start;
2515+
}
2516+
2517+
return $next;
2518+
}//end if
2519+
}//end if
24832520
}//end if
24842521

24852522
$lastNotEmpty = $start;

src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc

+35
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,41 @@ foo(function ($foo) {
15791579
];
15801580
});
15811581

1582+
// Issue #110.
1583+
echo match (1) {
1584+
0 => match (2) {
1585+
2 => match (3) {
1586+
3 => 3,
1587+
default => -1,
1588+
},
1589+
},
1590+
1 => match (2) {
1591+
1 => match (3) {
1592+
3 => 3,
1593+
default => -1,
1594+
},
1595+
2 => match (3) {
1596+
3 => 3,
1597+
default => -1,
1598+
},
1599+
},
1600+
};
1601+
1602+
// Issue #437.
1603+
match (true) {
1604+
default => [
1605+
'unrelated' => '',
1606+
'example' => array_filter(
1607+
array_map(
1608+
function () {
1609+
return null;
1610+
},
1611+
[]
1612+
)
1613+
)
1614+
]
1615+
};
1616+
15821617
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
15831618
?>
15841619

src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.1.inc.fixed

+35
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,41 @@ foo(function ($foo) {
15791579
];
15801580
});
15811581

1582+
// Issue #110.
1583+
echo match (1) {
1584+
0 => match (2) {
1585+
2 => match (3) {
1586+
3 => 3,
1587+
default => -1,
1588+
},
1589+
},
1590+
1 => match (2) {
1591+
1 => match (3) {
1592+
3 => 3,
1593+
default => -1,
1594+
},
1595+
2 => match (3) {
1596+
3 => 3,
1597+
default => -1,
1598+
},
1599+
},
1600+
};
1601+
1602+
// Issue #437.
1603+
match (true) {
1604+
default => [
1605+
'unrelated' => '',
1606+
'example' => array_filter(
1607+
array_map(
1608+
function () {
1609+
return null;
1610+
},
1611+
[]
1612+
)
1613+
)
1614+
]
1615+
};
1616+
15821617
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
15831618
?>
15841619

src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc

+35
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,41 @@ foo(function ($foo) {
15791579
];
15801580
});
15811581

1582+
// Issue #110.
1583+
echo match (1) {
1584+
0 => match (2) {
1585+
2 => match (3) {
1586+
3 => 3,
1587+
default => -1,
1588+
},
1589+
},
1590+
1 => match (2) {
1591+
1 => match (3) {
1592+
3 => 3,
1593+
default => -1,
1594+
},
1595+
2 => match (3) {
1596+
3 => 3,
1597+
default => -1,
1598+
},
1599+
},
1600+
};
1601+
1602+
// Issue #437.
1603+
match (true) {
1604+
default => [
1605+
'unrelated' => '',
1606+
'example' => array_filter(
1607+
array_map(
1608+
function () {
1609+
return null;
1610+
},
1611+
[]
1612+
)
1613+
)
1614+
]
1615+
};
1616+
15821617
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
15831618
?>
15841619

src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.2.inc.fixed

+35
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,41 @@ foo(function ($foo) {
15791579
];
15801580
});
15811581

1582+
// Issue #110.
1583+
echo match (1) {
1584+
0 => match (2) {
1585+
2 => match (3) {
1586+
3 => 3,
1587+
default => -1,
1588+
},
1589+
},
1590+
1 => match (2) {
1591+
1 => match (3) {
1592+
3 => 3,
1593+
default => -1,
1594+
},
1595+
2 => match (3) {
1596+
3 => 3,
1597+
default => -1,
1598+
},
1599+
},
1600+
};
1601+
1602+
// Issue #437.
1603+
match (true) {
1604+
default => [
1605+
'unrelated' => '',
1606+
'example' => array_filter(
1607+
array_map(
1608+
function () {
1609+
return null;
1610+
},
1611+
[]
1612+
)
1613+
)
1614+
]
1615+
};
1616+
15821617
/* ADD NEW TESTS ABOVE THIS LINE AND MAKE SURE THAT THE 1 (space-based) AND 2 (tab-based) FILES ARE IN SYNC! */
15831618
?>
15841619

src/Standards/Generic/Tests/WhiteSpace/ScopeIndentUnitTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,10 @@ public function getErrorList($testFile='')
192192
1527 => 1,
193193
1529 => 1,
194194
1530 => 1,
195-
1590 => 1,
196-
1591 => 1,
197-
1592 => 1,
198-
1593 => 1,
195+
1625 => 1,
196+
1626 => 1,
197+
1627 => 1,
198+
1628 => 1,
199199
];
200200

201201
}//end getErrorList()

tests/Core/File/FindStartOfStatementTest.inc

+36
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,39 @@ switch ($foo) {
162162
/* testInsideDefaultContinueStatement */
163163
continue $var;
164164
}
165+
166+
match ($var) {
167+
true =>
168+
/* test437ClosureDeclaration */
169+
function ($var) {
170+
/* test437EchoNestedWithinClosureWithinMatch */
171+
echo $var, 'text', PHP_EOL;
172+
},
173+
default => false
174+
};
175+
176+
match ($var) {
177+
/* test437NestedLongArrayWithinMatch */
178+
'a' => array( 1, 2.5, $var),
179+
/* test437NestedFunctionCallWithinMatch */
180+
'b' => functionCall( 11, $var, 50.50),
181+
/* test437NestedArrowFunctionWithinMatch */
182+
'c' => fn($p1, /* test437FnSecondParamWithinMatch */ $p2) => $p1 + $p2,
183+
default => false
184+
};
185+
186+
callMe($paramA, match ($var) {
187+
/* test437NestedLongArrayWithinNestedMatch */
188+
'a' => array( 1, 2.5, $var),
189+
/* test437NestedFunctionCallWithinNestedMatch */
190+
'b' => functionCall( 11, $var, 50.50),
191+
/* test437NestedArrowFunctionWithinNestedMatch */
192+
'c' => fn($p1, /* test437FnSecondParamWithinNestedMatch */ $p2) => $p1 + $p2,
193+
default => false
194+
});
195+
196+
match ($var) {
197+
/* test437NestedShortArrayWithinMatch */
198+
'a' => [ 1, 2.5, $var],
199+
default => false
200+
};

0 commit comments

Comments
 (0)