Skip to content

Commit c4e213e

Browse files
committed
SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch: New sniff that disallows string expression property fetch $object->{'foo'}
1 parent 91ab3d3 commit c4e213e

File tree

7 files changed

+150
-0
lines changed

7 files changed

+150
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Slevomat Coding Standard for [PHP_CodeSniffer](https://github.com/squizlabs/PHP_
5050
- [SlevomatCodingStandard.Classes.DisallowLateStaticBindingForConstants](doc/classes.md#slevomatcodingstandardclassesdisallowlatestaticbindingforconstants-) 🔧
5151
- [SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition](doc/classes.md#slevomatcodingstandardclassesdisallowmulticonstantdefinition-) 🔧
5252
- [SlevomatCodingStandard.Classes.DisallowMultiPropertyDefinition](doc/classes.md#slevomatcodingstandardclassesdisallowmultipropertydefinition-) 🔧
53+
- [SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch](doc/classes.md#slevomatcodingstandardclassesdisallowstringexpressionpropertyfetch-) 🔧
5354
- [SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces](doc/classes.md#slevomatcodingstandardclassesemptylinesaroundclassbraces-) 🔧
5455
- [SlevomatCodingStandard.Classes.EnumCaseSpacing](doc/classes.md#slevomatcodingstandardclassesenumcasespacing-) 🔧
5556
- [SlevomatCodingStandard.Classes.ForbiddenPublicProperty](doc/classes.md#slevomatcodingstandardclassesforbiddenpublicproperty)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Classes;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
use PHP_CodeSniffer\Sniffs\Sniff;
7+
use SlevomatCodingStandard\Helpers\FixerHelper;
8+
use SlevomatCodingStandard\Helpers\TokenHelper;
9+
use function preg_match;
10+
use const T_CONSTANT_ENCAPSED_STRING;
11+
use const T_OBJECT_OPERATOR;
12+
use const T_OPEN_CURLY_BRACKET;
13+
use const T_OPEN_PARENTHESIS;
14+
15+
class DisallowStringExpressionPropertyFetchSniff implements Sniff
16+
{
17+
18+
public const CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH = 'DisallowedStringExpressionPropertyFetch';
19+
20+
/**
21+
* @return array<int, (int|string)>
22+
*/
23+
public function register(): array
24+
{
25+
return [T_OBJECT_OPERATOR];
26+
}
27+
28+
/**
29+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
30+
* @param int $objectOperatorPointer
31+
*/
32+
public function process(File $phpcsFile, $objectOperatorPointer): void
33+
{
34+
$tokens = $phpcsFile->getTokens();
35+
36+
$curlyBracketOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $objectOperatorPointer + 1);
37+
38+
if ($tokens[$curlyBracketOpenerPointer]['code'] !== T_OPEN_CURLY_BRACKET) {
39+
return;
40+
}
41+
42+
$curlyBracketCloserPointer = $tokens[$curlyBracketOpenerPointer]['bracket_closer'];
43+
44+
if (TokenHelper::findNextExcluding(
45+
$phpcsFile,
46+
T_CONSTANT_ENCAPSED_STRING,
47+
$curlyBracketOpenerPointer + 1,
48+
$curlyBracketCloserPointer
49+
) !== null) {
50+
return;
51+
}
52+
53+
$pointerAfterCurlyBracketCloser = TokenHelper::findNextEffective($phpcsFile, $curlyBracketCloserPointer + 1);
54+
55+
if ($tokens[$pointerAfterCurlyBracketCloser]['code'] === T_OPEN_PARENTHESIS) {
56+
return;
57+
}
58+
59+
if (preg_match(
60+
'~^(["\'])([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\1$~',
61+
$tokens[$curlyBracketOpenerPointer + 1]['content'],
62+
$matches
63+
) !== 1) {
64+
return;
65+
}
66+
67+
$fix = $phpcsFile->addFixableError(
68+
'String expression property fetch is disallowed, use identifier property fetch.',
69+
$curlyBracketOpenerPointer,
70+
self::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH
71+
);
72+
73+
if (!$fix) {
74+
return;
75+
}
76+
77+
$phpcsFile->fixer->beginChangeset();
78+
79+
$phpcsFile->fixer->replaceToken($curlyBracketOpenerPointer, $matches[2]);
80+
FixerHelper::removeBetweenIncluding($phpcsFile, $curlyBracketOpenerPointer + 1, $curlyBracketCloserPointer);
81+
82+
$phpcsFile->fixer->endChangeset();
83+
}
84+
85+
}

doc/classes.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ Disallows multi constant definition.
113113

114114
Disallows multi property definition.
115115

116+
#### SlevomatCodingStandard.Classes.DisallowMultiPropertyDefinition 🔧
117+
118+
Disallows multi property definition.
119+
120+
#### SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch 🔧
121+
122+
Disallows string expression property fetch `$object->{'foo'}` when the property name is compatible with identifier access.
123+
116124
#### SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces 🔧
117125

118126
Enforces one configurable number of lines after opening class/interface/trait brace and one empty line before the closing brace.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\Classes;
4+
5+
use SlevomatCodingStandard\Sniffs\TestCase;
6+
7+
class DisallowStringExpressionPropertyFetchSniffTest extends TestCase
8+
{
9+
10+
public function testNoErrors(): void
11+
{
12+
$report = self::checkFile(__DIR__ . '/data/disallowStringExpressionPropertyFetchNoErrors.php');
13+
self::assertNoSniffErrorInFile($report);
14+
}
15+
16+
public function testErrors(): void
17+
{
18+
$report = self::checkFile(__DIR__ . '/data/disallowStringExpressionPropertyFetchErrors.php');
19+
20+
self::assertSame(3, $report->getErrorCount());
21+
22+
self::assertSniffError($report, 3, DisallowStringExpressionPropertyFetchSniff::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH);
23+
self::assertSniffError($report, 5, DisallowStringExpressionPropertyFetchSniff::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH);
24+
self::assertSniffError($report, 7, DisallowStringExpressionPropertyFetchSniff::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH);
25+
26+
self::assertAllFixedInFile($report);
27+
}
28+
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
$a->foo;
4+
5+
$a->boo;
6+
7+
$a->foo_boo;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
$a->{'foo'};
4+
5+
$a->{"boo"};
6+
7+
$a->{"foo_boo"};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
$a->foo;
4+
5+
$b->boo();
6+
7+
$c->{'coo'}();
8+
9+
$d->{'doo' . $c};
10+
11+
if (isset($a->{'not-compatible'})) {
12+
13+
}

0 commit comments

Comments
 (0)