Skip to content

Commit

Permalink
Merge branch 'feature/php-8-match-expressions' of https://github.com/…
Browse files Browse the repository at this point in the history
  • Loading branch information
gsherwood committed Feb 23, 2021
2 parents f5645cd + 8353436 commit f073df4
Show file tree
Hide file tree
Showing 11 changed files with 1,954 additions and 0 deletions.
18 changes: 18 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,16 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="AnonClassParenthesisOwnerTest.php" role="test" />
<file baseinstalldir="" name="BackfillFnTokenTest.inc" role="test" />
<file baseinstalldir="" name="BackfillFnTokenTest.php" role="test" />
<file baseinstalldir="" name="BackfillMatchTokenTest.inc" role="test" />
<file baseinstalldir="" name="BackfillMatchTokenTest.php" role="test" />
<file baseinstalldir="" name="BackfillNumericSeparatorTest.inc" role="test" />
<file baseinstalldir="" name="BackfillNumericSeparatorTest.php" role="test" />
<file baseinstalldir="" name="BitwiseOrTest.inc" role="test" />
<file baseinstalldir="" name="BitwiseOrTest.php" role="test" />
<file baseinstalldir="" name="DefaultKeywordTest.inc" role="test" />
<file baseinstalldir="" name="DefaultKeywordTest.php" role="test" />
<file baseinstalldir="" name="DoubleArrowTest.inc" role="test" />
<file baseinstalldir="" name="DoubleArrowTest.php" role="test" />
<file baseinstalldir="" name="GotoLabelTest.inc" role="test" />
<file baseinstalldir="" name="GotoLabelTest.php" role="test" />
<file baseinstalldir="" name="NamedFunctionCallArgumentsTest.inc" role="test" />
Expand Down Expand Up @@ -2100,10 +2106,16 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.php" name="tests/Core/Tokenizer/BackfillFnTokenTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.php" name="tests/Core/Tokenizer/BackfillMatchTokenTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.inc" name="tests/Core/Tokenizer/BackfillMatchTokenTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.php" name="tests/Core/Tokenizer/BitwiseOrTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.inc" name="tests/Core/Tokenizer/BitwiseOrTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.php" name="tests/Core/Tokenizer/DefaultKeywordTest.php" />
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.inc" name="tests/Core/Tokenizer/GotoLabelTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" name="tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" />
Expand Down Expand Up @@ -2176,10 +2188,16 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/AnonClassParenthesisOwnerTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.php" name="tests/Core/Tokenizer/BackfillFnTokenTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.php" name="tests/Core/Tokenizer/BackfillMatchTokenTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillMatchTokenTest.inc" name="tests/Core/Tokenizer/BackfillMatchTokenTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.php" name="tests/Core/Tokenizer/BitwiseOrTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.inc" name="tests/Core/Tokenizer/BitwiseOrTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.php" name="tests/Core/Tokenizer/DefaultKeywordTest.php" />
<install as="CodeSniffer/Core/Tokenizer/DefaultKeywordTest.inc" name="tests/Core/Tokenizer/DefaultKeywordTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.php" name="tests/Core/Tokenizer/DoubleArrowTest.php" />
<install as="CodeSniffer/Core/Tokenizer/DoubleArrowTest.inc" name="tests/Core/Tokenizer/DoubleArrowTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.inc" name="tests/Core/Tokenizer/GotoLabelTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" name="tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.php" />
Expand Down
238 changes: 238 additions & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ class PHP extends Tokenizer
T_SWITCH => T_SWITCH,
],
],
T_MATCH => [
'start' => [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET],
'end' => [T_CLOSE_CURLY_BRACKET => T_CLOSE_CURLY_BRACKET],
'strict' => true,
'shared' => false,
'with' => [],
],
T_START_HEREDOC => [
'start' => [T_START_HEREDOC => T_START_HEREDOC],
'end' => [T_END_HEREDOC => T_END_HEREDOC],
Expand Down Expand Up @@ -365,6 +372,9 @@ class PHP extends Tokenizer
T_LOGICAL_AND => 3,
T_LOGICAL_OR => 2,
T_LOGICAL_XOR => 3,
T_MATCH => 5,
T_MATCH_ARROW => 2,
T_MATCH_DEFAULT => 7,
T_METHOD_C => 10,
T_MINUS_EQUAL => 2,
T_POW_EQUAL => 3,
Expand Down Expand Up @@ -1254,6 +1264,138 @@ protected function tokenize($string)
continue;
}//end if

/*
Backfill the T_MATCH token for PHP versions < 8.0 and
do initial correction for non-match expression T_MATCH tokens
to T_STRING for PHP >= 8.0.
A final check for non-match expression T_MATCH tokens is done
in PHP::processAdditional().
*/

if ($tokenIsArray === true
&& (($token[0] === T_STRING
&& strtolower($token[1]) === 'match')
|| $token[0] === T_MATCH)
) {
$isMatch = false;
for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
if (isset($tokens[$x][0], Util\Tokens::$emptyTokens[$tokens[$x][0]]) === true) {
continue;
}

if ($tokens[$x] !== '(') {
// This is not a match expression.
break;
}

// Next was an open parenthesis, now check what is before the match keyword.
for ($y = ($stackPtr - 1); $y >= 0; $y--) {
if (isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true) {
continue;
}

if (is_array($tokens[$y]) === true
&& ($tokens[$y][0] === T_PAAMAYIM_NEKUDOTAYIM
|| $tokens[$y][0] === T_OBJECT_OPERATOR
|| $tokens[$y][0] === T_NS_SEPARATOR
|| $tokens[$y][0] === T_NEW
|| $tokens[$y][0] === T_FUNCTION
|| $tokens[$y][0] === T_CLASS
|| $tokens[$y][0] === T_INTERFACE
|| $tokens[$y][0] === T_TRAIT
|| $tokens[$y][0] === T_NAMESPACE
|| $tokens[$y][0] === T_CONST)
) {
// This is not a match expression.
break 2;
}

$isMatch = true;
break 2;
}//end for
}//end for

if ($isMatch === true && $token[0] === T_STRING) {
$newToken = [];
$newToken['code'] = T_MATCH;
$newToken['type'] = 'T_MATCH';
$newToken['content'] = $token[1];

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $stackPtr changed from T_STRING to T_MATCH".PHP_EOL;
}

$finalTokens[$newStackPtr] = $newToken;
$newStackPtr++;
continue;
} else if ($isMatch === false && $token[0] === T_MATCH) {
// PHP 8.0, match keyword, but not a match expression.
$newToken = [];
$newToken['code'] = T_STRING;
$newToken['type'] = 'T_STRING';
$newToken['content'] = $token[1];

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $stackPtr changed from T_MATCH to T_STRING".PHP_EOL;
}

$finalTokens[$newStackPtr] = $newToken;
$newStackPtr++;
continue;
}//end if
}//end if

/*
Retokenize the T_DEFAULT in match control structures as T_MATCH_DEFAULT
to prevent scope being set and the scope for switch default statements
breaking.
*/

if ($tokenIsArray === true
&& $token[0] === T_DEFAULT
) {
for ($x = ($stackPtr + 1); $x < $numTokens; $x++) {
if ($tokens[$x] === ',') {
// Skip over potential trailing comma (supported in PHP).
continue;
}

if (is_array($tokens[$x]) === false
|| isset(Util\Tokens::$emptyTokens[$tokens[$x][0]]) === false
) {
// Non-empty, non-comma content.
break;
}
}

if (isset($tokens[$x]) === true
&& is_array($tokens[$x]) === true
&& $tokens[$x][0] === T_DOUBLE_ARROW
) {
// Modify the original token stack for the double arrow so that
// future checks can disregard the double arrow token more easily.
// For match expression "case" statements, this is handled
// in PHP::processAdditional().
$tokens[$x][0] = T_MATCH_ARROW;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
}

$newToken = [];
$newToken['code'] = T_MATCH_DEFAULT;
$newToken['type'] = 'T_MATCH_DEFAULT';
$newToken['content'] = $token[1];

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $stackPtr changed from T_DEFAULT to T_MATCH_DEFAULT".PHP_EOL;
}

$finalTokens[$newStackPtr] = $newToken;
$newStackPtr++;
continue;
}//end if
}//end if

/*
Convert ? to T_NULLABLE OR T_INLINE_THEN
*/
Expand Down Expand Up @@ -2110,6 +2252,31 @@ protected function processAdditional()
$lastEndToken = null;

for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
// Arrow function closer should never be shared with the closer of a match
// control structure.
if (isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true
&& $scopeCloser === $this->tokens[$scopeCloser]['scope_closer']
&& $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_MATCH
) {
if ($arrow < $this->tokens[$scopeCloser]['scope_condition']) {
// Match in return value of arrow function. Move on to the next token.
continue;
}

// Arrow function as return value for the last match case without trailing comma.
if ($lastEndToken !== null) {
$scopeCloser = $lastEndToken;
break;
}

for ($lastNonEmpty = ($scopeCloser - 1); $lastNonEmpty > $arrow; $lastNonEmpty--) {
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$lastNonEmpty]['code']]) === false) {
$scopeCloser = $lastNonEmpty;
break 2;
}
}
}

if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
if ($lastEndToken !== null
&& $this->tokens[$scopeCloser]['code'] === T_CLOSE_PARENTHESIS
Expand Down Expand Up @@ -2265,6 +2432,77 @@ protected function processAdditional()
}
}

continue;
} else if ($this->tokens[$i]['code'] === T_MATCH) {
if (isset($this->tokens[$i]['scope_opener'], $this->tokens[$i]['scope_closer']) === false) {
// Not a match expression after all.
$this->tokens[$i]['code'] = T_STRING;
$this->tokens[$i]['type'] = 'T_STRING';

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $i changed from T_MATCH to T_STRING".PHP_EOL;
}

if (isset($this->tokens[$i]['parenthesis_opener'], $this->tokens[$i]['parenthesis_closer']) === true) {
$opener = $this->tokens[$i]['parenthesis_opener'];
$closer = $this->tokens[$i]['parenthesis_closer'];
unset(
$this->tokens[$opener]['parenthesis_owner'],
$this->tokens[$closer]['parenthesis_owner']
);
unset(
$this->tokens[$i]['parenthesis_opener'],
$this->tokens[$i]['parenthesis_closer'],
$this->tokens[$i]['parenthesis_owner']
);

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* cleaned parenthesis of token $i *".PHP_EOL;
}
}
} else {
// Retokenize the double arrows for match expression cases to `T_MATCH_ARROW`.
$searchFor = [
T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS,
T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY,
T_DOUBLE_ARROW => T_DOUBLE_ARROW,
];
$searchFor += Util\Tokens::$scopeOpeners;

for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
if (isset($searchFor[$this->tokens[$x]['code']]) === false) {
continue;
}

if (isset($this->tokens[$x]['scope_closer']) === true) {
$x = $this->tokens[$x]['scope_closer'];
continue;
}

if (isset($this->tokens[$x]['parenthesis_closer']) === true) {
$x = $this->tokens[$x]['parenthesis_closer'];
continue;
}

if (isset($this->tokens[$x]['bracket_closer']) === true) {
$x = $this->tokens[$x]['bracket_closer'];
continue;
}

// This must be a double arrow, but make sure anyhow.
if ($this->tokens[$x]['code'] === T_DOUBLE_ARROW) {
$this->tokens[$x]['code'] = T_MATCH_ARROW;
$this->tokens[$x]['type'] = 'T_MATCH_ARROW';

if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t\t* token $x changed from T_DOUBLE_ARROW to T_MATCH_ARROW".PHP_EOL;
}
}
}//end for
}//end if

continue;
} else if ($this->tokens[$i]['code'] === T_BITWISE_OR) {
/*
Expand Down
9 changes: 9 additions & 0 deletions src/Util/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
define('T_FN_ARROW', 'PHPCS_T_FN_ARROW');
define('T_TYPE_UNION', 'PHPCS_T_TYPE_UNION');
define('T_PARAM_NAME', 'PHPCS_T_PARAM_NAME');
define('T_MATCH_ARROW', 'PHPCS_T_MATCH_ARROW');
define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT');

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

if (defined('T_MATCH') === false) {
define('T_MATCH', 'PHPCS_T_MATCH');
}

// Tokens used for parsing doc blocks.
define('T_DOC_COMMENT_STAR', 'PHPCS_T_DOC_COMMENT_STAR');
define('T_DOC_COMMENT_WHITESPACE', 'PHPCS_T_DOC_COMMENT_WHITESPACE');
Expand Down Expand Up @@ -189,6 +195,7 @@ final class Tokens
T_CATCH => 50,
T_FINALLY => 50,
T_SWITCH => 50,
T_MATCH => 50,

T_SELF => 25,
T_PARENT => 25,
Expand Down Expand Up @@ -381,6 +388,7 @@ final class Tokens
T_ELSEIF => T_ELSEIF,
T_CATCH => T_CATCH,
T_DECLARE => T_DECLARE,
T_MATCH => T_MATCH,
];

/**
Expand Down Expand Up @@ -413,6 +421,7 @@ final class Tokens
T_PROPERTY => T_PROPERTY,
T_OBJECT => T_OBJECT,
T_USE => T_USE,
T_MATCH => T_MATCH,
];

/**
Expand Down
Loading

0 comments on commit f073df4

Please sign in to comment.