530 lines
21 KiB
PHP
530 lines
21 KiB
PHP
<?php
|
|
/**
|
|
* Tests the backfilling of the T_MATCH token to PHP < 8.0, as well as the
|
|
* setting of parenthesis/scopes for match control structures across PHP versions.
|
|
*
|
|
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
|
|
* @copyright 2020-2021 Squiz Pty Ltd (ABN 77 084 670 600)
|
|
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
|
*/
|
|
|
|
namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
|
|
|
|
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
|
|
use PHP_CodeSniffer\Util\Tokens;
|
|
|
|
class BackfillMatchTokenTest extends AbstractMethodUnitTest
|
|
{
|
|
|
|
|
|
/**
|
|
* Test tokenization of match expressions.
|
|
*
|
|
* @param string $testMarker The comment prefacing the target token.
|
|
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
|
|
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
|
|
* @param string $testContent The token content to look for.
|
|
*
|
|
* @dataProvider dataMatchExpression
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testMatchExpression($testMarker, $openerOffset, $closerOffset, $testContent='match')
|
|
{
|
|
$tokens = self::$phpcsFile->getTokens();
|
|
|
|
$token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent);
|
|
$tokenArray = $tokens[$token];
|
|
|
|
$this->assertSame(T_MATCH, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH (code)');
|
|
$this->assertSame('T_MATCH', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH (type)');
|
|
|
|
$this->scopeTestHelper($token, $openerOffset, $closerOffset);
|
|
$this->parenthesisTestHelper($token);
|
|
|
|
}//end testMatchExpression()
|
|
|
|
|
|
/**
|
|
* Data provider.
|
|
*
|
|
* @see testMatchExpression()
|
|
*
|
|
* @return array
|
|
*/
|
|
public function dataMatchExpression()
|
|
{
|
|
return [
|
|
'simple_match' => [
|
|
'/* testMatchSimple */',
|
|
6,
|
|
33,
|
|
],
|
|
'no_trailing_comma' => [
|
|
'/* testMatchNoTrailingComma */',
|
|
6,
|
|
24,
|
|
],
|
|
'with_default_case' => [
|
|
'/* testMatchWithDefault */',
|
|
6,
|
|
33,
|
|
],
|
|
'expression_in_condition' => [
|
|
'/* testMatchExpressionInCondition */',
|
|
6,
|
|
77,
|
|
],
|
|
'multicase' => [
|
|
'/* testMatchMultiCase */',
|
|
6,
|
|
40,
|
|
],
|
|
'multicase_trailing_comma_in_case' => [
|
|
'/* testMatchMultiCaseTrailingCommaInCase */',
|
|
6,
|
|
47,
|
|
],
|
|
'in_closure_not_lowercase' => [
|
|
'/* testMatchInClosureNotLowercase */',
|
|
6,
|
|
36,
|
|
'Match',
|
|
],
|
|
'in_arrow_function' => [
|
|
'/* testMatchInArrowFunction */',
|
|
5,
|
|
36,
|
|
],
|
|
'arrow_function_in_match_no_trailing_comma' => [
|
|
'/* testArrowFunctionInMatchNoTrailingComma */',
|
|
6,
|
|
44,
|
|
],
|
|
'in_function_call_param_not_lowercase' => [
|
|
'/* testMatchInFunctionCallParamNotLowercase */',
|
|
8,
|
|
32,
|
|
'MATCH',
|
|
],
|
|
'in_method_call_param' => [
|
|
'/* testMatchInMethodCallParam */',
|
|
5,
|
|
13,
|
|
],
|
|
'discard_result' => [
|
|
'/* testMatchDiscardResult */',
|
|
6,
|
|
18,
|
|
],
|
|
'duplicate_conditions_and_comments' => [
|
|
'/* testMatchWithDuplicateConditionsWithComments */',
|
|
12,
|
|
59,
|
|
],
|
|
'nested_match_outer' => [
|
|
'/* testNestedMatchOuter */',
|
|
6,
|
|
33,
|
|
],
|
|
'nested_match_inner' => [
|
|
'/* testNestedMatchInner */',
|
|
6,
|
|
14,
|
|
],
|
|
'ternary_condition' => [
|
|
'/* testMatchInTernaryCondition */',
|
|
6,
|
|
21,
|
|
],
|
|
'ternary_then' => [
|
|
'/* testMatchInTernaryThen */',
|
|
6,
|
|
21,
|
|
],
|
|
'ternary_else' => [
|
|
'/* testMatchInTernaryElse */',
|
|
6,
|
|
21,
|
|
],
|
|
'array_value' => [
|
|
'/* testMatchInArrayValue */',
|
|
6,
|
|
21,
|
|
],
|
|
'array_key' => [
|
|
'/* testMatchInArrayKey */',
|
|
6,
|
|
21,
|
|
],
|
|
'returning_array' => [
|
|
'/* testMatchreturningArray */',
|
|
6,
|
|
125,
|
|
],
|
|
'nested_in_switch_case_1' => [
|
|
'/* testMatchWithDefaultNestedInSwitchCase1 */',
|
|
6,
|
|
25,
|
|
],
|
|
'nested_in_switch_case_2' => [
|
|
'/* testMatchWithDefaultNestedInSwitchCase2 */',
|
|
6,
|
|
25,
|
|
],
|
|
'nested_in_switch_default' => [
|
|
'/* testMatchWithDefaultNestedInSwitchDefault */',
|
|
6,
|
|
25,
|
|
],
|
|
'match_with_nested_switch' => [
|
|
'/* testMatchContainingSwitch */',
|
|
6,
|
|
180,
|
|
],
|
|
'no_cases' => [
|
|
'/* testMatchNoCases */',
|
|
6,
|
|
7,
|
|
],
|
|
'multi_default' => [
|
|
'/* testMatchMultiDefault */',
|
|
6,
|
|
40,
|
|
],
|
|
];
|
|
|
|
}//end dataMatchExpression()
|
|
|
|
|
|
/**
|
|
* Verify that "match" keywords which are not match control structures get tokenized as T_STRING
|
|
* and don't have the extra token array indexes.
|
|
*
|
|
* @param string $testMarker The comment prefacing the target token.
|
|
* @param string $testContent The token content to look for.
|
|
*
|
|
* @dataProvider dataNotAMatchStructure
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testNotAMatchStructure($testMarker, $testContent='match')
|
|
{
|
|
$tokens = self::$phpcsFile->getTokens();
|
|
|
|
$token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent);
|
|
$tokenArray = $tokens[$token];
|
|
|
|
$this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)');
|
|
$this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)');
|
|
|
|
$this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set');
|
|
$this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set');
|
|
$this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set');
|
|
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set');
|
|
$this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set');
|
|
$this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set');
|
|
|
|
$next = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($token + 1), null, true);
|
|
if ($next !== false && $tokens[$next]['code'] === T_OPEN_PARENTHESIS) {
|
|
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set for opener after');
|
|
}
|
|
|
|
}//end testNotAMatchStructure()
|
|
|
|
|
|
/**
|
|
* Data provider.
|
|
*
|
|
* @see testNotAMatchStructure()
|
|
*
|
|
* @return array
|
|
*/
|
|
public function dataNotAMatchStructure()
|
|
{
|
|
return [
|
|
'static_method_call' => ['/* testNoMatchStaticMethodCall */'],
|
|
'class_constant_access' => [
|
|
'/* testNoMatchClassConstantAccess */',
|
|
'MATCH',
|
|
],
|
|
'class_constant_array_access' => [
|
|
'/* testNoMatchClassConstantArrayAccessMixedCase */',
|
|
'Match',
|
|
],
|
|
'method_call' => ['/* testNoMatchMethodCall */'],
|
|
'method_call_uppercase' => [
|
|
'/* testNoMatchMethodCallUpper */',
|
|
'MATCH',
|
|
],
|
|
'property_access' => ['/* testNoMatchPropertyAccess */'],
|
|
'namespaced_function_call' => ['/* testNoMatchNamespacedFunctionCall */'],
|
|
'namespace_operator_function_call' => ['/* testNoMatchNamespaceOperatorFunctionCall */'],
|
|
'interface_method_declaration' => ['/* testNoMatchInterfaceMethodDeclaration */'],
|
|
'class_constant_declaration' => ['/* testNoMatchClassConstantDeclarationLower */'],
|
|
'class_method_declaration' => ['/* testNoMatchClassMethodDeclaration */'],
|
|
'property_assigment' => ['/* testNoMatchPropertyAssignment */'],
|
|
'class_instantiation' => [
|
|
'/* testNoMatchClassInstantiation */',
|
|
'Match',
|
|
],
|
|
'anon_class_method_declaration' => [
|
|
'/* testNoMatchAnonClassMethodDeclaration */',
|
|
'maTCH',
|
|
],
|
|
'class_declaration' => [
|
|
'/* testNoMatchClassDeclaration */',
|
|
'Match',
|
|
],
|
|
'interface_declaration' => [
|
|
'/* testNoMatchInterfaceDeclaration */',
|
|
'Match',
|
|
],
|
|
'trait_declaration' => [
|
|
'/* testNoMatchTraitDeclaration */',
|
|
'Match',
|
|
],
|
|
'constant_declaration' => [
|
|
'/* testNoMatchConstantDeclaration */',
|
|
'MATCH',
|
|
],
|
|
'function_declaration' => ['/* testNoMatchFunctionDeclaration */'],
|
|
'namespace_declaration' => [
|
|
'/* testNoMatchNamespaceDeclaration */',
|
|
'Match',
|
|
],
|
|
'class_extends_declaration' => [
|
|
'/* testNoMatchExtendedClassDeclaration */',
|
|
'Match',
|
|
],
|
|
'class_implements_declaration' => [
|
|
'/* testNoMatchImplementedClassDeclaration */',
|
|
'Match',
|
|
],
|
|
'use_statement' => [
|
|
'/* testNoMatchInUseStatement */',
|
|
'Match',
|
|
],
|
|
'unsupported_inline_control_structure' => ['/* testNoMatchMissingCurlies */'],
|
|
'unsupported_alternative_syntax' => ['/* testNoMatchAlternativeSyntax */'],
|
|
'live_coding' => ['/* testLiveCoding */'],
|
|
];
|
|
|
|
}//end dataNotAMatchStructure()
|
|
|
|
|
|
/**
|
|
* Verify that the tokenization of switch structures is not affected by the backfill.
|
|
*
|
|
* @param string $testMarker The comment prefacing the target token.
|
|
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
|
|
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
|
|
*
|
|
* @dataProvider dataSwitchExpression
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testSwitchExpression($testMarker, $openerOffset, $closerOffset)
|
|
{
|
|
$token = $this->getTargetToken($testMarker, T_SWITCH);
|
|
|
|
$this->scopeTestHelper($token, $openerOffset, $closerOffset);
|
|
$this->parenthesisTestHelper($token);
|
|
|
|
}//end testSwitchExpression()
|
|
|
|
|
|
/**
|
|
* Data provider.
|
|
*
|
|
* @see testSwitchExpression()
|
|
*
|
|
* @return array
|
|
*/
|
|
public function dataSwitchExpression()
|
|
{
|
|
return [
|
|
'switch_containing_match' => [
|
|
'/* testSwitchContainingMatch */',
|
|
6,
|
|
174,
|
|
],
|
|
'match_containing_switch_1' => [
|
|
'/* testSwitchNestedInMatch1 */',
|
|
5,
|
|
63,
|
|
],
|
|
'match_containing_switch_2' => [
|
|
'/* testSwitchNestedInMatch2 */',
|
|
5,
|
|
63,
|
|
],
|
|
];
|
|
|
|
}//end dataSwitchExpression()
|
|
|
|
|
|
/**
|
|
* Verify that the tokenization of a switch case/default structure containing a match structure
|
|
* or contained *in* a match structure is not affected by the backfill.
|
|
*
|
|
* @param string $testMarker The comment prefacing the target token.
|
|
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
|
|
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
|
|
*
|
|
* @dataProvider dataSwitchCaseVersusMatch
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
|
|
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
|
|
*
|
|
* @return void
|
|
*/
|
|
public function testSwitchCaseVersusMatch($testMarker, $openerOffset, $closerOffset)
|
|
{
|
|
$token = $this->getTargetToken($testMarker, [T_CASE, T_DEFAULT]);
|
|
|
|
$this->scopeTestHelper($token, $openerOffset, $closerOffset);
|
|
|
|
}//end testSwitchCaseVersusMatch()
|
|
|
|
|
|
/**
|
|
* Data provider.
|
|
*
|
|
* @see testSwitchCaseVersusMatch()
|
|
*
|
|
* @return array
|
|
*/
|
|
public function dataSwitchCaseVersusMatch()
|
|
{
|
|
return [
|
|
'switch_with_nested_match_case_1' => [
|
|
'/* testMatchWithDefaultNestedInSwitchCase1 */',
|
|
3,
|
|
55,
|
|
],
|
|
'switch_with_nested_match_case_2' => [
|
|
'/* testMatchWithDefaultNestedInSwitchCase2 */',
|
|
4,
|
|
21,
|
|
],
|
|
'switch_with_nested_match_default_case' => [
|
|
'/* testMatchWithDefaultNestedInSwitchDefault */',
|
|
1,
|
|
38,
|
|
],
|
|
'match_with_nested_switch_case' => [
|
|
'/* testSwitchDefaultNestedInMatchCase */',
|
|
1,
|
|
18,
|
|
],
|
|
'match_with_nested_switch_default_case' => [
|
|
'/* testSwitchDefaultNestedInMatchDefault */',
|
|
1,
|
|
20,
|
|
],
|
|
];
|
|
|
|
}//end dataSwitchCaseVersusMatch()
|
|
|
|
|
|
/**
|
|
* Helper function to verify that all scope related array indexes for a control structure
|
|
* are set correctly.
|
|
*
|
|
* @param string $token The control structure token to check.
|
|
* @param int $openerOffset The expected offset of the scope opener in relation to
|
|
* the control structure token.
|
|
* @param int $closerOffset The expected offset of the scope closer in relation to
|
|
* the control structure token.
|
|
* @param bool $skipScopeCloserCheck Whether to skip the scope closer check.
|
|
* This should be set to "true" when testing nested arrow functions,
|
|
* where the "inner" arrow function shares a scope closer with the
|
|
* "outer" arrow function, as the 'scope_condition' for the scope closer
|
|
* of the "inner" arrow function will point to the "outer" arrow function.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function scopeTestHelper($token, $openerOffset, $closerOffset, $skipScopeCloserCheck=false)
|
|
{
|
|
$tokens = self::$phpcsFile->getTokens();
|
|
$tokenArray = $tokens[$token];
|
|
$tokenType = $tokenArray['type'];
|
|
$expectedScopeOpener = ($token + $openerOffset);
|
|
$expectedScopeCloser = ($token + $closerOffset);
|
|
|
|
$this->assertArrayHasKey('scope_condition', $tokenArray, 'Scope condition is not set');
|
|
$this->assertArrayHasKey('scope_opener', $tokenArray, 'Scope opener is not set');
|
|
$this->assertArrayHasKey('scope_closer', $tokenArray, 'Scope closer is not set');
|
|
$this->assertSame($token, $tokenArray['scope_condition'], 'Scope condition is not the '.$tokenType.' token');
|
|
$this->assertSame($expectedScopeOpener, $tokenArray['scope_opener'], 'Scope opener of the '.$tokenType.' token incorrect');
|
|
$this->assertSame($expectedScopeCloser, $tokenArray['scope_closer'], 'Scope closer of the '.$tokenType.' token incorrect');
|
|
|
|
$opener = $tokenArray['scope_opener'];
|
|
$this->assertArrayHasKey('scope_condition', $tokens[$opener], 'Opener scope condition is not set');
|
|
$this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Opener scope opener is not set');
|
|
$this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Opener scope closer is not set');
|
|
$this->assertSame($token, $tokens[$opener]['scope_condition'], 'Opener scope condition is not the '.$tokenType.' token');
|
|
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], $tokenType.' opener scope opener token incorrect');
|
|
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], $tokenType.' opener scope closer token incorrect');
|
|
|
|
$closer = $tokenArray['scope_closer'];
|
|
$this->assertArrayHasKey('scope_condition', $tokens[$closer], 'Closer scope condition is not set');
|
|
$this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Closer scope opener is not set');
|
|
$this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Closer scope closer is not set');
|
|
if ($skipScopeCloserCheck === false) {
|
|
$this->assertSame($token, $tokens[$closer]['scope_condition'], 'Closer scope condition is not the '.$tokenType.' token');
|
|
}
|
|
|
|
$this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], $tokenType.' closer scope opener token incorrect');
|
|
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], $tokenType.' closer scope closer token incorrect');
|
|
|
|
if (($opener + 1) !== $closer) {
|
|
for ($i = ($opener + 1); $i < $closer; $i++) {
|
|
$this->assertArrayHasKey(
|
|
$token,
|
|
$tokens[$i]['conditions'],
|
|
$tokenType.' condition not added for token belonging to the '.$tokenType.' structure'
|
|
);
|
|
}
|
|
}
|
|
|
|
}//end scopeTestHelper()
|
|
|
|
|
|
/**
|
|
* Helper function to verify that all parenthesis related array indexes for a control structure
|
|
* token are set correctly.
|
|
*
|
|
* @param int $token The position of the control structure token.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function parenthesisTestHelper($token)
|
|
{
|
|
$tokens = self::$phpcsFile->getTokens();
|
|
$tokenArray = $tokens[$token];
|
|
$tokenType = $tokenArray['type'];
|
|
|
|
$this->assertArrayHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is not set');
|
|
$this->assertArrayHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is not set');
|
|
$this->assertArrayHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is not set');
|
|
$this->assertSame($token, $tokenArray['parenthesis_owner'], 'Parenthesis owner is not the '.$tokenType.' token');
|
|
|
|
$opener = $tokenArray['parenthesis_opener'];
|
|
$this->assertArrayHasKey('parenthesis_owner', $tokens[$opener], 'Opening parenthesis owner is not set');
|
|
$this->assertSame($token, $tokens[$opener]['parenthesis_owner'], 'Opening parenthesis owner is not the '.$tokenType.' token');
|
|
|
|
$closer = $tokenArray['parenthesis_closer'];
|
|
$this->assertArrayHasKey('parenthesis_owner', $tokens[$closer], 'Closing parenthesis owner is not set');
|
|
$this->assertSame($token, $tokens[$closer]['parenthesis_owner'], 'Closing parenthesis owner is not the '.$tokenType.' token');
|
|
|
|
}//end parenthesisTestHelper()
|
|
|
|
|
|
}//end class
|