Skip to content

Commit

Permalink
SlevomatCodingStandard.Strings.DisallowVariableParsing: Fixed false p…
Browse files Browse the repository at this point in the history
…ositives
  • Loading branch information
kukulich committed Apr 24, 2023
1 parent cf10256 commit af87461
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 48 deletions.
133 changes: 92 additions & 41 deletions SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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 = '~(?<!{|\[)\$[\w\[\]\->]+(?!})~';

/** @var bool */
public $disallowDollarCurlySyntax = true;

Expand Down Expand Up @@ -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('<?php "' . $tokenContent . '"')
: token_get_all('<?php ' . $tokenContent);

for ($i = 0; $i < count($stringTokens); $i++) {
$stringToken = $stringTokens[$i];

if (!is_array($stringToken)) {
continue;
}
}

// Cover strings where $... syntax is used
// phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($this->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;
}

}
33 changes: 27 additions & 6 deletions tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function testErrorsCurlyDollarSyntax(): void
]
);

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

self::assertSniffError(
$report,
Expand Down Expand Up @@ -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
Expand All @@ -123,7 +137,7 @@ public function testErrorsSimpleSyntax(): void
]
);

self::assertSame(6, $report->getErrorCount());
self::assertSame(7, $report->getErrorCount());

self::assertSniffError(
$report,
Expand All @@ -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(
Expand All @@ -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".'
);
}

Expand Down
4 changes: 3 additions & 1 deletion tests/Sniffs/Strings/data/disallowVariableParsingErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@
Some heredoc line with object variable $object->name
EOT;

"{$array[$simpleString]}";
"{$array[$simpleString]} $simpleString";

"{$a->test($b)}";

0 comments on commit af87461

Please sign in to comment.