diff --git a/package.xml b/package.xml index 4a1151a7df..41fe14e2b4 100644 --- a/package.xml +++ b/package.xml @@ -199,6 +199,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2168,6 +2170,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -2268,6 +2272,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/src/Files/File.php b/src/Files/File.php index b0e67cec10..a15af762fc 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1469,6 +1469,7 @@ public function getMethodParameters($stackPtr) case T_NAMESPACE: case T_NS_SEPARATOR: case T_TYPE_UNION: + case T_TYPE_INTERSECTION: case T_FALSE: case T_NULL: // Part of a type hint or default value. @@ -1685,16 +1686,17 @@ public function getMethodProperties($stackPtr) } $valid = [ - T_STRING => T_STRING, - T_CALLABLE => T_CALLABLE, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_STATIC => T_STATIC, - T_FALSE => T_FALSE, - T_NULL => T_NULL, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_TYPE_UNION => T_TYPE_UNION, + T_STRING => T_STRING, + T_CALLABLE => T_CALLABLE, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_STATIC => T_STATIC, + T_FALSE => T_FALSE, + T_NULL => T_NULL, + T_NAMESPACE => T_NAMESPACE, + T_NS_SEPARATOR => T_NS_SEPARATOR, + T_TYPE_UNION => T_TYPE_UNION, + T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, ]; for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) { @@ -1886,15 +1888,16 @@ public function getMemberProperties($stackPtr) if ($i < $stackPtr) { // We've found a type. $valid = [ - T_STRING => T_STRING, - T_CALLABLE => T_CALLABLE, - T_SELF => T_SELF, - T_PARENT => T_PARENT, - T_FALSE => T_FALSE, - T_NULL => T_NULL, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_TYPE_UNION => T_TYPE_UNION, + T_STRING => T_STRING, + T_CALLABLE => T_CALLABLE, + T_SELF => T_SELF, + T_PARENT => T_PARENT, + T_FALSE => T_FALSE, + T_NULL => T_NULL, + T_NAMESPACE => T_NAMESPACE, + T_NS_SEPARATOR => T_NS_SEPARATOR, + T_TYPE_UNION => T_TYPE_UNION, + T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, ]; for ($i; $i < $stackPtr; $i++) { diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 62894393f7..96331f11db 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -103,7 +103,9 @@ public function process(File $phpcsFile, $stackPtr) $error = 'PHP property type declarations must be lowercase; expected "%s" but found "%s"'; $errorCode = 'PropertyTypeFound'; - if (strpos($type, '|') !== false) { + if ($props['type_token'] === T_TYPE_INTERSECTION) { + // Intersection types don't support simple types. + } else if (strpos($type, '|') !== false) { $this->processUnionType( $phpcsFile, $props['type_token'], @@ -132,7 +134,9 @@ public function process(File $phpcsFile, $stackPtr) $error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"'; $errorCode = 'ReturnTypeFound'; - if (strpos($returnType, '|') !== false) { + if ($props['return_type_token'] === T_TYPE_INTERSECTION) { + // Intersection types don't support simple types. + } else if (strpos($returnType, '|') !== false) { $this->processUnionType( $phpcsFile, $props['return_type_token'], @@ -162,7 +166,9 @@ public function process(File $phpcsFile, $stackPtr) $error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"'; $errorCode = 'ParamTypeFound'; - if (strpos($typeHint, '|') !== false) { + if ($param['type_hint_token'] === T_TYPE_INTERSECTION) { + // Intersection types don't support simple types. + } else if (strpos($typeHint, '|') !== false) { $this->processUnionType( $phpcsFile, $param['type_hint_token'], diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index d61d72d1e7..5233a59202 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -462,6 +462,7 @@ class PHP extends Tokenizer T_OPEN_SHORT_ARRAY => 1, T_CLOSE_SHORT_ARRAY => 1, T_TYPE_UNION => 1, + T_TYPE_INTERSECTION => 1, ]; /** @@ -2406,18 +2407,19 @@ protected function processAdditional() if (isset($this->tokens[$x]) === true && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) { $ignore = Util\Tokens::$emptyTokens; $ignore += [ - T_ARRAY => T_ARRAY, - T_CALLABLE => T_CALLABLE, - T_COLON => T_COLON, - T_NAMESPACE => T_NAMESPACE, - T_NS_SEPARATOR => T_NS_SEPARATOR, - T_NULL => T_NULL, - T_NULLABLE => T_NULLABLE, - T_PARENT => T_PARENT, - T_SELF => T_SELF, - T_STATIC => T_STATIC, - T_STRING => T_STRING, - T_TYPE_UNION => T_TYPE_UNION, + T_ARRAY => T_ARRAY, + T_CALLABLE => T_CALLABLE, + T_COLON => T_COLON, + T_NAMESPACE => T_NAMESPACE, + T_NS_SEPARATOR => T_NS_SEPARATOR, + T_NULL => T_NULL, + T_NULLABLE => T_NULLABLE, + T_PARENT => T_PARENT, + T_SELF => T_SELF, + T_STATIC => T_STATIC, + T_STRING => T_STRING, + T_TYPE_UNION => T_TYPE_UNION, + T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, ]; $closer = $this->tokens[$x]['parenthesis_closer']; @@ -2713,9 +2715,12 @@ protected function processAdditional() }//end if continue; - } else if ($this->tokens[$i]['code'] === T_BITWISE_OR) { + } else if ($this->tokens[$i]['code'] === T_BITWISE_OR + || $this->tokens[$i]['code'] === T_BITWISE_AND + ) { /* Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR. + Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND. */ $allowed = [ @@ -2780,12 +2785,12 @@ protected function processAdditional() }//end for if ($typeTokenCount === 0 || isset($suspectedType) === false) { - // Definitely not a union type, move on. + // Definitely not a union or intersection type, move on. continue; } $typeTokenCount = 0; - $unionOperators = [$i]; + $typeOperators = [$i]; $confirmed = false; for ($x = ($i - 1); $x >= 0; $x--) { @@ -2798,13 +2803,13 @@ protected function processAdditional() continue; } - // Union types can't use the nullable operator, but be tolerant to parse errors. + // Union and intersection types can't use the nullable operator, but be tolerant to parse errors. if ($typeTokenCount > 0 && $this->tokens[$x]['code'] === T_NULLABLE) { continue; } - if ($this->tokens[$x]['code'] === T_BITWISE_OR) { - $unionOperators[] = $x; + if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) { + $typeOperators[] = $x; continue; } @@ -2870,17 +2875,27 @@ protected function processAdditional() }//end if if ($confirmed === false) { - // Not a union type after all, move on. + // Not a union or intersection type after all, move on. continue; } - foreach ($unionOperators as $x) { - $this->tokens[$x]['code'] = T_TYPE_UNION; - $this->tokens[$x]['type'] = 'T_TYPE_UNION'; + foreach ($typeOperators as $x) { + if ($this->tokens[$x]['code'] === T_BITWISE_OR) { + $this->tokens[$x]['code'] = T_TYPE_UNION; + $this->tokens[$x]['type'] = 'T_TYPE_UNION'; - if (PHP_CODESNIFFER_VERBOSITY > 1) { - $line = $this->tokens[$x]['line']; - echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL; + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$x]['line']; + echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL; + } + } else { + $this->tokens[$x]['code'] = T_TYPE_INTERSECTION; + $this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION'; + + if (PHP_CODESNIFFER_VERBOSITY > 1) { + $line = $this->tokens[$x]['line']; + echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL; + } } } @@ -2938,6 +2953,7 @@ protected function processAdditional() T_NAME_RELATIVE => T_NAME_RELATIVE, T_NAME_QUALIFIED => T_NAME_QUALIFIED, T_TYPE_UNION => T_TYPE_UNION, + T_TYPE_INTERSECTION => T_TYPE_INTERSECTION, T_BITWISE_OR => T_BITWISE_OR, T_BITWISE_AND => T_BITWISE_AND, T_ARRAY => T_ARRAY, diff --git a/src/Util/Tokens.php b/src/Util/Tokens.php index febd9c5732..53e5ef4b98 100644 --- a/src/Util/Tokens.php +++ b/src/Util/Tokens.php @@ -81,6 +81,7 @@ define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT'); define('T_ATTRIBUTE_END', 'PHPCS_T_ATTRIBUTE_END'); define('T_ENUM_CASE', 'PHPCS_T_ENUM_CASE'); +define('T_TYPE_INTERSECTION', 'PHPCS_T_TYPE_INTERSECTION'); // Some PHP 5.5 tokens, replicated for lower versions. if (defined('T_FINALLY') === false) { diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index 4533c5ffaf..044caec185 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -280,3 +280,19 @@ enum Direction implements ArrayAccess /* testEnumMethodParamNotProperty */ public function offsetGet($val) { ... } } + +$anon = class() { + /* testPHP81IntersectionTypes */ + public Foo&Bar $intersectionType; + + /* testPHP81MoreIntersectionTypes */ + public Foo&Bar&Baz $moreIntersectionTypes; + + /* testPHP81IllegalIntersectionTypes */ + // Intentional fatal error - types which are not allowed for intersection type, but that's not the concern of the method. + public int&string $illegalIntersectionType; + + /* testPHP81NulltableIntersectionType */ + // Intentional fatal error - nullability is not allowed with intersection type, but that's not the concern of the method. + public ?Foo&Bar $nullableIntersectionType; +}; diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index cc709e69c7..ae16a778fe 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -736,6 +736,46 @@ public function dataGetMemberProperties() '/* testEnumProperty */', [], ], + [ + '/* testPHP81IntersectionTypes */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'Foo&Bar', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP81MoreIntersectionTypes */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'Foo&Bar&Baz', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP81IllegalIntersectionTypes */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => 'int&string', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP81NulltableIntersectionType */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'type' => '?Foo&Bar', + 'nullable_type' => true, + ], + ], ]; }//end dataGetMemberProperties() diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index e7565e4cad..dc46549140 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -145,3 +145,20 @@ class ParametersWithAttributes( &...$otherParam, ) {} } + +/* testPHP8IntersectionTypes */ +function intersectionTypes(Foo&Bar $obj1, Boo&Bar $obj2) {} + +/* testPHP81IntersectionTypesWithSpreadOperatorAndReference */ +function globalFunctionWithSpreadAndReference(Boo&Bar &$paramA, Foo&Bar ...$paramB) {} + +/* testPHP81MoreIntersectionTypes */ +function moreIntersectionTypes(MyClassA&\Package\MyClassB&\Package\MyClassC $var) {} + +/* testPHP81IllegalIntersectionTypes */ +// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method. +$closure = function (string&int $numeric_string) {}; + +/* testPHP81NullableIntersectionTypes */ +// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method. +$closure = function (?Foo&Bar $object) {}; diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index e07bd869d3..f70d64f2d1 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -991,6 +991,139 @@ public function testParameterAttributesInFunctionDeclaration() }//end testParameterAttributesInFunctionDeclaration() + /** + * Verify recognition of PHP8.1 intersection type declaration. + * + * @return void + */ + public function testPHP8IntersectionTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$obj1', + 'content' => 'Foo&Bar $obj1', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'Foo&Bar', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$obj2', + 'content' => 'Boo&Bar $obj2', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'Boo&Bar', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8IntersectionTypes() + + + /** + * Verify recognition of PHP8 intersection type declaration when the variable has either a spread operator or a reference. + * + * @return void + */ + public function testPHP81IntersectionTypesWithSpreadOperatorAndReference() + { + $expected = []; + $expected[0] = [ + 'name' => '$paramA', + 'content' => 'Boo&Bar &$paramA', + 'has_attributes' => false, + 'pass_by_reference' => true, + 'variable_length' => false, + 'type_hint' => 'Boo&Bar', + 'nullable_type' => false, + ]; + $expected[1] = [ + 'name' => '$paramB', + 'content' => 'Foo&Bar ...$paramB', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => true, + 'type_hint' => 'Foo&Bar', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81IntersectionTypesWithSpreadOperatorAndReference() + + + /** + * Verify recognition of PHP8.1 intersection type declaration with more types. + * + * @return void + */ + public function testPHP81MoreIntersectionTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'MyClassA&\Package\MyClassB&\Package\MyClassC $var', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'MyClassA&\Package\MyClassB&\Package\MyClassC', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81MoreIntersectionTypes() + + + /** + * Verify recognition of PHP8.1 intersection type declaration with illegal simple types. + * + * @return void + */ + public function testPHP81IllegalIntersectionTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$numeric_string', + 'content' => 'string&int $numeric_string', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'string&int', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81IllegalIntersectionTypes() + + + /** + * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability. + * + * @return void + */ + public function testPHP81NullableIntersectionTypes() + { + $expected = []; + $expected[0] = [ + 'name' => '$object', + 'content' => '?Foo&Bar $object', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?Foo&Bar', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81NullableIntersectionTypes() + + /** * Test helper. * diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc index d890aad92b..0c592369a5 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/File/GetMethodPropertiesTest.inc @@ -133,3 +133,20 @@ function never(): never {} /* testPHP81NullableNeverType */ // Intentional fatal error - nullability is not allowed with never, but that's not the concern of the method. function nullableNever(): ?never {} + +/* testPHP8IntersectionTypes */ +function intersectionTypes(): Foo&Bar {} + +/* testPHP81MoreIntersectionTypes */ +function moreIntersectionTypes(): MyClassA&\Package\MyClassB&\Package\MyClassC {} + +/* testPHP81IntersectionArrowFunction */ +$fn = fn($var): MyClassA&\Package\MyClassB => $var; + +/* testPHP81IllegalIntersectionTypes */ +// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method. +$closure = function (): string&int {}; + +/* testPHP81NullableIntersectionTypes */ +// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method. +$closure = function (): ?Foo&Bar {}; diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index 9872cf3987..2f552bfecd 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -774,6 +774,122 @@ public function testPHP81NullableNeverType() }//end testPHP81NullableNeverType() + /** + /** + * Verify recognition of PHP8.1 intersection type declaration. + * + * @return void + */ + public function testPHP8IntersectionTypes() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'Foo&Bar', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP8IntersectionTypes() + + + /** + * Verify recognition of PHP8.1 intersection type declaration with more types. + * + * @return void + */ + public function testPHP81MoreIntersectionTypes() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'MyClassA&\Package\MyClassB&\Package\MyClassC', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81MoreIntersectionTypes() + + + /** + * Verify recognition of PHP8.1 intersection type declaration in arrow function. + * + * @return void + */ + public function testPHP81IntersectionArrowFunction() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'MyClassA&\Package\MyClassB', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81IntersectionArrowFunction() + + + /** + * Verify recognition of PHP8.1 intersection type declaration with illegal simple types. + * + * @return void + */ + public function testPHP81IllegalIntersectionTypes() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'string&int', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81IllegalIntersectionTypes() + + + /** + * Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability. + * + * @return void + */ + public function testPHP81NullableIntersectionTypes() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => '?Foo&Bar', + 'nullable_return_type' => true, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP81NullableIntersectionTypes() + + /** * Test helper. * diff --git a/tests/Core/Tokenizer/TypeIntersectionTest.inc b/tests/Core/Tokenizer/TypeIntersectionTest.inc new file mode 100644 index 0000000000..abf9b85ba8 --- /dev/null +++ b/tests/Core/Tokenizer/TypeIntersectionTest.inc @@ -0,0 +1,120 @@ + $param & $int; + +/* testTypeIntersectionArrowReturnType */ +$arrowWithReturnType = fn ($param) : Foo&Bar => $param * 10; + +/* testBitwiseAndInArrayKey */ +$array = array( + A & B => /* testBitwiseAndInArrayValue */ B & C +); + +/* testBitwiseAndInShortArrayKey */ +$array = [ + A & B => /* testBitwiseAndInShortArrayValue */ B & C +]; + +/* testBitwiseAndNonArrowFnFunctionCall */ +$obj->fn($something & $else); + +/* testBitwiseAnd6 */ +function &fn(/* testTypeIntersectionNonArrowFunctionDeclaration */ Foo&Bar $something) {} + +/* testTypeIntersectionWithInvalidTypes */ +function (int&string $var) {}; + +/* testLiveCoding */ +// Intentional parse error. This has to be the last test in the file. +return function( Foo& diff --git a/tests/Core/Tokenizer/TypeIntersectionTest.php b/tests/Core/Tokenizer/TypeIntersectionTest.php new file mode 100644 index 0000000000..2170021933 --- /dev/null +++ b/tests/Core/Tokenizer/TypeIntersectionTest.php @@ -0,0 +1,138 @@ + + * @author Jaroslav HanslĂ­k + * @copyright 2020 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; + +class TypeIntersectionTest extends AbstractMethodUnitTest +{ + + + /** + * Test that non-intersection type bitwise and tokens are still tokenized as bitwise and. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataBitwiseAnd + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testBitwiseAnd($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $opener = $this->getTargetToken($testMarker, [T_BITWISE_AND, T_TYPE_INTERSECTION]); + $this->assertSame(T_BITWISE_AND, $tokens[$opener]['code']); + $this->assertSame('T_BITWISE_AND', $tokens[$opener]['type']); + + }//end testBitwiseAnd() + + + /** + * Data provider. + * + * @see testBitwiseAnd() + * + * @return array + */ + public function dataBitwiseAnd() + { + return [ + ['/* testBitwiseAnd1 */'], + ['/* testBitwiseAnd2 */'], + ['/* testBitwiseAndPropertyDefaultValue */'], + ['/* testBitwiseAndParamDefaultValue */'], + ['/* testBitwiseAnd3 */'], + ['/* testBitwiseAnd4 */'], + ['/* testBitwiseAnd5 */'], + ['/* testBitwiseAndClosureParamDefault */'], + ['/* testBitwiseAndArrowParamDefault */'], + ['/* testBitwiseAndArrowExpression */'], + ['/* testBitwiseAndInArrayKey */'], + ['/* testBitwiseAndInArrayValue */'], + ['/* testBitwiseAndInShortArrayKey */'], + ['/* testBitwiseAndInShortArrayValue */'], + ['/* testBitwiseAndNonArrowFnFunctionCall */'], + ['/* testBitwiseAnd6 */'], + ['/* testLiveCoding */'], + ]; + + }//end dataBitwiseAnd() + + + /** + * Test that bitwise and tokens when used as part of a intersection type are tokenized as `T_TYPE_INTERSECTION`. + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * + * @dataProvider dataTypeIntersection + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testTypeIntersection($testMarker) + { + $tokens = self::$phpcsFile->getTokens(); + + $opener = $this->getTargetToken($testMarker, [T_BITWISE_AND, T_TYPE_INTERSECTION]); + $this->assertSame(T_TYPE_INTERSECTION, $tokens[$opener]['code']); + $this->assertSame('T_TYPE_INTERSECTION', $tokens[$opener]['type']); + + }//end testTypeIntersection() + + + /** + * Data provider. + * + * @see testTypeIntersection() + * + * @return array + */ + public function dataTypeIntersection() + { + return [ + ['/* testTypeIntersectionPropertySimple */'], + ['/* testTypeIntersectionPropertyReverseModifierOrder */'], + ['/* testTypeIntersectionPropertyMulti1 */'], + ['/* testTypeIntersectionPropertyMulti2 */'], + ['/* testTypeIntersectionPropertyMulti3 */'], + ['/* testTypeIntersectionPropertyNamespaceRelative */'], + ['/* testTypeIntersectionPropertyPartiallyQualified */'], + ['/* testTypeIntersectionPropertyFullyQualified */'], + ['/* testTypeIntersectionPropertyWithReadOnlyKeyword */'], + ['/* testTypeIntersectionParam1 */'], + ['/* testTypeIntersectionParam2 */'], + ['/* testTypeIntersectionParam3 */'], + ['/* testTypeIntersectionParamNamespaceRelative */'], + ['/* testTypeIntersectionParamPartiallyQualified */'], + ['/* testTypeIntersectionParamFullyQualified */'], + ['/* testTypeIntersectionReturnType */'], + ['/* testTypeIntersectionConstructorPropertyPromotion */'], + ['/* testTypeIntersectionAbstractMethodReturnType1 */'], + ['/* testTypeIntersectionAbstractMethodReturnType2 */'], + ['/* testTypeIntersectionReturnTypeNamespaceRelative */'], + ['/* testTypeIntersectionReturnPartiallyQualified */'], + ['/* testTypeIntersectionReturnFullyQualified */'], + ['/* testTypeIntersectionClosureParamIllegalNullable */'], + ['/* testTypeIntersectionWithReference */'], + ['/* testTypeIntersectionWithSpreadOperator */'], + ['/* testTypeIntersectionClosureReturn */'], + ['/* testTypeIntersectionArrowParam */'], + ['/* testTypeIntersectionArrowReturnType */'], + ['/* testTypeIntersectionNonArrowFunctionDeclaration */'], + ['/* testTypeIntersectionWithInvalidTypes */'], + ]; + + }//end dataTypeIntersection() + + +}//end class