Skip to content

Commit

Permalink
Tokenizer::recurseScopeMap(): fix scope setting for namespace operators
Browse files Browse the repository at this point in the history
The namespace keyword as a scope opener can never be within another scope, so we can safely ignore it when encountered as it will always be the namespace keyword used as an operator in that case.

Includes dedicated unit tests.
  • Loading branch information
jrfnl committed Aug 31, 2020
1 parent 801c8c1 commit 632ba23
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="BackfillFnTokenTest.php" role="test" />
<file baseinstalldir="" name="BackfillNumericSeparatorTest.inc" role="test" />
<file baseinstalldir="" name="BackfillNumericSeparatorTest.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 @@ -1973,6 +1975,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.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/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 @@ -2032,6 +2036,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.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/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
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
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 632ba23

Please sign in to comment.