From c4e213e6e57f741451a08e68ef838802eec92287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 8 Apr 2023 15:22:00 +0200 Subject: [PATCH] SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch: New sniff that disallows string expression property fetch $object->{'foo'} --- README.md | 1 + ...llowStringExpressionPropertyFetchSniff.php | 85 +++++++++++++++++++ doc/classes.md | 8 ++ ...StringExpressionPropertyFetchSniffTest.php | 29 +++++++ ...ingExpressionPropertyFetchErrors.fixed.php | 7 ++ ...lowStringExpressionPropertyFetchErrors.php | 7 ++ ...wStringExpressionPropertyFetchNoErrors.php | 13 +++ 7 files changed, 150 insertions(+) create mode 100644 SlevomatCodingStandard/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniff.php create mode 100644 tests/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniffTest.php create mode 100644 tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.fixed.php create mode 100644 tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.php create mode 100644 tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchNoErrors.php diff --git a/README.md b/README.md index cc48950e4..162e16075 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Slevomat Coding Standard for [PHP_CodeSniffer](https://github.com/squizlabs/PHP_ - [SlevomatCodingStandard.Classes.DisallowLateStaticBindingForConstants](doc/classes.md#slevomatcodingstandardclassesdisallowlatestaticbindingforconstants-) 🔧 - [SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition](doc/classes.md#slevomatcodingstandardclassesdisallowmulticonstantdefinition-) 🔧 - [SlevomatCodingStandard.Classes.DisallowMultiPropertyDefinition](doc/classes.md#slevomatcodingstandardclassesdisallowmultipropertydefinition-) 🔧 + - [SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch](doc/classes.md#slevomatcodingstandardclassesdisallowstringexpressionpropertyfetch-) 🔧 - [SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces](doc/classes.md#slevomatcodingstandardclassesemptylinesaroundclassbraces-) 🔧 - [SlevomatCodingStandard.Classes.EnumCaseSpacing](doc/classes.md#slevomatcodingstandardclassesenumcasespacing-) 🔧 - [SlevomatCodingStandard.Classes.ForbiddenPublicProperty](doc/classes.md#slevomatcodingstandardclassesforbiddenpublicproperty) diff --git a/SlevomatCodingStandard/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniff.php b/SlevomatCodingStandard/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniff.php new file mode 100644 index 000000000..81f01b7d0 --- /dev/null +++ b/SlevomatCodingStandard/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniff.php @@ -0,0 +1,85 @@ + + */ + public function register(): array + { + return [T_OBJECT_OPERATOR]; + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + * @param int $objectOperatorPointer + */ + public function process(File $phpcsFile, $objectOperatorPointer): void + { + $tokens = $phpcsFile->getTokens(); + + $curlyBracketOpenerPointer = TokenHelper::findNextEffective($phpcsFile, $objectOperatorPointer + 1); + + if ($tokens[$curlyBracketOpenerPointer]['code'] !== T_OPEN_CURLY_BRACKET) { + return; + } + + $curlyBracketCloserPointer = $tokens[$curlyBracketOpenerPointer]['bracket_closer']; + + if (TokenHelper::findNextExcluding( + $phpcsFile, + T_CONSTANT_ENCAPSED_STRING, + $curlyBracketOpenerPointer + 1, + $curlyBracketCloserPointer + ) !== null) { + return; + } + + $pointerAfterCurlyBracketCloser = TokenHelper::findNextEffective($phpcsFile, $curlyBracketCloserPointer + 1); + + if ($tokens[$pointerAfterCurlyBracketCloser]['code'] === T_OPEN_PARENTHESIS) { + return; + } + + if (preg_match( + '~^(["\'])([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\1$~', + $tokens[$curlyBracketOpenerPointer + 1]['content'], + $matches + ) !== 1) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'String expression property fetch is disallowed, use identifier property fetch.', + $curlyBracketOpenerPointer, + self::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH + ); + + if (!$fix) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + + $phpcsFile->fixer->replaceToken($curlyBracketOpenerPointer, $matches[2]); + FixerHelper::removeBetweenIncluding($phpcsFile, $curlyBracketOpenerPointer + 1, $curlyBracketCloserPointer); + + $phpcsFile->fixer->endChangeset(); + } + +} diff --git a/doc/classes.md b/doc/classes.md index b4826f932..5bbed250b 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -113,6 +113,14 @@ Disallows multi constant definition. Disallows multi property definition. +#### SlevomatCodingStandard.Classes.DisallowMultiPropertyDefinition 🔧 + +Disallows multi property definition. + +#### SlevomatCodingStandard.Classes.DisallowStringExpressionPropertyFetch 🔧 + +Disallows string expression property fetch `$object->{'foo'}` when the property name is compatible with identifier access. + #### SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces 🔧 Enforces one configurable number of lines after opening class/interface/trait brace and one empty line before the closing brace. diff --git a/tests/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniffTest.php b/tests/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniffTest.php new file mode 100644 index 000000000..164d15246 --- /dev/null +++ b/tests/Sniffs/Classes/DisallowStringExpressionPropertyFetchSniffTest.php @@ -0,0 +1,29 @@ +getErrorCount()); + + self::assertSniffError($report, 3, DisallowStringExpressionPropertyFetchSniff::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH); + self::assertSniffError($report, 5, DisallowStringExpressionPropertyFetchSniff::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH); + self::assertSniffError($report, 7, DisallowStringExpressionPropertyFetchSniff::CODE_DISALLOWED_STRING_EXPRESSION_PROPERTY_FETCH); + + self::assertAllFixedInFile($report); + } + +} diff --git a/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.fixed.php b/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.fixed.php new file mode 100644 index 000000000..964cee3ef --- /dev/null +++ b/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.fixed.php @@ -0,0 +1,7 @@ +foo; + +$a->boo; + +$a->foo_boo; diff --git a/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.php b/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.php new file mode 100644 index 000000000..f5f20cabe --- /dev/null +++ b/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchErrors.php @@ -0,0 +1,7 @@ +{'foo'}; + +$a->{"boo"}; + +$a->{"foo_boo"}; diff --git a/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchNoErrors.php b/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchNoErrors.php new file mode 100644 index 000000000..361a7f4f7 --- /dev/null +++ b/tests/Sniffs/Classes/data/disallowStringExpressionPropertyFetchNoErrors.php @@ -0,0 +1,13 @@ +foo; + +$b->boo(); + +$c->{'coo'}(); + +$d->{'doo' . $c}; + +if (isset($a->{'not-compatible'})) { + +}