Skip to content

Commit 632ba23

Browse files
committed
Tokenizer::recurseScopeMap(): fix scope setting for namespace operators
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.
1 parent 801c8c1 commit 632ba23

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

package.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
103103
<file baseinstalldir="" name="BackfillFnTokenTest.php" role="test" />
104104
<file baseinstalldir="" name="BackfillNumericSeparatorTest.inc" role="test" />
105105
<file baseinstalldir="" name="BackfillNumericSeparatorTest.php" role="test" />
106+
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.inc" role="test" />
107+
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.php" role="test" />
106108
<file baseinstalldir="" name="ShortArrayTest.inc" role="test" />
107109
<file baseinstalldir="" name="ShortArrayTest.php" role="test" />
108110
<file baseinstalldir="" name="StableCommentWhitespaceTest.inc" role="test" />
@@ -1973,6 +1975,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
19731975
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
19741976
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
19751977
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
1978+
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
1979+
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" />
19761980
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
19771981
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.inc" name="tests/Core/Tokenizer/ShortArrayTest.inc" />
19781982
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceTest.php" name="tests/Core/Tokenizer/StableCommentWhitespaceTest.php" />
@@ -2032,6 +2036,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
20322036
<install as="CodeSniffer/Core/Tokenizer/BackfillFnTokenTest.inc" name="tests/Core/Tokenizer/BackfillFnTokenTest.inc" />
20332037
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.php" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.php" />
20342038
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
2039+
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
2040+
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" />
20352041
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
20362042
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.inc" name="tests/Core/Tokenizer/ShortArrayTest.inc" />
20372043
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceTest.php" name="tests/Core/Tokenizer/StableCommentWhitespaceTest.php" />

src/Tokenizers/Tokenizer.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,13 @@ private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
11111111
continue;
11121112
}
11131113

1114+
if ($tokenType === T_NAMESPACE) {
1115+
// PHP namespace keywords are special because they can be
1116+
// used as blocks but also inline as operators.
1117+
// So if we find them nested inside another opener, just skip them.
1118+
continue;
1119+
}
1120+
11141121
if ($tokenType === T_FUNCTION
11151122
&& $this->tokens[$stackPtr]['code'] !== T_FUNCTION
11161123
) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/* testClassExtends */
4+
class Foo extends namespace\Bar {}
5+
6+
/* testClassImplements */
7+
$anon = new class implements namespace\Foo {}
8+
9+
/* testInterfaceExtends */
10+
interface FooBar extends namespace\BarFoo {}
11+
12+
/* testFunctionReturnType */
13+
function foo() : namespace\Baz {}
14+
15+
/* testClosureReturnType */
16+
$closure = function () : namespace\Baz {}
17+
18+
/* testArrowFunctionReturnType */
19+
$fn = fn() : namespace\Baz => new namespace\Baz;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
/**
3+
* Tests the adding of the "bracket_opener/closer" keys to use group tokens.
4+
*
5+
* @author Juliette Reinders Folmer <[email protected]>
6+
* @copyright 2020 Squiz Pty Ltd (ABN 77 084 670 600)
7+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8+
*/
9+
10+
namespace PHP_CodeSniffer\Tests\Core\Tokenizer;
11+
12+
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
13+
14+
class ScopeSettingWithNamespaceOperatorTest extends AbstractMethodUnitTest
15+
{
16+
17+
18+
/**
19+
* Test that the scope opener/closers are set correctly when the namespace keyword is encountered as an operator.
20+
*
21+
* @param string $testMarker The comment which prefaces the target tokens in the test file.
22+
* @param int|string[] $tokenTypes The token type to search for.
23+
* @param int|string[] $open Optional. The token type for the scope opener.
24+
* @param int|string[] $close Optional. The token type for the scope closer.
25+
*
26+
* @dataProvider dataScopeSetting
27+
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
28+
*
29+
* @return void
30+
*/
31+
public function testScopeSetting($testMarker, $tokenTypes, $open = T_OPEN_CURLY_BRACKET, $close = T_CLOSE_CURLY_BRACKET)
32+
{
33+
$tokens = self::$phpcsFile->getTokens();
34+
35+
$target = $this->getTargetToken($testMarker, $tokenTypes);
36+
$opener = $this->getTargetToken($testMarker, $open);
37+
$closer = $this->getTargetToken($testMarker, $close);
38+
39+
$this->assertArrayHasKey('scope_opener', $tokens[$target], 'Scope opener missing');
40+
$this->assertArrayHasKey('scope_closer', $tokens[$target], 'Scope closer missing');
41+
$this->assertSame($opener, $tokens[$target]['scope_opener'], 'Scope opener not same');
42+
$this->assertSame($closer, $tokens[$target]['scope_closer'], 'Scope closer not same');
43+
44+
$this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Scope opener missing for open curly');
45+
$this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Scope closer missing for open curly');
46+
$this->assertSame($opener, $tokens[$opener]['scope_opener'], 'Scope opener not same for open curly');
47+
$this->assertSame($closer, $tokens[$opener]['scope_closer'], 'Scope closer not same for open curly');
48+
49+
$this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Scope opener missing for close curly');
50+
$this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Scope closer missing for close curly');
51+
$this->assertSame($opener, $tokens[$closer]['scope_opener'], 'Scope opener not same for close curly');
52+
$this->assertSame($closer, $tokens[$closer]['scope_closer'], 'Scope closer not same for close curly');
53+
54+
}//end testScopeSetting()
55+
56+
57+
/**
58+
* Data provider.
59+
*
60+
* @see testScopeSetting()
61+
*
62+
* @return array
63+
*/
64+
public function dataScopeSetting()
65+
{
66+
return [
67+
[
68+
'/* testClassExtends */',
69+
[T_CLASS],
70+
],
71+
[
72+
'/* testClassImplements */',
73+
[T_ANON_CLASS],
74+
],
75+
[
76+
'/* testInterfaceExtends */',
77+
[T_INTERFACE],
78+
],
79+
[
80+
'/* testFunctionReturnType */',
81+
[T_FUNCTION],
82+
],
83+
[
84+
'/* testClosureReturnType */',
85+
[T_CLOSURE],
86+
],
87+
[
88+
'/* testArrowFunctionReturnType */',
89+
[T_FN],
90+
[T_FN_ARROW],
91+
[T_SEMICOLON],
92+
],
93+
];
94+
95+
}//end dataScopeSetting()
96+
97+
98+
}//end class

0 commit comments

Comments
 (0)