Skip to content

Commit

Permalink
Tests/Tokenizer/DNFTypesTest: split the test class
Browse files Browse the repository at this point in the history
... into two test classes, one targetting the `Tokenizer\PHP` class, one targetting the `Tokenizer\Tokenizer` class.

While this does mean there is now some duplication between these test classes, I don't think that's problematic.

It also allows for these tests to diverge based on the specific test needs for each of the classes under test.
  • Loading branch information
jrfnl committed May 15, 2024
1 parent 841f909 commit 9c961f7
Show file tree
Hide file tree
Showing 4 changed files with 567 additions and 58 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
namespace PHP_CodeSniffer\Tests\Core\Tokenizer\PHP;

use PHP_CodeSniffer\Tests\Core\Tokenizer\AbstractTokenizerTestCase;
use PHP_CodeSniffer\Util\Tokens;

final class DNFTypesTest extends AbstractTokenizerTestCase
Expand All @@ -18,17 +19,16 @@ final class DNFTypesTest extends AbstractTokenizerTestCase
/**
* Test that parentheses when **not** used in a type declaration are correctly tokenized.
*
* @param string $testMarker The comment prefacing the target token.
* @param int|false $owner Optional. The parentheses owner or false when no parentheses owner is expected.
* @param bool $skipCheckInside Optional. Skip checking correct token type inside the parentheses.
* Use judiciously for combined normal + DNF tests only.
* @param string $testMarker The comment prefacing the target token.
* @param bool $skipCheckInside Optional. Skip checking correct token type inside the parentheses.
* Use judiciously for combined normal + DNF tests only.
*
* @dataProvider dataNormalParentheses
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createParenthesisNestingMap
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNormalParentheses($testMarker, $owner=false, $skipCheckInside=false)
public function testNormalParentheses($testMarker, $skipCheckInside=false)
{
$tokens = $this->phpcsFile->getTokens();

Expand All @@ -39,40 +39,14 @@ public function testNormalParentheses($testMarker, $owner=false, $skipCheckInsid
$this->assertSame(T_OPEN_PARENTHESIS, $opener['code'], 'Token tokenized as '.$opener['type'].', not T_OPEN_PARENTHESIS (code)');
$this->assertSame('T_OPEN_PARENTHESIS', $opener['type'], 'Token tokenized as '.$opener['type'].', not T_OPEN_PARENTHESIS (type)');

if ($owner !== false) {
$this->assertArrayHasKey('parenthesis_owner', $opener, 'Parenthesis owner is not set');
$this->assertSame(($openPtr + $owner), $opener['parenthesis_owner'], 'Opener parenthesis owner is not the expected token');
} else {
$this->assertArrayNotHasKey('parenthesis_owner', $opener, 'Parenthesis owner is set');
}

$this->assertArrayHasKey('parenthesis_opener', $opener, 'Parenthesis opener is not set');
$this->assertArrayHasKey('parenthesis_closer', $opener, 'Parenthesis closer is not set');
$this->assertSame($openPtr, $opener['parenthesis_opener'], 'Parenthesis opener is not the expected token');

$closePtr = $opener['parenthesis_closer'];
$closer = $tokens[$closePtr];

$this->assertSame(')', $closer['content'], 'Content of type close parenthesis is not ")"');
$this->assertSame(T_CLOSE_PARENTHESIS, $closer['code'], 'Token tokenized as '.$closer['type'].', not T_CLOSE_PARENTHESIS (code)');
$this->assertSame('T_CLOSE_PARENTHESIS', $closer['type'], 'Token tokenized as '.$closer['type'].', not T_CLOSE_PARENTHESIS (type)');

if ($owner !== false) {
$this->assertArrayHasKey('parenthesis_owner', $closer, 'Parenthesis owner is not set');
$this->assertSame(($openPtr + $owner), $closer['parenthesis_owner'], 'Closer parenthesis owner is not the expected token');
} else {
$this->assertArrayNotHasKey('parenthesis_owner', $closer, 'Parenthesis owner is set');
}

$this->assertArrayHasKey('parenthesis_opener', $closer, 'Parenthesis opener is not set');
$this->assertArrayHasKey('parenthesis_closer', $closer, 'Parenthesis closer is not set');
$this->assertSame($closePtr, $closer['parenthesis_closer'], 'Parenthesis closer is not the expected token');

for ($i = ($openPtr + 1); $i < $closePtr; $i++) {
$this->assertArrayHasKey('nested_parenthesis', $tokens[$i], "Nested parenthesis key not set on token $i ({$tokens[$i]['type']})");
$this->assertArrayHasKey($openPtr, $tokens[$i]['nested_parenthesis'], 'Nested parenthesis is missing target parentheses set');
$this->assertSame($closePtr, $tokens[$i]['nested_parenthesis'][$openPtr], 'Nested parenthesis closer not set correctly');

// If there are ampersands, make sure these are tokenized as bitwise and.
if ($skipCheckInside === false && $tokens[$i]['content'] === '&') {
$this->assertSame(T_BITWISE_AND, $tokens[$i]['code'], 'Token tokenized as '.$tokens[$i]['type'].', not T_BITWISE_AND (code)');
Expand Down Expand Up @@ -133,44 +107,36 @@ public static function dataNormalParentheses()
],
'parens with owner: function; & in default value' => [
'testMarker' => '/* testParensOwnerFunctionAmpersandInDefaultValue */',
'owner' => -3,
],
'parens with owner: closure; param declared by & ref' => [
'testMarker' => '/* testParensOwnerClosureAmpersandParamRef */',
'owner' => -1,
],
'parens with owner: if' => [
'testMarker' => '/* testParensOwnerIf */',
'owner' => -2,
],
'parens without owner in if condition' => [
'testMarker' => '/* testParensNoOwnerInIfCondition */',
],
'parens with owner: for' => [
'testMarker' => '/* testParensOwnerFor */',
'owner' => -2,
],
'parens without owner in for condition' => [
'testMarker' => '/* testParensNoOwnerInForCondition */',
],
'parens with owner: match' => [
'testMarker' => '/* testParensOwnerMatch */',
'owner' => -1,
],
'parens with owner: array' => [
'testMarker' => '/* testParensOwnerArray */',
'owner' => -2,
],
'parens without owner in array; function call with & in callable' => [
'testMarker' => '/* testParensNoOwnerFunctionCallWithAmpersandInCallable */',
],
'parens with owner: fn; & in return value' => [
'testMarker' => '/* testParensOwnerArrowFn */',
'owner' => -1,
],
'parens with owner: list with reference vars' => [
'testMarker' => '/* testParensOwnerListWithRefVars */',
'owner' => -1,
],
'parens without owner, function call with DNF look-a-like param' => [
'testMarker' => '/* testParensNoOwnerFunctionCallwithDNFLookALikeParam */',
Expand Down Expand Up @@ -199,11 +165,9 @@ public static function dataNormalParentheses()
],
'parens with owner: closure; & in default value' => [
'testMarker' => '/* testParensOwnerClosureAmpersandInDefaultValue */',
'owner' => -2,
],
'parens with owner: fn; dnf used within' => [
'testMarker' => '/* testParensOwnerArrowDNFUsedWithin */',
'owner' => -2,
'skipCheckInside' => true,
],
'parens without owner: default value for param in arrow function' => [
Expand All @@ -228,7 +192,6 @@ public static function dataNormalParentheses()
*
* @dataProvider dataDNFTypeParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createParenthesisNestingMap
*
* @return void
*/
Expand All @@ -243,29 +206,15 @@ public function testDNFTypeParentheses($testMarker)
$this->assertSame(T_TYPE_OPEN_PARENTHESIS, $opener['code'], 'Token tokenized as '.$opener['type'].', not T_TYPE_OPEN_PARENTHESIS (code)');
$this->assertSame('T_TYPE_OPEN_PARENTHESIS', $opener['type'], 'Token tokenized as '.$opener['type'].', not T_TYPE_OPEN_PARENTHESIS (type)');

$this->assertArrayNotHasKey('parenthesis_owner', $opener, 'Parenthesis owner is set');
$this->assertArrayHasKey('parenthesis_opener', $opener, 'Parenthesis opener is not set');
$this->assertArrayHasKey('parenthesis_closer', $opener, 'Parenthesis closer is not set');
$this->assertSame($openPtr, $opener['parenthesis_opener'], 'Parenthesis opener is not the expected token');

$closePtr = $opener['parenthesis_closer'];
$closer = $tokens[$closePtr];

$this->assertSame(')', $closer['content'], 'Content of type close parenthesis is not ")"');
$this->assertSame(T_TYPE_CLOSE_PARENTHESIS, $closer['code'], 'Token tokenized as '.$closer['type'].', not T_TYPE_CLOSE_PARENTHESIS (code)');
$this->assertSame('T_TYPE_CLOSE_PARENTHESIS', $closer['type'], 'Token tokenized as '.$closer['type'].', not T_TYPE_CLOSE_PARENTHESIS (type)');

$this->assertArrayNotHasKey('parenthesis_owner', $closer, 'Parenthesis owner is set');
$this->assertArrayHasKey('parenthesis_opener', $closer, 'Parenthesis opener is not set');
$this->assertArrayHasKey('parenthesis_closer', $closer, 'Parenthesis closer is not set');
$this->assertSame($closePtr, $closer['parenthesis_closer'], 'Parenthesis closer is not the expected token');

$intersectionCount = 0;
for ($i = ($openPtr + 1); $i < $closePtr; $i++) {
$this->assertArrayHasKey('nested_parenthesis', $tokens[$i], "Nested parenthesis key not set on token $i ({$tokens[$i]['type']})");
$this->assertArrayHasKey($openPtr, $tokens[$i]['nested_parenthesis'], 'Nested parenthesis is missing target parentheses set');
$this->assertSame($closePtr, $tokens[$i]['nested_parenthesis'][$openPtr], 'Nested parenthesis closer not set correctly');

if ($tokens[$i]['content'] === '&') {
$this->assertSame(
T_TYPE_INTERSECTION,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

/*
* Non-exhaustive code samples of "normal" parentheses.
*/

/* testParensNoOwner */
$a = ( CONST_A & CONST_B ) | CONST_C === false;

/* testParensNoOwnerInTernary */
$a = $var ? $something : CONST_C | ( CONST_A & CONST_B );

/* testParensNoOwnerInShortTernary */
$a = $var ?: ( CONST_A & CONST_B );

/* testParensOwnerFunctionAmpersandInDefaultValue */
function defaultValueLooksLikeDNF( mixed $param = (CONST_A&CONST_B) ) {}

/* testParensOwnerClosureAmpersandParamRef */
$closureWithParamRef = function(&$param) {};

/* testParensOwnerIf */
if ( /* testParensNoOwnerInIfCondition */ CONST_C | ( CONST_A & /*comment*/ CONST_B ) > 10 ) {}

/* testParensOwnerFor */
for ($i =0; $i < /* testParensNoOwnerInForCondition */ ( CONST_A & CONST_B ); $i++ );

/* testParensOwnerMatch */
$match = match(CONST_A & CONST_B) {
default => $a,
};

/* testParensOwnerArray */
$array = array (
'text',
\CONST_A & \Fully\Qualified\CONST_B,
/* testParensNoOwnerFunctionCallWithAmpersandInCallable */
do_something($a, /* testParensOwnerArrowFn */ fn($b) => $a & $b, $c),
);

/* testParensOwnerListWithRefVars */
list(&$a, &$b) = $array;

/* testParensNoOwnerFunctionCallwithDNFLookALikeParam */
$obj->static((CONST_A&CONST_B)|CONST_C | $var);


/*
* DNF parentheses.
*/

class DNFTypes {
/* testDNFTypeOOConstUnqualifiedClasses */
public const (A&B)|D UNQUALIFIED = new Foo;

/* testDNFTypeOOConstReverseModifierOrder */
protected final const int|(Foo&Bar)|float MODIFIERS_REVERSED /* testParensNoOwnerOOConstDefaultValue */ = (E_WARNING & E_NOTICE) | E_DEPRECATED;

const
/* testDNFTypeOOConstMulti1 */
(A&B) |
/* testDNFTypeOOConstMulti2 */
(C&D) | // phpcs:ignore Stnd.Cat.Sniff
/* testDNFTypeOOConstMulti3 */
(Y&D)
| null MULTI_DNF = false;

/* testDNFTypeOOConstNamespaceRelative */
final protected const (namespace\Sub\NameA&namespace\Sub\NameB)|namespace\Sub\NameC NAMESPACE_RELATIVE = new namespace\Sub\NameB;
/* testDNFTypeOOConstPartiallyQualified */
const Partially\Qualified\NameC|(Partially\Qualified\NameA&Partially\Qualified\NameB) PARTIALLY_QUALIFIED = new Partially\Qualified\NameA;
/* testDNFTypeOOConstFullyQualified */
const (\Fully\Qualified\NameA&\Fully\Qualified\NameB)|\Fully\Qualified\NameC FULLY_QUALIFIED = new \Fully\Qualified\NameB();
/* testDNFTypePropertyUnqualifiedClasses */
public static (Foo&Bar)|object $obj;
/* testDNFTypePropertyReverseModifierOrder */
static protected string|(A&B)|bool $dnf /* testParensNoOwnerPropertyDefaultValue1 */ = ( E_WARNING & E_NOTICE ) | /* testParensNoOwnerPropertyDefaultValue2 */ (E_ALL & E_DEPRECATED);
private
/* testDNFTypePropertyMultiNamespaceRelative */
(namespace\Sub\NameA&namespace\Sub\NameB) |
/* testDNFTypePropertyMultiPartiallyQualified */
(Partially\Qualified\NameA&Partially\Qualified\NameB) | // phpcs:ignore Stnd.Cat.Sniff
false
/* testDNFTypePropertyMultiFullyQualified */
| (\Fully\Qualified\NameA&\Fully\Qualified\NameB) $multiDnf;
/* testDNFTypePropertyWithReadOnlyKeyword1 */
protected readonly (A&B) | /* testDNFTypePropertyWithReadOnlyKeyword2 */ (C&D) $readonly;
/* testDNFTypePropertyWithStaticAndReadOnlyKeywords */
static readonly (A&B&C)|array $staticReadonly;
/* testDNFTypePropertyWithOnlyStaticKeyword */
static (A&B&C)|true $obj;
public function paramTypes(
/* testDNFTypeParam1WithAttribute */
#[MyAttribute]
(\Foo&Bar)|int|float $paramA /* testParensNoOwnerParamDefaultValue */ = SOMETHING | (CONSTANT_A & CONSTANT_B),
/* testDNFTypeParam2 */
(Foo&\Bar) /* testDNFTypeParam3 */ |(Baz&Fop) &...$paramB = null,
) {
/* testParensNoOwnerInReturnValue1 */
return (
/* testParensNoOwnerInReturnValue2 */
($a1 & $b1) |
/* testParensNoOwnerInReturnValue3 */
($a2 & $b2)
) + $c;
}
public function identifierNames(
/* testDNFTypeParamNamespaceRelative */
(namespace\Sub\NameA&namespace\Sub\NameB)|false $paramA,
/* testDNFTypeParamPartiallyQualified */
Partially\Qualified\NameC|(Partially\Qualified\NameA&Partially\Qualified\NameB) $paramB,
/* testDNFTypeParamFullyQualified */
name|(\Fully\Qualified\NameA&\Fully\Qualified\NameB) $paramC,
) {}

public function __construct(
/* testDNFTypeConstructorPropertyPromotion1 */
public (A&B)| /* testDNFTypeConstructorPropertyPromotion2 */ (A&D) $property
) {}

public function returnType()/* testDNFTypeReturnType1 */ : A|(B&D)|/* testDNFTypeReturnType2 */(B&W)|null {}

abstract public function abstractMethod(): /* testDNFTypeAbstractMethodReturnType1 */ (X&Y) /* testDNFTypeAbstractMethodReturnType2 */ |(W&Z);

public function identifierNamesReturnRelative(
) : /* testDNFTypeReturnTypeNamespaceRelative */ (namespace\Sub\NameA&namespace\Sub\NameB)|namespace\Sub\NameC {}

public function identifierNamesReturnPQ(
) : /* testDNFTypeReturnPartiallyQualified */Partially\Qualified\NameA|(Partially\Qualified\NameB&Partially\Qualified\NameC) {}

// Illegal type: segments which are strict subsets of others are disallowed, but that's not the concern of the tokenizer.
public function identifierNamesReturnFQ(
) /* testDNFTypeReturnFullyQualified */ : (\Fully\Qualified\NameA&\Fully\Qualified\NameB)|\Fully\Qualified\NameB {}
}

function globalFunctionWithSpreadAndReference(
/* testDNFTypeWithReference */
float|(B&A) &$paramA,
/* testDNFTypeWithSpreadOperator */
string|(B|D) ...$paramB
) {}


$closureWithParamType = function ( /* testDNFTypeClosureParamIllegalNullable */ ?(A&B)|bool $string) {};

/* testParensOwnerClosureAmpersandInDefaultValue */
$closureWithReturnType = function ($string = NONSENSE & FAKE) /* testDNFTypeClosureReturn */ : (\Package\MyA&PackageB)|null {};

/* testParensOwnerArrowDNFUsedWithin */
$arrowWithParamType = fn (
/* testDNFTypeArrowParam */
object|(A&B&C)|array $param,
/* testParensNoOwnerAmpersandInDefaultValue */ ?int $int = (CONSTA & CONSTB )| CONST_C
)
/* testParensNoOwnerInArrowReturnExpression */
=> ($param & $foo ) | $int;

$arrowWithReturnType = fn ($param) : /* testDNFTypeArrowReturnType */ int|(A&B) => $param * 10;

$arrowWithParamReturnByRef = fn &(
/* testDNFTypeArrowParamWithReturnByRef */
(A&B)|null $param
) => $param * 10;

function InvalidSyntaxes(
/* testDNFTypeParamIllegalUnnecessaryParens */
(A&B) $parensNotNeeded

/* testDNFTypeParamIllegalIntersectUnionReversed */
A&(B|D) $onlyIntersectAllowedWithinParensAndUnionOutside

/* testDNFTypeParamIllegalNestedParens */
A|(B&(D|W)|null) $nestedParensNotAllowed
) {}
Loading

0 comments on commit 9c961f7

Please sign in to comment.