Skip to content

Commit af87461

Browse files
committed
SlevomatCodingStandard.Strings.DisallowVariableParsing: Fixed false positives
1 parent cf10256 commit af87461

File tree

3 files changed

+122
-48
lines changed

3 files changed

+122
-48
lines changed

SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
use PHP_CodeSniffer\Files\File;
66
use PHP_CodeSniffer\Sniffs\Sniff;
77
use UnexpectedValueException;
8-
use function preg_match;
8+
use function count;
9+
use function in_array;
10+
use function is_array;
911
use function sprintf;
12+
use function strpos;
13+
use function token_get_all;
1014
use const T_DOUBLE_QUOTED_STRING;
1115
use const T_HEREDOC;
16+
use const T_VARIABLE;
1217

1318
class DisallowVariableParsingSniff implements Sniff
1419
{
@@ -19,10 +24,6 @@ class DisallowVariableParsingSniff implements Sniff
1924

2025
public const CODE_DISALLOWED_SIMPLE_SYNTAX = 'DisallowedSimpleSyntax';
2126

22-
private const DOLLAR_CURLY_SYNTAX_PATTERN = '~\${[\w\[\]]+}~';
23-
private const CURLY_DOLLAR_SYNTAX_PATTERN = '~{\$[\w\[\]\->]+}~';
24-
private const SIMPLE_SYNTAX_PATTERN = '~(?<!{|\[)\$[\w\[\]\->]+(?!})~';
25-
2627
/** @var bool */
2728
public $disallowDollarCurlySyntax = true;
2829

@@ -56,48 +57,98 @@ public function process(File $phpcsFile, $stringPointer): void
5657
$tokens = $phpcsFile->getTokens();
5758
$tokenContent = $tokens[$stringPointer]['content'];
5859

59-
// Cover strings where ${...} syntax is used
60-
if ($this->disallowDollarCurlySyntax && preg_match(self::DOLLAR_CURLY_SYNTAX_PATTERN, $tokenContent, $invalidFragments) === 1) {
61-
foreach ($invalidFragments as $fragment) {
62-
$phpcsFile->addError(
63-
sprintf(
64-
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "%s".',
65-
$fragment
66-
),
67-
$stringPointer,
68-
self::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX
69-
);
70-
}
60+
if (strpos($tokenContent, '$') === false) {
61+
return;
7162
}
7263

73-
// Cover strings where {$...} syntax is used
74-
if ($this->disallowCurlyDollarSyntax && preg_match(self::CURLY_DOLLAR_SYNTAX_PATTERN, $tokenContent, $invalidFragments) === 1) {
75-
foreach ($invalidFragments as $fragment) {
76-
$phpcsFile->addError(
77-
sprintf(
78-
'Using variable syntax "{$...}" inside string is disallowed, found "%s".',
79-
$fragment
80-
),
81-
$stringPointer,
82-
self::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX
83-
);
64+
$stringTokens = $tokens[$stringPointer]['code'] === T_HEREDOC
65+
? token_get_all('<?php "' . $tokenContent . '"')
66+
: token_get_all('<?php ' . $tokenContent);
67+
68+
for ($i = 0; $i < count($stringTokens); $i++) {
69+
$stringToken = $stringTokens[$i];
70+
71+
if (!is_array($stringToken)) {
72+
continue;
8473
}
85-
}
8674

87-
// Cover strings where $... syntax is used
88-
// phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
89-
if ($this->disallowSimpleSyntax && preg_match(self::SIMPLE_SYNTAX_PATTERN, $tokenContent, $invalidFragments) === 1) {
90-
foreach ($invalidFragments as $fragment) {
91-
$phpcsFile->addError(
92-
sprintf(
93-
'Using variable syntax "$..." inside string is disallowed, found "%s".',
94-
$fragment
95-
),
96-
$stringPointer,
97-
self::CODE_DISALLOWED_SIMPLE_SYNTAX
98-
);
75+
if ($this->disallowDollarCurlySyntax && $this->getTokenContent($stringToken) === '${') {
76+
$usedVariable = $stringToken[1];
77+
78+
for ($j = $i + 1; $j < count($stringTokens); $j++) {
79+
$usedVariable .= $this->getTokenContent($stringTokens[$j]);
80+
81+
if ($this->getTokenContent($stringTokens[$j]) === '}') {
82+
$phpcsFile->addError(
83+
sprintf(
84+
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "%s".',
85+
$usedVariable
86+
),
87+
$stringPointer,
88+
self::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX
89+
);
90+
91+
break;
92+
}
93+
}
94+
} elseif ($stringToken[0] === T_VARIABLE) {
95+
if ($this->disallowCurlyDollarSyntax && $this->getTokenContent($stringTokens[$i - 1]) === '{') {
96+
$usedVariable = $stringToken[1];
97+
98+
for ($j = $i + 1; $j < count($stringTokens); $j++) {
99+
$stringTokenContent = $this->getTokenContent($stringTokens[$j]);
100+
if ($stringTokenContent === '}') {
101+
break;
102+
}
103+
104+
$usedVariable .= $stringTokenContent;
105+
}
106+
107+
$phpcsFile->addError(
108+
sprintf(
109+
'Using variable syntax "{$...}" inside string is disallowed, found "{%s}".',
110+
$usedVariable
111+
),
112+
$stringPointer,
113+
self::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX
114+
);
115+
} elseif ($this->disallowSimpleSyntax) {
116+
$error = true;
117+
118+
for ($j = $i - 1; $j >= 0; $j--) {
119+
$stringTokenContent = $this->getTokenContent($stringTokens[$j]);
120+
121+
if (in_array($stringTokenContent, ['{', '${'], true)) {
122+
$error = false;
123+
break;
124+
}
125+
126+
if ($stringTokenContent === '}') {
127+
break;
128+
}
129+
}
130+
131+
if ($error) {
132+
$phpcsFile->addError(
133+
sprintf(
134+
'Using variable syntax "$..." inside string is disallowed, found "%s".',
135+
$this->getTokenContent($stringToken)
136+
),
137+
$stringPointer,
138+
self::CODE_DISALLOWED_SIMPLE_SYNTAX
139+
);
140+
}
141+
}
99142
}
100143
}
101144
}
102145

146+
/**
147+
* @param array{0: int, 1: string}|string $token
148+
*/
149+
private function getTokenContent($token): string
150+
{
151+
return is_array($token) ? $token[1] : $token;
152+
}
153+
103154
}

tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public function testErrorsCurlyDollarSyntax(): void
6767
]
6868
);
6969

70-
self::assertSame(6, $report->getErrorCount());
70+
self::assertSame(8, $report->getErrorCount());
7171

7272
self::assertSniffError(
7373
$report,
@@ -110,6 +110,20 @@ public function testErrorsCurlyDollarSyntax(): void
110110
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
111111
'Using variable syntax "{$...}" inside string is disallowed, found "{$object->name}".'
112112
);
113+
114+
self::assertSniffError(
115+
$report,
116+
51,
117+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
118+
'Using variable syntax "{$...}" inside string is disallowed, found "{$array[$simpleString]}".'
119+
);
120+
121+
self::assertSniffError(
122+
$report,
123+
53,
124+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
125+
'Using variable syntax "{$...}" inside string is disallowed, found "{$a->test($b)}".'
126+
);
113127
}
114128

115129
public function testErrorsSimpleSyntax(): void
@@ -123,7 +137,7 @@ public function testErrorsSimpleSyntax(): void
123137
]
124138
);
125139

126-
self::assertSame(6, $report->getErrorCount());
140+
self::assertSame(7, $report->getErrorCount());
127141

128142
self::assertSniffError(
129143
$report,
@@ -136,14 +150,14 @@ public function testErrorsSimpleSyntax(): void
136150
$report,
137151
38,
138152
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
139-
'Using variable syntax "$..." inside string is disallowed, found "$array[1]".'
153+
'Using variable syntax "$..." inside string is disallowed, found "$array".'
140154
);
141155

142156
self::assertSniffError(
143157
$report,
144158
39,
145159
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
146-
'Using variable syntax "$..." inside string is disallowed, found "$object->name".'
160+
'Using variable syntax "$..." inside string is disallowed, found "$object".'
147161
);
148162

149163
self::assertSniffError(
@@ -157,14 +171,21 @@ public function testErrorsSimpleSyntax(): void
157171
$report,
158172
47,
159173
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
160-
'Using variable syntax "$..." inside string is disallowed, found "$array[1]".'
174+
'Using variable syntax "$..." inside string is disallowed, found "$array".'
161175
);
162176

163177
self::assertSniffError(
164178
$report,
165179
48,
166180
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
167-
'Using variable syntax "$..." inside string is disallowed, found "$object->name".'
181+
'Using variable syntax "$..." inside string is disallowed, found "$object".'
182+
);
183+
184+
self::assertSniffError(
185+
$report,
186+
51,
187+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
188+
'Using variable syntax "$..." inside string is disallowed, found "$simpleString".'
168189
);
169190
}
170191

tests/Sniffs/Strings/data/disallowVariableParsingErrors.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@
4848
Some heredoc line with object variable $object->name
4949
EOT;
5050

51-
"{$array[$simpleString]}";
51+
"{$array[$simpleString]} $simpleString";
52+
53+
"{$a->test($b)}";

0 commit comments

Comments
 (0)