Skip to content

Commit 2d91603

Browse files
jrfnlgsherwood
andcommitted
FunctionDeclarations::getArrowFunctionOpenClose(): allow for returning heredoc/nowdoc
... as per upstream commit squizlabs/PHP_CodeSniffer/commit/ce62dee92da3b038a399a99353fc055b39d2853e which will be included in PHPCS 3.5.6. Also see: squizlabs/PHP_CodeSniffer 2926 As the PHPCS Tokenizer will hang completely when it encounters an arrow function with a heredoc/nowdoc return in PHPCS 3.5.3-3.5.5, this change needs a separate test file as these tests need to be skipped on those PHPCS versions. Co-authored-by: Greg Sherwood <[email protected]>
1 parent 26509fd commit 2d91603

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed

PHPCSUtils/Utils/FunctionDeclarations.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr)
765765

766766
if (isset($tokens[$scopeCloser]['scope_closer']) === true
767767
&& $tokens[$scopeCloser]['code'] !== \T_INLINE_ELSE
768+
&& $tokens[$scopeCloser]['code'] !== \T_END_HEREDOC
769+
&& $tokens[$scopeCloser]['code'] !== \T_END_NOWDOC
768770
) {
769771
// We minus 1 here in case the closer can be shared with us.
770772
$scopeCloser = ($tokens[$scopeCloser]['scope_closer'] - 1);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
/* testHeredoc */
4+
$fn1 = fn() => <<<HTML
5+
fn
6+
HTML;
7+
8+
/* testNowdoc */
9+
$fn1 = fn() => <<<'HTML'
10+
fn
11+
HTML;
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019-2020 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\Tests\Utils\FunctionDeclarations;
12+
13+
use PHPCSUtils\BackCompat\Helper;
14+
use PHPCSUtils\TestUtils\UtilityMethodTestCase;
15+
use PHPCSUtils\Tokens\Collections;
16+
use PHPCSUtils\Utils\FunctionDeclarations;
17+
18+
/**
19+
* Tests for the \PHPCSUtils\Utils\FunctionDeclarations::isArrowFunction() and the
20+
* \PHPCSUtils\Utils\FunctionDeclarations::getArrowFunctionOpenClose() methods for
21+
* a particular situation which will hang the tokenizer.
22+
*
23+
* These tests are based on the `Tokenizer/BackfillFnTokenTest` file in PHPCS itself.
24+
*
25+
* @link https://github.com/squizlabs/php_codesniffer/issues/2926
26+
*
27+
* @covers \PHPCSUtils\Utils\FunctionDeclarations::isArrowFunction
28+
* @covers \PHPCSUtils\Utils\FunctionDeclarations::getArrowFunctionOpenClose
29+
*
30+
* @group functiondeclarations
31+
*
32+
* @since 1.0.0
33+
*/
34+
class IsArrowFunction2926Test extends UtilityMethodTestCase
35+
{
36+
37+
/**
38+
* PHPCS versions in which the tokenizer will hang for these particular test cases.
39+
*
40+
* @var array
41+
*/
42+
private $unsupportedPHPCSVersions = [
43+
'3.5.3' => true,
44+
'3.5.4' => true,
45+
'3.5.5' => true,
46+
];
47+
48+
/**
49+
* Whether the test case file has been tokenized.
50+
*
51+
* Efficiency tweak as the tokenization is done in "before" not in "before class"
52+
* for this test.
53+
*
54+
* @var bool
55+
*/
56+
private static $tokenized = false;
57+
58+
/**
59+
* Do NOT Initialize PHPCS & tokenize the test case file.
60+
*
61+
* Skip tokenizing the test case file on "before class" as at that time, we can't skip the test
62+
* yet if the PHPCS version in incompatible and it would hang the Tokenizer (and therefore
63+
* the test) if it is.
64+
*
65+
* @beforeClass
66+
*
67+
* @return void
68+
*/
69+
public static function setUpTestFile()
70+
{
71+
// Skip the tokenizing of the test case file at this time.
72+
}
73+
74+
/**
75+
* Initialize PHPCS & tokenize the test case file on compatible PHPCS versions.
76+
*
77+
* Skip this test on PHPCS versions on which the Tokenizer will hang.
78+
*
79+
* @before
80+
*
81+
* @return void
82+
*/
83+
public function setUpTestFileForReal()
84+
{
85+
$phpcsVersion = Helper::getVersion();
86+
87+
if (isset($this->unsupportedPHPCSVersions[$phpcsVersion]) === true) {
88+
$this->markTestSkipped("Issue 2926 can not be tested on PHPCS $phpcsVersion as the Tokenizer will hang.");
89+
}
90+
91+
if (self::$tokenized === false) {
92+
parent::setUpTestFile();
93+
self::$tokenized = true;
94+
}
95+
}
96+
97+
/**
98+
* Test correctly detecting arrow functions.
99+
*
100+
* @dataProvider dataArrowFunction
101+
*
102+
* @param string $testMarker The comment which prefaces the target token in the test file.
103+
* @param array $expected The expected return value for the respective functions.
104+
* @param array $targetContent The content for the target token to look for in case there could
105+
* be confusion.
106+
*
107+
* @return void
108+
*/
109+
public function testIsArrowFunction($testMarker, $expected, $targetContent = null)
110+
{
111+
$targets = Collections::arrowFunctionTokensBC();
112+
$stackPtr = $this->getTargetToken($testMarker, $targets, $targetContent);
113+
$result = FunctionDeclarations::isArrowFunction(self::$phpcsFile, $stackPtr);
114+
$this->assertSame($expected['is'], $result);
115+
}
116+
117+
/**
118+
* Test correctly detecting arrow functions.
119+
*
120+
* @dataProvider dataArrowFunction
121+
*
122+
* @param string $testMarker The comment which prefaces the target token in the test file.
123+
* @param array $expected The expected return value for the respective functions.
124+
* @param string $targetContent The content for the target token to look for in case there could
125+
* be confusion.
126+
*
127+
* @return void
128+
*/
129+
public function testGetArrowFunctionOpenClose($testMarker, $expected, $targetContent = 'fn')
130+
{
131+
$targets = Collections::arrowFunctionTokensBC();
132+
$stackPtr = $this->getTargetToken($testMarker, $targets, $targetContent);
133+
134+
// Change from offsets to absolute token positions.
135+
if ($expected['get'] != false) {
136+
foreach ($expected['get'] as $key => $value) {
137+
$expected['get'][$key] += $stackPtr;
138+
}
139+
}
140+
141+
$result = FunctionDeclarations::getArrowFunctionOpenClose(self::$phpcsFile, $stackPtr);
142+
$this->assertSame($expected['get'], $result);
143+
}
144+
145+
/**
146+
* Data provider.
147+
*
148+
* @see testIsArrowFunction() For the array format.
149+
* @see testgetArrowFunctionOpenClose() For the array format.
150+
*
151+
* @return array
152+
*/
153+
public function dataArrowFunction()
154+
{
155+
return [
156+
'arrow-function-returning-heredoc' => [
157+
'/* testHeredoc */',
158+
[
159+
'is' => true,
160+
'get' => [
161+
'parenthesis_opener' => 1,
162+
'parenthesis_closer' => 2,
163+
'scope_opener' => 4,
164+
'scope_closer' => 9,
165+
],
166+
],
167+
],
168+
'arrow-function-returning-nowdoc' => [
169+
'/* testNowdoc */',
170+
[
171+
'is' => true,
172+
'get' => [
173+
'parenthesis_opener' => 1,
174+
'parenthesis_closer' => 2,
175+
'scope_opener' => 4,
176+
'scope_closer' => 9,
177+
],
178+
],
179+
],
180+
];
181+
}
182+
}

0 commit comments

Comments
 (0)