Skip to content

Commit 91ab3d3

Browse files
Ekimikkukulich
authored andcommitted
SlevomatCodingStandard.Strings.DisallowVariableParsing: New sniff to disallow variable parsing inside strings
1 parent 81b4d3a commit 91ab3d3

File tree

6 files changed

+355
-0
lines changed

6 files changed

+355
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Slevomat Coding Standard for [PHP_CodeSniffer](https://github.com/squizlabs/PHP_
170170
- [SlevomatCodingStandard.PHP.TypeCast](doc/php.md#slevomatcodingstandardphptypecast-) 🔧
171171
- [SlevomatCodingStandard.PHP.UselessParentheses](doc/php.md#slevomatcodingstandardphpuselessparentheses-) 🔧
172172
- [SlevomatCodingStandard.PHP.UselessSemicolon](doc/php.md#slevomatcodingstandardphpuselesssemicolon-) 🔧
173+
- [SlevomatCodingStandard.Strings.DisallowVariableParsing](doc/strings.md#slevomatcodingstandardstringsdisallowvariableparsing)
173174
- [SlevomatCodingStandard.TypeHints.DeclareStrictTypes](doc/type-hints.md#slevomatcodingstandardtypehintsdeclarestricttypes-) 🔧
174175
- [SlevomatCodingStandard.TypeHints.DisallowArrayTypeHintSyntax](doc/type-hints.md#slevomatcodingstandardtypehintsdisallowarraytypehintsyntax-) 🔧
175176
- [SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint](doc/type-hints.md#slevomatcodingstandardtypehintsdisallowmixedtypehint)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Strings;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
use PHP_CodeSniffer\Sniffs\Sniff;
7+
use UnexpectedValueException;
8+
use function preg_match;
9+
use function sprintf;
10+
use const T_DOUBLE_QUOTED_STRING;
11+
use const T_HEREDOC;
12+
13+
class DisallowVariableParsingSniff implements Sniff
14+
{
15+
16+
public const CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX = 'DisallowedDollarCurlySyntax';
17+
18+
public const CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX = 'DisallowedCurlyDollarSyntax';
19+
20+
public const CODE_DISALLOWED_SIMPLE_SYNTAX = 'DisallowedSimpleSyntax';
21+
22+
private const DOLLAR_CURLY_SYNTAX_PATTERN = '~\${[\w\[\]]+}~';
23+
private const CURLY_DOLLAR_SYNTAX_PATTERN = '~{\$[\w\[\]\->]+}~';
24+
private const SIMPLE_SYNTAX_PATTERN = '~(?<!{)\$[\w\[\]\->]+(?!})~';
25+
26+
/** @var bool */
27+
public $disallowDollarCurlySyntax = true;
28+
29+
/** @var bool */
30+
public $disallowCurlyDollarSyntax = false;
31+
32+
/** @var bool */
33+
public $disallowSimpleSyntax = false;
34+
35+
/**
36+
* @return array<int, (int|string)>
37+
*/
38+
public function register(): array
39+
{
40+
return [
41+
T_DOUBLE_QUOTED_STRING,
42+
T_HEREDOC,
43+
];
44+
}
45+
46+
/**
47+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
48+
* @param int $stringPointer
49+
*/
50+
public function process(File $phpcsFile, $stringPointer): void
51+
{
52+
if (!$this->disallowDollarCurlySyntax && !$this->disallowCurlyDollarSyntax && !$this->disallowSimpleSyntax) {
53+
throw new UnexpectedValueException('No option is set.');
54+
}
55+
56+
$tokens = $phpcsFile->getTokens();
57+
$tokenContent = $tokens[$stringPointer]['content'];
58+
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+
}
71+
}
72+
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+
);
84+
}
85+
}
86+
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+
);
99+
}
100+
}
101+
}
102+
103+
}

doc/strings.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Strings
2+
3+
#### SlevomatCodingStandard.Strings.DisallowVariableParsing
4+
5+
Disallows variable parsing inside strings.
6+
7+
Sniff provides the following settings:
8+
9+
* `disallowDollarCurlySyntax`: disallows usage of `${...}`, enabled by default.
10+
* `disallowCurlyDollarSyntax`: disallows usage of `{$...}`, disabled by default.
11+
* `disallowSimpleSyntax`: disallows usage of `$...`, disabled by default.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Strings;
4+
5+
use SlevomatCodingStandard\Sniffs\TestCase;
6+
use UnexpectedValueException;
7+
8+
class DisallowVariableParsingSniffTest extends TestCase
9+
{
10+
11+
public function testNoErrors(): void
12+
{
13+
$report = self::checkFile(__DIR__ . '/data/disallowVariableParsingNoError.php');
14+
self::assertNoSniffErrorInFile($report);
15+
}
16+
17+
public function testErrorsDollarCurlySyntax(): void
18+
{
19+
$report = self::checkFile(
20+
__DIR__ . '/data/disallowVariableParsingErrors.php',
21+
[
22+
'disallowDollarCurlySyntax' => true,
23+
'disallowCurlyDollarSyntax' => false,
24+
'disallowSimpleSyntax' => false,
25+
]
26+
);
27+
28+
self::assertSame(4, $report->getErrorCount());
29+
30+
self::assertSniffError(
31+
$report,
32+
10,
33+
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
34+
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${simpleString}".'
35+
);
36+
37+
self::assertSniffError(
38+
$report,
39+
11,
40+
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
41+
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${array[1]}".'
42+
);
43+
44+
self::assertSniffError(
45+
$report,
46+
17,
47+
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
48+
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${simpleString}".'
49+
);
50+
51+
self::assertSniffError(
52+
$report,
53+
18,
54+
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
55+
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${array[1]}".'
56+
);
57+
}
58+
59+
public function testErrorsCurlyDollarSyntax(): void
60+
{
61+
$report = self::checkFile(
62+
__DIR__ . '/data/disallowVariableParsingErrors.php',
63+
[
64+
'disallowCurlyDollarSyntax' => true,
65+
'disallowDollarCurlySyntax' => false,
66+
'disallowSimpleSyntax' => false,
67+
]
68+
);
69+
70+
self::assertSame(6, $report->getErrorCount());
71+
72+
self::assertSniffError(
73+
$report,
74+
22,
75+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
76+
'Using variable syntax "{$...}" inside string is disallowed, found "{$simpleString}".'
77+
);
78+
79+
self::assertSniffError(
80+
$report,
81+
23,
82+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
83+
'Using variable syntax "{$...}" inside string is disallowed, found "{$array[1]}".'
84+
);
85+
86+
self::assertSniffError(
87+
$report,
88+
24,
89+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
90+
'Using variable syntax "{$...}" inside string is disallowed, found "{$object->name}".'
91+
);
92+
93+
self::assertSniffError(
94+
$report,
95+
31,
96+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
97+
'Using variable syntax "{$...}" inside string is disallowed, found "{$simpleString}".'
98+
);
99+
100+
self::assertSniffError(
101+
$report,
102+
32,
103+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
104+
'Using variable syntax "{$...}" inside string is disallowed, found "{$array[1]}".'
105+
);
106+
107+
self::assertSniffError(
108+
$report,
109+
33,
110+
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
111+
'Using variable syntax "{$...}" inside string is disallowed, found "{$object->name}".'
112+
);
113+
}
114+
115+
public function testErrorsSimpleSyntax(): void
116+
{
117+
$report = self::checkFile(
118+
__DIR__ . '/data/disallowVariableParsingErrors.php',
119+
[
120+
'disallowSimpleSyntax' => true,
121+
'disallowDollarCurlySyntax' => false,
122+
'disallowCurlyDollarSyntax' => false,
123+
]
124+
);
125+
126+
self::assertSame(6, $report->getErrorCount());
127+
128+
self::assertSniffError(
129+
$report,
130+
37,
131+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
132+
'Using variable syntax "$..." inside string is disallowed, found "$simpleString".'
133+
);
134+
135+
self::assertSniffError(
136+
$report,
137+
38,
138+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
139+
'Using variable syntax "$..." inside string is disallowed, found "$array[1]".'
140+
);
141+
142+
self::assertSniffError(
143+
$report,
144+
39,
145+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
146+
'Using variable syntax "$..." inside string is disallowed, found "$object->name".'
147+
);
148+
149+
self::assertSniffError(
150+
$report,
151+
46,
152+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
153+
'Using variable syntax "$..." inside string is disallowed, found "$simpleString".'
154+
);
155+
156+
self::assertSniffError(
157+
$report,
158+
47,
159+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
160+
'Using variable syntax "$..." inside string is disallowed, found "$array[1]".'
161+
);
162+
163+
self::assertSniffError(
164+
$report,
165+
48,
166+
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
167+
'Using variable syntax "$..." inside string is disallowed, found "$object->name".'
168+
);
169+
}
170+
171+
public function testNoOptionIsSet(): void
172+
{
173+
$this->expectException(UnexpectedValueException::class);
174+
$this->expectExceptionMessage('No option is set.');
175+
176+
self::checkFile(__DIR__ . '/data/disallowVariableParsingNoError.php', [
177+
'disallowDollarCurlySyntax' => false,
178+
'disallowCurlyDollarSyntax' => false,
179+
'disallowSimpleSyntax' => false,
180+
]);
181+
}
182+
183+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
$simpleString = 'foo';
4+
$array = ['foo', 'bar', 'baz'];
5+
6+
$object = new \stdClass();
7+
$object->name = 'foobar';
8+
9+
// Covers strings where ${...} syntax is used
10+
$dollarCurlySyntaxDoubleQuotedScalar = "Some double quoted string with scalar variable ${simpleString}.";
11+
$dollarCurlySyntaxDoubleQuotedArrayItem = "Some double quoted string with array item variable ${array[1]}.";
12+
13+
$dollarCurlySyntaxSingleQuotedScalar = 'Some single quoted string with scalar variable ${simpleString}.';
14+
$dollarCurlySyntaxSingleQuotedArrayItem = 'Some single quoted string with array item variable ${array[1]}.';
15+
16+
$dollarCurlySyntaxHereDoc = <<<EOT
17+
Some heredoc line with scalar variable ${simpleString}
18+
Some heredoc line with array item variable ${array[1]}
19+
EOT;
20+
21+
// Covers strings where {$...} syntax is used
22+
$curlyDollarSyntaxDoubleQuotedScalar = "Some double quoted string with scalar variable {$simpleString}.";
23+
$curlyDollarSyntaxDoubleQuotedArrayItem = "Some double quoted string with array item variable {$array[1]}.";
24+
$curlyDollarSyntaxDoubleQuotedObject = "Some double quoted string with object variable {$object->name}.";
25+
26+
$curlyDollarSyntaxSingleQuotedScalar = 'Some single quoted string with scalar variable {$simpleString}.';
27+
$curlyDollarSyntaxSingleQuotedArrayItem = 'Some single quoted string with array item variable {$array[1]}.';
28+
$curlyDollarSyntaxSingleQuotedObject = 'Some single quoted string with object variable {$object->name}.';
29+
30+
$curlyDollarSyntaxHereDoc = <<<EOT
31+
Some heredoc line with scalar variable {$simpleString}
32+
Some heredoc line with array item variable {$array[1]}
33+
Some heredoc line with object variable {$object->name}
34+
EOT;
35+
36+
// Covers strings where $... syntax is used
37+
$simpleSyntaxDoubleQuotedScalar = "Some double quoted string with scalar variable $simpleString.";
38+
$simpleSyntaxDoubleQuotedArrayItem = "Some double quoted string with array item variable $array[1].";
39+
$simpleSyntaxDoubleQuotedObject = "Some double quoted string with object variable $object->name.";
40+
41+
$simpleSyntaxSingleQuotedScalar = 'Some single quoted string with scalar variable $simpleString.';
42+
$simpleSyntaxSingleQuotedArrayItem = 'Some single quoted string with array item variable $array[1].';
43+
$simpleSyntaxSingleQuotedObject = 'Some single quoted string with object variable $object->name.';
44+
45+
$simpleSyntaxHereDoc = <<<EOT
46+
Some heredoc line with scalar variable $simpleString
47+
Some heredoc line with array item variable $array[1]
48+
Some heredoc line with object variable $object->name
49+
EOT;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
$noVariablesDoubleQuoted = "Some double quoted string without variables";
4+
$noVariablesSingleQuoted = 'Some single quoted string without variables';
5+
6+
$noVariablesHereDoc = <<<EOT
7+
Some heredoc line without variables
8+
EOT;

0 commit comments

Comments
 (0)