diff --git a/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php b/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php index e012d0b8e..45a8c4048 100644 --- a/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php +++ b/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php @@ -5,10 +5,15 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use UnexpectedValueException; -use function preg_match; +use function count; +use function in_array; +use function is_array; use function sprintf; +use function strpos; +use function token_get_all; use const T_DOUBLE_QUOTED_STRING; use const T_HEREDOC; +use const T_VARIABLE; class DisallowVariableParsingSniff implements Sniff { @@ -19,10 +24,6 @@ class DisallowVariableParsingSniff implements Sniff public const CODE_DISALLOWED_SIMPLE_SYNTAX = 'DisallowedSimpleSyntax'; - private const DOLLAR_CURLY_SYNTAX_PATTERN = '~\${[\w\[\]]+}~'; - private const CURLY_DOLLAR_SYNTAX_PATTERN = '~{\$[\w\[\]\->]+}~'; - private const SIMPLE_SYNTAX_PATTERN = '~(?]+(?!})~'; - /** @var bool */ public $disallowDollarCurlySyntax = true; @@ -56,48 +57,98 @@ public function process(File $phpcsFile, $stringPointer): void $tokens = $phpcsFile->getTokens(); $tokenContent = $tokens[$stringPointer]['content']; - // Cover strings where ${...} syntax is used - if ($this->disallowDollarCurlySyntax && preg_match(self::DOLLAR_CURLY_SYNTAX_PATTERN, $tokenContent, $invalidFragments) === 1) { - foreach ($invalidFragments as $fragment) { - $phpcsFile->addError( - sprintf( - 'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "%s".', - $fragment - ), - $stringPointer, - self::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX - ); - } + if (strpos($tokenContent, '$') === false) { + return; } - // Cover strings where {$...} syntax is used - if ($this->disallowCurlyDollarSyntax && preg_match(self::CURLY_DOLLAR_SYNTAX_PATTERN, $tokenContent, $invalidFragments) === 1) { - foreach ($invalidFragments as $fragment) { - $phpcsFile->addError( - sprintf( - 'Using variable syntax "{$...}" inside string is disallowed, found "%s".', - $fragment - ), - $stringPointer, - self::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX - ); + $stringTokens = $tokens[$stringPointer]['code'] === T_HEREDOC + ? token_get_all('disallowSimpleSyntax && preg_match(self::SIMPLE_SYNTAX_PATTERN, $tokenContent, $invalidFragments) === 1) { - foreach ($invalidFragments as $fragment) { - $phpcsFile->addError( - sprintf( - 'Using variable syntax "$..." inside string is disallowed, found "%s".', - $fragment - ), - $stringPointer, - self::CODE_DISALLOWED_SIMPLE_SYNTAX - ); + if ($this->disallowDollarCurlySyntax && $this->getTokenContent($stringToken) === '${') { + $usedVariable = $stringToken[1]; + + for ($j = $i + 1; $j < count($stringTokens); $j++) { + $usedVariable .= $this->getTokenContent($stringTokens[$j]); + + if ($this->getTokenContent($stringTokens[$j]) === '}') { + $phpcsFile->addError( + sprintf( + 'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "%s".', + $usedVariable + ), + $stringPointer, + self::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX + ); + + break; + } + } + } elseif ($stringToken[0] === T_VARIABLE) { + if ($this->disallowCurlyDollarSyntax && $this->getTokenContent($stringTokens[$i - 1]) === '{') { + $usedVariable = $stringToken[1]; + + for ($j = $i + 1; $j < count($stringTokens); $j++) { + $stringTokenContent = $this->getTokenContent($stringTokens[$j]); + if ($stringTokenContent === '}') { + break; + } + + $usedVariable .= $stringTokenContent; + } + + $phpcsFile->addError( + sprintf( + 'Using variable syntax "{$...}" inside string is disallowed, found "{%s}".', + $usedVariable + ), + $stringPointer, + self::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX + ); + } elseif ($this->disallowSimpleSyntax) { + $error = true; + + for ($j = $i - 1; $j >= 0; $j--) { + $stringTokenContent = $this->getTokenContent($stringTokens[$j]); + + if (in_array($stringTokenContent, ['{', '${'], true)) { + $error = false; + break; + } + + if ($stringTokenContent === '}') { + break; + } + } + + if ($error) { + $phpcsFile->addError( + sprintf( + 'Using variable syntax "$..." inside string is disallowed, found "%s".', + $this->getTokenContent($stringToken) + ), + $stringPointer, + self::CODE_DISALLOWED_SIMPLE_SYNTAX + ); + } + } } } } + /** + * @param array{0: int, 1: string}|string $token + */ + private function getTokenContent($token): string + { + return is_array($token) ? $token[1] : $token; + } + } diff --git a/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php b/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php index d9f95df89..a2fc69e25 100644 --- a/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php +++ b/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php @@ -67,7 +67,7 @@ public function testErrorsCurlyDollarSyntax(): void ] ); - self::assertSame(6, $report->getErrorCount()); + self::assertSame(8, $report->getErrorCount()); self::assertSniffError( $report, @@ -110,6 +110,20 @@ public function testErrorsCurlyDollarSyntax(): void DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX, 'Using variable syntax "{$...}" inside string is disallowed, found "{$object->name}".' ); + + self::assertSniffError( + $report, + 51, + DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX, + 'Using variable syntax "{$...}" inside string is disallowed, found "{$array[$simpleString]}".' + ); + + self::assertSniffError( + $report, + 53, + DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX, + 'Using variable syntax "{$...}" inside string is disallowed, found "{$a->test($b)}".' + ); } public function testErrorsSimpleSyntax(): void @@ -123,7 +137,7 @@ public function testErrorsSimpleSyntax(): void ] ); - self::assertSame(6, $report->getErrorCount()); + self::assertSame(7, $report->getErrorCount()); self::assertSniffError( $report, @@ -136,14 +150,14 @@ public function testErrorsSimpleSyntax(): void $report, 38, DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX, - 'Using variable syntax "$..." inside string is disallowed, found "$array[1]".' + 'Using variable syntax "$..." inside string is disallowed, found "$array".' ); self::assertSniffError( $report, 39, DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX, - 'Using variable syntax "$..." inside string is disallowed, found "$object->name".' + 'Using variable syntax "$..." inside string is disallowed, found "$object".' ); self::assertSniffError( @@ -157,14 +171,21 @@ public function testErrorsSimpleSyntax(): void $report, 47, DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX, - 'Using variable syntax "$..." inside string is disallowed, found "$array[1]".' + 'Using variable syntax "$..." inside string is disallowed, found "$array".' ); self::assertSniffError( $report, 48, DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX, - 'Using variable syntax "$..." inside string is disallowed, found "$object->name".' + 'Using variable syntax "$..." inside string is disallowed, found "$object".' + ); + + self::assertSniffError( + $report, + 51, + DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX, + 'Using variable syntax "$..." inside string is disallowed, found "$simpleString".' ); } diff --git a/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php b/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php index 650cd3bfb..443f794f0 100644 --- a/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php +++ b/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php @@ -48,4 +48,6 @@ Some heredoc line with object variable $object->name EOT; -"{$array[$simpleString]}"; +"{$array[$simpleString]} $simpleString"; + +"{$a->test($b)}";