Skip to content

Commit f073df4

Browse files
committed
Merge branch 'feature/php-8-match-expressions' of https://github.com/jrfnl/PHP_CodeSniffer
2 parents f5645cd + 8353436 commit f073df4

11 files changed

+1954
-0
lines changed

package.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,16 @@ http://pear.php.net/dtd/package-2.0.xsd">
196196
<file baseinstalldir="" name="AnonClassParenthesisOwnerTest.php" role="test" />
197197
<file baseinstalldir="" name="BackfillFnTokenTest.inc" role="test" />
198198
<file baseinstalldir="" name="BackfillFnTokenTest.php" role="test" />
199+
<file baseinstalldir="" name="BackfillMatchTokenTest.inc" role="test" />
200+
<file baseinstalldir="" name="BackfillMatchTokenTest.php" role="test" />
199201
<file baseinstalldir="" name="BackfillNumericSeparatorTest.inc" role="test" />
200202
<file baseinstalldir="" name="BackfillNumericSeparatorTest.php" role="test" />
201203
<file baseinstalldir="" name="BitwiseOrTest.inc" role="test" />
202204
<file baseinstalldir="" name="BitwiseOrTest.php" role="test" />
205+
<file baseinstalldir="" name="DefaultKeywordTest.inc" role="test" />
206+
<file baseinstalldir="" name="DefaultKeywordTest.php" role="test" />
207+
<file baseinstalldir="" name="DoubleArrowTest.inc" role="test" />
208+
<file baseinstalldir="" name="DoubleArrowTest.php" role="test" />
203209
<file baseinstalldir="" name="GotoLabelTest.inc" role="test" />
204210
<file baseinstalldir="" name="GotoLabelTest.php" role="test" />
205211
<file baseinstalldir="" name="NamedFunctionCallArgumentsTest.inc" role="test" />
@@ -2100,10 +2106,16 @@ http://pear.php.net/dtd/package-2.0.xsd">
21002106
<install as="CodeSniffer/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" />
21012107
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.php" name="tests/Core/Tokenizer/BackfillFnTokenTest.php" />
21022108
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
2109+
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.php" name="tests/Core/Tokenizer/BackfillMatchTokenTest.php" />
2110+
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.inc" name="tests/Core/Tokenizer/BackfillMatchTokenTest.inc" />
21032111
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
21042112
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
21052113
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.php" name="tests/Core/Tokenizer/BitwiseOrTest.php" />
21062114
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.inc" name="tests/Core/Tokenizer/BitwiseOrTest.inc" />
2115+
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.php" name="tests/Core/Tokenizer/DefaultKeywordTest.php" />
2116+
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
2117+
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
2118+
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
21072119
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
21082120
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.inc" name="tests/Core/Tokenizer/GotoLabelTest.inc" />
21092121
<install as="CodeSniffer/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" name="tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" />
@@ -2176,10 +2188,16 @@ http://pear.php.net/dtd/package-2.0.xsd">
21762188
<install as="CodeSniffer/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" />
21772189
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.php" name="tests/Core/Tokenizer/BackfillFnTokenTest.php" />
21782190
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
2191+
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.php" name="tests/Core/Tokenizer/BackfillMatchTokenTest.php" />
2192+
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.inc" name="tests/Core/Tokenizer/BackfillMatchTokenTest.inc" />
21792193
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
21802194
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
21812195
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.php" name="tests/Core/Tokenizer/BitwiseOrTest.php" />
21822196
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.inc" name="tests/Core/Tokenizer/BitwiseOrTest.inc" />
2197+
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.php" name="tests/Core/Tokenizer/DefaultKeywordTest.php" />
2198+
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
2199+
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
2200+
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
21832201
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
21842202
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.inc" name="tests/Core/Tokenizer/GotoLabelTest.inc" />
21852203
<install as="CodeSniffer/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" name="tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" />

src/Tokenizers/PHP.php

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,13 @@ class PHP extends Tokenizer
251251
T_SWITCH => T_SWITCH,
252252
],
253253
],
254+
T_MATCH => [
255+
'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
256+
'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
257+
'strict' => true,
258+
'shared' => false,
259+
'with' => [],
260+
],
254261
T_START_HEREDOC => [
255262
'start' => [T_START_HEREDOC => T_START_HEREDOC],
256263
'end' => [T_END_HEREDOC => T_END_HEREDOC],
@@ -365,6 +372,9 @@ class PHP extends Tokenizer
365372
T_LOGICAL_AND => 3,
366373
T_LOGICAL_OR => 2,
367374
T_LOGICAL_XOR => 3,
375+
T_MATCH => 5,
376+
T_MATCH_ARROW => 2,
377+
T_MATCH_DEFAULT => 7,
368378
T_METHOD_C => 10,
369379
T_MINUS_EQUAL => 2,
370380
T_POW_EQUAL => 3,
@@ -1254,6 +1264,138 @@ protected function tokenize($string)
12541264
continue;
12551265
}//end if
12561266

1267+
/*
1268+
Backfill the T_MATCH token for PHP versions < 8.0 and
1269+
do initial correction for non-match expression T_MATCH tokens
1270+
to T_STRING for PHP >= 8.0.
1271+
A final check for non-match expression T_MATCH tokens is done
1272+
in PHP::processAdditional().
1273+
*/
1274+
1275+
if ($tokenIsArray === true
1276+
&& (($token[0] === T_STRING
1277+
&& strtolower($token[1]) === 'match')
1278+
|| $token[0] === T_MATCH)
1279+
) {
1280+
$isMatch = false;
1281+
for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1282+
if (isset($tokens[$x][0], Util\Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
1283+
continue;
1284+
}
1285+
1286+
if ($tokens[$x] !== '(') {
1287+
// This is not a match expression.
1288+
break;
1289+
}
1290+
1291+
// Next was an open parenthesis, now check what is before the match keyword.
1292+
for ($y = ($stackPtr - 1); $y >= 0; $y--) {
1293+
if (isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true) {
1294+
continue;
1295+
}
1296+
1297+
if (is_array($tokens[$y]) === true
1298+
&& ($tokens[$y][0] === T_PAAMAYIM_NEKUDOTAYIM
1299+
|| $tokens[$y][0] === T_OBJECT_OPERATOR
1300+
|| $tokens[$y][0] === T_NS_SEPARATOR
1301+
|| $tokens[$y][0] === T_NEW
1302+
|| $tokens[$y][0] === T_FUNCTION
1303+
|| $tokens[$y][0] === T_CLASS
1304+
|| $tokens[$y][0] === T_INTERFACE
1305+
|| $tokens[$y][0] === T_TRAIT
1306+
|| $tokens[$y][0] === T_NAMESPACE
1307+
|| $tokens[$y][0] === T_CONST)
1308+
) {
1309+
// This is not a match expression.
1310+
break 2;
1311+
}
1312+
1313+
$isMatch = true;
1314+
break 2;
1315+
}//end for
1316+
}//end for
1317+
1318+
if ($isMatch === true && $token[0] === T_STRING) {
1319+
$newToken = [];
1320+
$newToken['code'] = T_MATCH;
1321+
$newToken['type'] = 'T_MATCH';
1322+
$newToken['content'] = $token[1];
1323+
1324+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1325+
echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL;
1326+
}
1327+
1328+
$finalTokens[$newStackPtr] = $newToken;
1329+
$newStackPtr++;
1330+
continue;
1331+
} else if ($isMatch === false && $token[0] === T_MATCH) {
1332+
// PHP 8.0, match keyword, but not a match expression.
1333+
$newToken = [];
1334+
$newToken['code'] = T_STRING;
1335+
$newToken['type'] = 'T_STRING';
1336+
$newToken['content'] = $token[1];
1337+
1338+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1339+
echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL;
1340+
}
1341+
1342+
$finalTokens[$newStackPtr] = $newToken;
1343+
$newStackPtr++;
1344+
continue;
1345+
}//end if
1346+
}//end if
1347+
1348+
/*
1349+
Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
1350+
to prevent scope being set and the scope for switch default statements
1351+
breaking.
1352+
*/
1353+
1354+
if ($tokenIsArray === true
1355+
&& $token[0] === T_DEFAULT
1356+
) {
1357+
for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
1358+
if ($tokens[$x] === ',') {
1359+
// Skip over potential trailing comma (supported in PHP).
1360+
continue;
1361+
}
1362+
1363+
if (is_array($tokens[$x]) === false
1364+
|| isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
1365+
) {
1366+
// Non-empty, non-comma content.
1367+
break;
1368+
}
1369+
}
1370+
1371+
if (isset($tokens[$x]) === true
1372+
&& is_array($tokens[$x]) === true
1373+
&& $tokens[$x][0] === T_DOUBLE_ARROW
1374+
) {
1375+
// Modify the original token stack for the double arrow so that
1376+
// future checks can disregard the double arrow token more easily.
1377+
// For match expression "case" statements, this is handled
1378+
// in PHP::processAdditional().
1379+
$tokens[$x][0] = T_MATCH_ARROW;
1380+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1381+
echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
1382+
}
1383+
1384+
$newToken = [];
1385+
$newToken['code'] = T_MATCH_DEFAULT;
1386+
$newToken['type'] = 'T_MATCH_DEFAULT';
1387+
$newToken['content'] = $token[1];
1388+
1389+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1390+
echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
1391+
}
1392+
1393+
$finalTokens[$newStackPtr] = $newToken;
1394+
$newStackPtr++;
1395+
continue;
1396+
}//end if
1397+
}//end if
1398+
12571399
/*
12581400
Convert ? to T_NULLABLE OR T_INLINE_THEN
12591401
*/
@@ -2110,6 +2252,31 @@ protected function processAdditional()
21102252
$lastEndToken = null;
21112253

21122254
for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
2255+
// Arrow function closer should never be shared with the closer of a match
2256+
// control structure.
2257+
if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
2258+
&& $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
2259+
&& $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
2260+
) {
2261+
if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
2262+
// Match in return value of arrow function. Move on to the next token.
2263+
continue;
2264+
}
2265+
2266+
// Arrow function as return value for the last match case without trailing comma.
2267+
if ($lastEndToken !== null) {
2268+
$scopeCloser = $lastEndToken;
2269+
break;
2270+
}
2271+
2272+
for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
2273+
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
2274+
$scopeCloser = $lastNonEmpty;
2275+
break 2;
2276+
}
2277+
}
2278+
}
2279+
21132280
if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
21142281
if ($lastEndToken !== null
21152282
&& $this->tokens[$scopeCloser]['code'] === T_CLOSE_PARENTHESIS
@@ -2265,6 +2432,77 @@ protected function processAdditional()
22652432
}
22662433
}
22672434

2435+
continue;
2436+
} else if ($this->tokens[$i]['code'] === T_MATCH) {
2437+
if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
2438+
// Not a match expression after all.
2439+
$this->tokens[$i]['code'] = T_STRING;
2440+
$this->tokens[$i]['type'] = 'T_STRING';
2441+
2442+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
2443+
echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL;
2444+
}
2445+
2446+
if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
2447+
$opener = $this->tokens[$i]['parenthesis_opener'];
2448+
$closer = $this->tokens[$i]['parenthesis_closer'];
2449+
unset(
2450+
$this->tokens[$opener]['parenthesis_owner'],
2451+
$this->tokens[$closer]['parenthesis_owner']
2452+
);
2453+
unset(
2454+
$this->tokens[$i]['parenthesis_opener'],
2455+
$this->tokens[$i]['parenthesis_closer'],
2456+
$this->tokens[$i]['parenthesis_owner']
2457+
);
2458+
2459+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
2460+
echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL;
2461+
}
2462+
}
2463+
} else {
2464+
// Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
2465+
$searchFor = [
2466+
T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
2467+
T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
2468+
T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS,
2469+
T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY,
2470+
T_DOUBLE_ARROW => T_DOUBLE_ARROW,
2471+
];
2472+
$searchFor += Util\Tokens::$scopeOpeners;
2473+
2474+
for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
2475+
if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
2476+
continue;
2477+
}
2478+
2479+
if (isset($this->tokens[$x]['scope_closer']) === true) {
2480+
$x = $this->tokens[$x]['scope_closer'];
2481+
continue;
2482+
}
2483+
2484+
if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
2485+
$x = $this->tokens[$x]['parenthesis_closer'];
2486+
continue;
2487+
}
2488+
2489+
if (isset($this->tokens[$x]['bracket_closer']) === true) {
2490+
$x = $this->tokens[$x]['bracket_closer'];
2491+
continue;
2492+
}
2493+
2494+
// This must be a double arrow, but make sure anyhow.
2495+
if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
2496+
$this->tokens[$x]['code'] = T_MATCH_ARROW;
2497+
$this->tokens[$x]['type'] = 'T_MATCH_ARROW';
2498+
2499+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
2500+
echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
2501+
}
2502+
}
2503+
}//end for
2504+
}//end if
2505+
22682506
continue;
22692507
} else if ($this->tokens[$i]['code'] === T_BITWISE_OR) {
22702508
/*

src/Util/Tokens.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
define('T_FN_ARROW', 'PHPCS_T_FN_ARROW');
7878
define('T_TYPE_UNION', 'PHPCS_T_TYPE_UNION');
7979
define('T_PARAM_NAME', 'PHPCS_T_PARAM_NAME');
80+
define('T_MATCH_ARROW', 'PHPCS_T_MATCH_ARROW');
81+
define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT');
8082

8183
// Some PHP 5.5 tokens, replicated for lower versions.
8284
if (defined('T_FINALLY') === false) {
@@ -143,6 +145,10 @@
143145
define('T_NAME_RELATIVE', 'PHPCS_T_NAME_RELATIVE');
144146
}
145147

148+
if (defined('T_MATCH') === false) {
149+
define('T_MATCH', 'PHPCS_T_MATCH');
150+
}
151+
146152
// Tokens used for parsing doc blocks.
147153
define('T_DOC_COMMENT_STAR', 'PHPCS_T_DOC_COMMENT_STAR');
148154
define('T_DOC_COMMENT_WHITESPACE', 'PHPCS_T_DOC_COMMENT_WHITESPACE');
@@ -189,6 +195,7 @@ final class Tokens
189195
T_CATCH => 50,
190196
T_FINALLY => 50,
191197
T_SWITCH => 50,
198+
T_MATCH => 50,
192199

193200
T_SELF => 25,
194201
T_PARENT => 25,
@@ -381,6 +388,7 @@ final class Tokens
381388
T_ELSEIF => T_ELSEIF,
382389
T_CATCH => T_CATCH,
383390
T_DECLARE => T_DECLARE,
391+
T_MATCH => T_MATCH,
384392
];
385393

386394
/**
@@ -413,6 +421,7 @@ final class Tokens
413421
T_PROPERTY => T_PROPERTY,
414422
T_OBJECT => T_OBJECT,
415423
T_USE => T_USE,
424+
T_MATCH => T_MATCH,
416425
];
417426

418427
/**

0 commit comments

Comments
 (0)