Skip to content

Commit ecdcbed

Browse files
authored
Merge pull request #212 from magento-l3/ACP2E-1868
ACP2E-1868: Move sniff PHPCompatibility.TextStrings.RemovedDollarBraceStringEmbeds from PHPCompatibility to Magento Coding Standards
2 parents 7465837 + 32d4ded commit ecdcbed

5 files changed

+521
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/**
3+
* PHPCompatibility, an external standard for PHP_CodeSniffer.
4+
*
5+
* @package PHPCompatibility
6+
* @copyright 2012-2022 PHPCompatibility Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCompatibility/PHPCompatibility
9+
*/
10+
11+
namespace Magento2\Sniffs\PHPCompatibility;
12+
13+
use PHP_CodeSniffer\Files\File;
14+
use PHPCompatibility\Sniff;
15+
use PHPCSUtils\Utils\GetTokensAsString;
16+
use PHPCSUtils\Utils\TextStrings;
17+
18+
/**
19+
* Detect use of select forms of variable embedding in heredocs and double strings as deprecated per PHP 8.2.
20+
*
21+
* > PHP allows embedding variables in strings with double-quotes (") and heredoc in various ways.
22+
* > 1. Directly embedding variables (`$foo`)
23+
* > 2. Braces outside the variable (`{$foo}`)
24+
* > 3. Braces after the dollar sign (`${foo}`)
25+
* > 4. Variable variables (`${expr}`, equivalent to `(string) ${expr}`)
26+
* >
27+
* > [...] to deprecate options 3 and 4 in PHP 8.2 and remove them in PHP 9.0.
28+
*
29+
* PHP version 8.2
30+
* PHP version 9.0
31+
*
32+
* @link https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation
33+
*
34+
* @since 10.0.0
35+
*/
36+
class RemovedDollarBraceStringEmbedsSniff extends Sniff
37+
{
38+
39+
/**
40+
* Returns an array of tokens this test wants to listen for.
41+
*
42+
* @since 10.0.0
43+
*
44+
* @return array
45+
*/
46+
public function register()
47+
{
48+
return [
49+
\T_DOUBLE_QUOTED_STRING,
50+
\T_START_HEREDOC,
51+
\T_DOLLAR_OPEN_CURLY_BRACES,
52+
];
53+
}
54+
55+
/**
56+
* Processes this test, when one of its tokens is encountered.
57+
*
58+
* @since 10.0.0
59+
*
60+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
61+
* @param int $stackPtr The position of the current token in the
62+
* stack passed in $tokens.
63+
*
64+
* @return void|int Void or a stack pointer to skip forward.
65+
*/
66+
public function process(File $phpcsFile, $stackPtr)
67+
{
68+
if ($this->supportsAbove('8.2') === false) {
69+
return;
70+
}
71+
72+
$tokens = $phpcsFile->getTokens();
73+
74+
/*
75+
* Defensive coding, this code is not expected to ever actually be hit since PHPCS#3604
76+
* (included in 3.7.0), but _will_ be hit if a file containing a PHP 7.3 indented heredoc/nowdocs
77+
* is scanned with PHPCS on PHP < 7.3. People shouldn't do that, but hey, we can't stop them.
78+
*/
79+
if ($tokens[$stackPtr]['code'] === \T_DOLLAR_OPEN_CURLY_BRACES) {
80+
// @codeCoverageIgnoreStart
81+
if ($tokens[($stackPtr - 1)]['code'] === \T_DOUBLE_QUOTED_STRING) {
82+
--$stackPtr;
83+
} else {
84+
// Throw an error anyway, though it won't be very informative.
85+
$message = 'Using ${} in strings is deprecated since PHP 8.2, use {$var} or {${expr}} instead.';
86+
$code = 'DeprecatedDollarBraceEmbed';
87+
$phpcsFile->addWarning($message, $stackPtr, $code);
88+
return;
89+
}
90+
// @codeCoverageIgnoreEnd
91+
}
92+
93+
$endOfString = TextStrings::getEndOfCompleteTextString($phpcsFile, $stackPtr);
94+
$startOfString = $stackPtr;
95+
if ($tokens[$stackPtr]['code'] === \T_START_HEREDOC) {
96+
$startOfString = ($stackPtr + 1);
97+
}
98+
99+
$contents = GetTokensAsString::normal($phpcsFile, $startOfString, $endOfString);
100+
if (\strpos($contents, '${') === false) {
101+
// No interpolation found or only interpolations which are still supported.
102+
return ($endOfString + 1);
103+
}
104+
105+
$embeds = TextStrings::getEmbeds($contents);
106+
foreach ($embeds as $offset => $embed) {
107+
if (\strpos($embed, '${') !== 0) {
108+
continue;
109+
}
110+
111+
// Figure out the stack pointer to throw the warning on.
112+
$errorPtr = $startOfString;
113+
$length = 0;
114+
while (($length + $tokens[$errorPtr]['length']) < $offset) {
115+
$length += $tokens[$errorPtr]['length'];
116+
++$errorPtr;
117+
}
118+
119+
// Type 4.
120+
$message = 'Using %s (variable variables) in strings is deprecated since PHP 8.2, use {${expr}} instead.';
121+
$code = 'DeprecatedExpressionSyntax';
122+
if (\preg_match('`^\$\{(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+)(?:\[([\'"])?[^\$\{\}\]]+(?:\2)?\])?\}$`', $embed) === 1) {
123+
// Type 3.
124+
$message = 'Using ${var} in strings is deprecated since PHP 8.2, use {$var} instead. Found: %s';
125+
$code = 'DeprecatedVariableSyntax';
126+
}
127+
128+
$phpcsFile->addWarning($message, $errorPtr, $code, [$embed]);
129+
130+
}
131+
132+
return ($endOfString + 1);
133+
}
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
/*
4+
* Embedded variables which are supported cross-version.
5+
*/
6+
7+
// Type 1: directly embedding variables.
8+
echo "$foo";
9+
echo "$$foo";
10+
echo "$foo[bar]";
11+
echo "$foo->bar";
12+
$text = "some text $var some text";
13+
14+
$heredoc = <<<EOD
15+
some text $var some text
16+
EOD;
17+
18+
// Type 2: Braces around the variable/expression.
19+
echo "{$foo}";
20+
echo "{$$foo}";
21+
echo "{$foo['bar']}";
22+
echo "{$foo->bar}";
23+
echo "{$foo->bar()}";
24+
echo "{$foo['bar']->baz()()}";
25+
echo "{${$bar}}";
26+
echo "{$foo()}";
27+
echo "{${$object->getMethod()}}"
28+
$text = "some text {$var} some text";
29+
30+
$heredoc = <<<"EOD"
31+
some text {$var} some text
32+
EOD;
33+
34+
/*
35+
* Not our target.
36+
*/
37+
38+
// Ordinary variable variables outside strings.
39+
$foo = ${'bar'};
40+
41+
// Heredoc without embeds.
42+
echo <<<EOD
43+
Some text
44+
EOD;
45+
46+
// Not actually interpolated - $ is escaped. The second $foo is to force T_DOUBLE_QUOTED_STRING tokenization.
47+
echo "\${foo} and $foo";
48+
echo "\${foo[\"bar\"]} and $foo";
49+
echo "$\{foo} and $foo";
50+
51+
52+
/*
53+
* PHP 8.2: deprecated forms of embedding variables.
54+
*/
55+
56+
// Type 3: Braces after the dollar sign.
57+
echo "${foo}";
58+
echo "${foo['bar']}";
59+
$text = "some text ${foo} some ${text}";
60+
61+
$heredoc = <<<EOD
62+
some text ${foo} some text
63+
EOD;
64+
65+
echo "\\${foo}"; // Not actually escaped, the backslash escapes the backslash, not the dollar sign.
66+
67+
// Type 4: Variable variables.
68+
echo "${$bar}";
69+
echo "${(foo)}";
70+
echo "${foo->bar}";
71+
echo "${$object->getMethod()}"
72+
$text = "some text ${(foo)} some text";
73+
echo "${substr('laruence', 0, 2)}";
74+
75+
echo "${foo["${bar}"]}";
76+
echo "${foo["${bar['baz']}"]}";
77+
echo "${foo->{$baz}}";
78+
echo "${foo->{${'a'}}}";
79+
echo "${foo->{"${'a'}"}}";
80+
81+
// Verify correct handling of stack pointers in multi-token code.
82+
$text = "Line without embed
83+
some text ${foo["${bar}"]} some text
84+
some text ${foo["${bar['baz']}"]} some text
85+
some text ${foo->{${'a'}}} some text
86+
";
87+
88+
$heredoc = <<<"EOD"
89+
some text ${(foo)} some text
90+
EOD;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/**
4+
* The tests involving PHP 7.3+ indented heredocs are in a separate test case file
5+
* as any code after an indented heredoc will be tokenizer garbage on PHP < 7.3.
6+
*/
7+
8+
// No embeds.
9+
$php73IndentedHeredoc = <<<"EOD"
10+
some text some text
11+
EOD;
12+
13+
/*
14+
* Embedded variables which are supported cross-version.
15+
*/
16+
17+
// Type 1: directly embedding variables.
18+
$php73IndentedHeredoc = <<<"EOD"
19+
some text $foo[bar] some text
20+
EOD;
21+
22+
// Type 2: Braces around the variable/expression.
23+
$php73IndentedHeredoc = <<<EOD
24+
some text {${$bar}} some text
25+
EOD;
26+
27+
/*
28+
* PHP 8.2: deprecated forms of embedding variables.
29+
*/
30+
31+
// Type 3: Braces after the dollar sign.
32+
$php73IndentedHeredoc = <<<"EOD"
33+
some text ${foo['bar']} some text
34+
EOD;
35+
36+
// Type 4: Variable variables.
37+
$php73IndentedHeredoc = <<<EOD
38+
Line without embed
39+
some text ${$object->getMethod()} some text
40+
some text ${foo["${bar['baz']}"]} some text
41+
some text ${foo->{${'a'}}} some text
42+
EOD;

0 commit comments

Comments
 (0)