Skip to content

Commit

Permalink
Merge branch 'feature/bugfixes-namespace-operator-in-typedeclarations…
Browse files Browse the repository at this point in the history
  • Loading branch information
gsherwood committed Sep 20, 2020
2 parents 6525028 + 16d2cd1 commit 4db6a74
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="BackfillNumericSeparatorTest.php" role="test" />
<file baseinstalldir="" name="NullsafeObjectOperatorTest.inc" role="test" />
<file baseinstalldir="" name="NullsafeObjectOperatorTest.php" role="test" />
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.inc" role="test" />
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.php" role="test" />
<file baseinstalldir="" name="ShortArrayTest.inc" role="test" />
<file baseinstalldir="" name="ShortArrayTest.php" role="test" />
<file baseinstalldir="" name="StableCommentWhitespaceTest.inc" role="test" />
Expand Down Expand Up @@ -2000,6 +2002,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.php" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.inc" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.inc" name="tests/Core/Tokenizer/ShortArrayTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceTest.php" name="tests/Core/Tokenizer/StableCommentWhitespaceTest.php" />
Expand Down Expand Up @@ -2063,6 +2067,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.php" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.inc" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.inc" name="tests/Core/Tokenizer/ShortArrayTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceTest.php" name="tests/Core/Tokenizer/StableCommentWhitespaceTest.php" />
Expand Down
3 changes: 3 additions & 0 deletions src/Files/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,7 @@ public function getMethodParameters($stackPtr)
$typeHintEndToken = $i;
}
break;
case T_NAMESPACE:
case T_NS_SEPARATOR:
// Part of a type hint or default value.
if ($defaultStart === null) {
Expand Down Expand Up @@ -1630,6 +1631,7 @@ public function getMethodProperties($stackPtr)
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_STATIC => T_STATIC,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
];

Expand Down Expand Up @@ -1813,6 +1815,7 @@ public function getMemberProperties($stackPtr)
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
];

Expand Down
3 changes: 3 additions & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,7 @@ protected function tokenize($string)
|| $tokenType === T_NAME_RELATIVE
|| $tokenType === T_NAME_QUALIFIED
|| $tokenType === T_ARRAY
|| $tokenType === T_NAMESPACE
|| $tokenType === T_NS_SEPARATOR
) {
$lastRelevantNonEmpty = $tokenType;
Expand Down Expand Up @@ -1473,6 +1474,7 @@ function return types. We want to keep the parenthesis map clean,
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
];

Expand Down Expand Up @@ -1987,6 +1989,7 @@ protected function processAdditional()
T_STRING => T_STRING,
T_ARRAY => T_ARRAY,
T_COLON => T_COLON,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_NULLABLE => T_NULLABLE,
T_CALLABLE => T_CALLABLE,
Expand Down
7 changes: 7 additions & 0 deletions src/Tokenizers/Tokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,13 @@ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
continue;
}

if ($tokenType === T_NAMESPACE) {
// PHP namespace keywords are special because they can be
// used as blocks but also inline as operators.
// So if we find them nested inside another opener, just skip them.
continue;
}

if ($tokenType === T_FUNCTION
&& $this->tokens[$stackPtr]['code'] !== T_FUNCTION
) {
Expand Down
5 changes: 5 additions & 0 deletions tests/Core/File/GetMemberPropertiesTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,8 @@ class PHP8Mixed {
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
private ?mixed $nullableMixed;
}

class NSOperatorInType {
/* testNamespaceOperatorTypeHint */
public ?namespace\Name $prop;
}
10 changes: 10 additions & 0 deletions tests/Core/File/GetMemberPropertiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,16 @@ public function dataGetMemberProperties()
'nullable_type' => true,
],
],
[
'/* testNamespaceOperatorTypeHint */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '?namespace\Name',
'nullable_type' => true,
],
],
];

}//end dataGetMemberProperties()
Expand Down
3 changes: 3 additions & 0 deletions tests/Core/File/GetMethodParametersTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ function mixedTypeHint(mixed &...$var1) {}
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
function mixedTypeHintNullable(?Mixed $var1) {}

/* testNamespaceOperatorTypeHint */
function namespaceOperatorTypeHint(?namespace\Name $var1) {}
22 changes: 22 additions & 0 deletions tests/Core/File/GetMethodParametersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,28 @@ public function testPHP8MixedTypeHintNullable()
}//end testPHP8MixedTypeHintNullable()


/**
* Verify recognition of type declarations using the namespace operator.
*
* @return void
*/
public function testNamespaceOperatorTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => '?namespace\Name $var1',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '?namespace\Name',
'nullable_type' => true,
];

$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testNamespaceOperatorTypeHint()


/**
* Test helper.
*
Expand Down
3 changes: 3 additions & 0 deletions tests/Core/File/GetMethodPropertiesTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@ function mixedTypeHint() :mixed {}
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
function mixedTypeHintNullable(): ?mixed {}

/* testNamespaceOperatorTypeHint */
function namespaceOperatorTypeHint() : ?namespace\Name {}
23 changes: 23 additions & 0 deletions tests/Core/File/GetMethodPropertiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,29 @@ public function testPHP8MixedTypeHintNullable()
}//end testPHP8MixedTypeHintNullable()


/**
* Test a function with return type using the namespace operator.
*
* @return void
*/
public function testNamespaceOperatorTypeHint()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '?namespace\Name',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];

$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testNamespaceOperatorTypeHint()


/**
* Test helper.
*
Expand Down
3 changes: 3 additions & 0 deletions tests/Core/Tokenizer/BackfillFnTokenTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ $a = fn($x) => yield 'k' => $x;
/* testNullableNamespace */
$a = fn(?\DateTime $x) : ?\DateTime => $x;

/* testNamespaceOperatorInTypes */
$fn = fn(namespace\Foo $a) : ?namespace\Foo => $a;

/* testSelfReturnType */
fn(self $a) : self => $a;

Expand Down
28 changes: 28 additions & 0 deletions tests/Core/Tokenizer/BackfillFnTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,34 @@ public function testNullableNamespace()
}//end testNullableNamespace()


/**
* Test arrow functions that use the namespace operator in the return type.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNamespaceOperatorInTypes()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testNamespaceOperatorInTypes */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 16), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 19), 'Scope closer is not the semicolon token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 16), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 19), 'Opener scope closer is not the semicolon token');

$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 16), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 19), 'Closer scope closer is not the semicolon token');

}//end testNamespaceOperatorInTypes()


/**
* Test arrow functions that use self/parent/callable/array/static return types.
*
Expand Down
19 changes: 19 additions & 0 deletions tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/* testClassExtends */
class Foo extends namespace\Bar {}

/* testClassImplements */
$anon = new class implements namespace\Foo {}

/* testInterfaceExtends */
interface FooBar extends namespace\BarFoo {}

/* testFunctionReturnType */
function foo() : namespace\Baz {}

/* testClosureReturnType */
$closure = function () : namespace\Baz {}

/* testArrowFunctionReturnType */
$fn = fn() : namespace\Baz => new namespace\Baz;
98 changes: 98 additions & 0 deletions tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php
/**
* Tests the adding of the "bracket_opener/closer" keys to use group tokens.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @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 ScopeSettingWithNamespaceOperatorTest extends AbstractMethodUnitTest
{


/**
* Test that the scope opener/closers are set correctly when the namespace keyword is encountered as an operator.
*
* @param string $testMarker The comment which prefaces the target tokens in the test file.
* @param int|string[] $tokenTypes The token type to search for.
* @param int|string[] $open Optional. The token type for the scope opener.
* @param int|string[] $close Optional. The token type for the scope closer.
*
* @dataProvider dataScopeSetting
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
*
* @return void
*/
public function testScopeSetting($testMarker, $tokenTypes, $open=T_OPEN_CURLY_BRACKET, $close=T_CLOSE_CURLY_BRACKET)
{
$tokens = self::$phpcsFile->getTokens();

$target = $this->getTargetToken($testMarker, $tokenTypes);
$opener = $this->getTargetToken($testMarker, $open);
$closer = $this->getTargetToken($testMarker, $close);

$this->assertArrayHasKey('scope_opener', $tokens[$target], 'Scope opener missing');
$this->assertArrayHasKey('scope_closer', $tokens[$target], 'Scope closer missing');
$this->assertSame($opener, $tokens[$target]['scope_opener'], 'Scope opener not same');
$this->assertSame($closer, $tokens[$target]['scope_closer'], 'Scope closer not same');

$this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Scope opener missing for open curly');
$this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Scope closer missing for open curly');
$this->assertSame($opener, $tokens[$opener]['scope_opener'], 'Scope opener not same for open curly');
$this->assertSame($closer, $tokens[$opener]['scope_closer'], 'Scope closer not same for open curly');

$this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Scope opener missing for close curly');
$this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Scope closer missing for close curly');
$this->assertSame($opener, $tokens[$closer]['scope_opener'], 'Scope opener not same for close curly');
$this->assertSame($closer, $tokens[$closer]['scope_closer'], 'Scope closer not same for close curly');

}//end testScopeSetting()


/**
* Data provider.
*
* @see testScopeSetting()
*
* @return array
*/
public function dataScopeSetting()
{
return [
[
'/* testClassExtends */',
[T_CLASS],
],
[
'/* testClassImplements */',
[T_ANON_CLASS],
],
[
'/* testInterfaceExtends */',
[T_INTERFACE],
],
[
'/* testFunctionReturnType */',
[T_FUNCTION],
],
[
'/* testClosureReturnType */',
[T_CLOSURE],
],
[
'/* testArrowFunctionReturnType */',
[T_FN],
[T_FN_ARROW],
[T_SEMICOLON],
],
];

}//end dataScopeSetting()


}//end class

0 comments on commit 4db6a74

Please sign in to comment.