Skip to content

Commit

Permalink
SlevomatCodingStandard.Strings.DisallowVariableParsing: New sniff to …
Browse files Browse the repository at this point in the history
…disallow variable parsing inside strings
  • Loading branch information
Ekimik authored and kukulich committed Apr 9, 2023
1 parent 81b4d3a commit 91ab3d3
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ Slevomat Coding Standard for [PHP_CodeSniffer](https://github.com/squizlabs/PHP_
- [SlevomatCodingStandard.PHP.TypeCast](doc/php.md#slevomatcodingstandardphptypecast-) 🔧
- [SlevomatCodingStandard.PHP.UselessParentheses](doc/php.md#slevomatcodingstandardphpuselessparentheses-) 🔧
- [SlevomatCodingStandard.PHP.UselessSemicolon](doc/php.md#slevomatcodingstandardphpuselesssemicolon-) 🔧
- [SlevomatCodingStandard.Strings.DisallowVariableParsing](doc/strings.md#slevomatcodingstandardstringsdisallowvariableparsing)
- [SlevomatCodingStandard.TypeHints.DeclareStrictTypes](doc/type-hints.md#slevomatcodingstandardtypehintsdeclarestricttypes-) 🔧
- [SlevomatCodingStandard.TypeHints.DisallowArrayTypeHintSyntax](doc/type-hints.md#slevomatcodingstandardtypehintsdisallowarraytypehintsyntax-) 🔧
- [SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint](doc/type-hints.md#slevomatcodingstandardtypehintsdisallowmixedtypehint)
Expand Down
103 changes: 103 additions & 0 deletions SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php declare(strict_types = 1);

namespace SlevomatCodingStandard\Sniffs\Strings;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use UnexpectedValueException;
use function preg_match;
use function sprintf;
use const T_DOUBLE_QUOTED_STRING;
use const T_HEREDOC;

class DisallowVariableParsingSniff implements Sniff
{

public const CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX = 'DisallowedDollarCurlySyntax';

public const CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX = 'DisallowedCurlyDollarSyntax';

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;

/** @var bool */
public $disallowCurlyDollarSyntax = false;

/** @var bool */
public $disallowSimpleSyntax = false;

/**
* @return array<int, (int|string)>
*/
public function register(): array
{
return [
T_DOUBLE_QUOTED_STRING,
T_HEREDOC,
];
}

/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $stringPointer
*/
public function process(File $phpcsFile, $stringPointer): void
{
if (!$this->disallowDollarCurlySyntax && !$this->disallowCurlyDollarSyntax && !$this->disallowSimpleSyntax) {
throw new UnexpectedValueException('No option is set.');
}

$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
);
}
}

// 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
);
}
}

// 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
);
}
}
}

}
11 changes: 11 additions & 0 deletions doc/strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Strings

#### SlevomatCodingStandard.Strings.DisallowVariableParsing

Disallows variable parsing inside strings.

Sniff provides the following settings:

* `disallowDollarCurlySyntax`: disallows usage of `${...}`, enabled by default.
* `disallowCurlyDollarSyntax`: disallows usage of `{$...}`, disabled by default.
* `disallowSimpleSyntax`: disallows usage of `$...`, disabled by default.
183 changes: 183 additions & 0 deletions tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php declare(strict_types = 1);

namespace SlevomatCodingStandard\Sniffs\Strings;

use SlevomatCodingStandard\Sniffs\TestCase;
use UnexpectedValueException;

class DisallowVariableParsingSniffTest extends TestCase
{

public function testNoErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/disallowVariableParsingNoError.php');
self::assertNoSniffErrorInFile($report);
}

public function testErrorsDollarCurlySyntax(): void
{
$report = self::checkFile(
__DIR__ . '/data/disallowVariableParsingErrors.php',
[
'disallowDollarCurlySyntax' => true,
'disallowCurlyDollarSyntax' => false,
'disallowSimpleSyntax' => false,
]
);

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

self::assertSniffError(
$report,
10,
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${simpleString}".'
);

self::assertSniffError(
$report,
11,
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${array[1]}".'
);

self::assertSniffError(
$report,
17,
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${simpleString}".'
);

self::assertSniffError(
$report,
18,
DisallowVariableParsingSniff::CODE_DISALLOWED_DOLLAR_CURLY_SYNTAX,
'Using variable syntax "${...}" inside string is disallowed as syntax "${...}" is deprecated as of PHP 8.2, found "${array[1]}".'
);
}

public function testErrorsCurlyDollarSyntax(): void
{
$report = self::checkFile(
__DIR__ . '/data/disallowVariableParsingErrors.php',
[
'disallowCurlyDollarSyntax' => true,
'disallowDollarCurlySyntax' => false,
'disallowSimpleSyntax' => false,
]
);

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

self::assertSniffError(
$report,
22,
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
'Using variable syntax "{$...}" inside string is disallowed, found "{$simpleString}".'
);

self::assertSniffError(
$report,
23,
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
'Using variable syntax "{$...}" inside string is disallowed, found "{$array[1]}".'
);

self::assertSniffError(
$report,
24,
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
'Using variable syntax "{$...}" inside string is disallowed, found "{$object->name}".'
);

self::assertSniffError(
$report,
31,
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
'Using variable syntax "{$...}" inside string is disallowed, found "{$simpleString}".'
);

self::assertSniffError(
$report,
32,
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
'Using variable syntax "{$...}" inside string is disallowed, found "{$array[1]}".'
);

self::assertSniffError(
$report,
33,
DisallowVariableParsingSniff::CODE_DISALLOWED_CURLY_DOLLAR_SYNTAX,
'Using variable syntax "{$...}" inside string is disallowed, found "{$object->name}".'
);
}

public function testErrorsSimpleSyntax(): void
{
$report = self::checkFile(
__DIR__ . '/data/disallowVariableParsingErrors.php',
[
'disallowSimpleSyntax' => true,
'disallowDollarCurlySyntax' => false,
'disallowCurlyDollarSyntax' => false,
]
);

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

self::assertSniffError(
$report,
37,
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
'Using variable syntax "$..." inside string is disallowed, found "$simpleString".'
);

self::assertSniffError(
$report,
38,
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
'Using variable syntax "$..." inside string is disallowed, found "$array[1]".'
);

self::assertSniffError(
$report,
39,
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
'Using variable syntax "$..." inside string is disallowed, found "$object->name".'
);

self::assertSniffError(
$report,
46,
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
'Using variable syntax "$..." inside string is disallowed, found "$simpleString".'
);

self::assertSniffError(
$report,
47,
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
'Using variable syntax "$..." inside string is disallowed, found "$array[1]".'
);

self::assertSniffError(
$report,
48,
DisallowVariableParsingSniff::CODE_DISALLOWED_SIMPLE_SYNTAX,
'Using variable syntax "$..." inside string is disallowed, found "$object->name".'
);
}

public function testNoOptionIsSet(): void
{
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('No option is set.');

self::checkFile(__DIR__ . '/data/disallowVariableParsingNoError.php', [
'disallowDollarCurlySyntax' => false,
'disallowCurlyDollarSyntax' => false,
'disallowSimpleSyntax' => false,
]);
}

}
49 changes: 49 additions & 0 deletions tests/Sniffs/Strings/data/disallowVariableParsingErrors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

$simpleString = 'foo';
$array = ['foo', 'bar', 'baz'];

$object = new \stdClass();
$object->name = 'foobar';

// Covers strings where ${...} syntax is used
$dollarCurlySyntaxDoubleQuotedScalar = "Some double quoted string with scalar variable ${simpleString}.";
$dollarCurlySyntaxDoubleQuotedArrayItem = "Some double quoted string with array item variable ${array[1]}.";

$dollarCurlySyntaxSingleQuotedScalar = 'Some single quoted string with scalar variable ${simpleString}.';
$dollarCurlySyntaxSingleQuotedArrayItem = 'Some single quoted string with array item variable ${array[1]}.';

$dollarCurlySyntaxHereDoc = <<<EOT
Some heredoc line with scalar variable ${simpleString}
Some heredoc line with array item variable ${array[1]}
EOT;

// Covers strings where {$...} syntax is used
$curlyDollarSyntaxDoubleQuotedScalar = "Some double quoted string with scalar variable {$simpleString}.";
$curlyDollarSyntaxDoubleQuotedArrayItem = "Some double quoted string with array item variable {$array[1]}.";
$curlyDollarSyntaxDoubleQuotedObject = "Some double quoted string with object variable {$object->name}.";

$curlyDollarSyntaxSingleQuotedScalar = 'Some single quoted string with scalar variable {$simpleString}.';
$curlyDollarSyntaxSingleQuotedArrayItem = 'Some single quoted string with array item variable {$array[1]}.';
$curlyDollarSyntaxSingleQuotedObject = 'Some single quoted string with object variable {$object->name}.';

$curlyDollarSyntaxHereDoc = <<<EOT
Some heredoc line with scalar variable {$simpleString}
Some heredoc line with array item variable {$array[1]}
Some heredoc line with object variable {$object->name}
EOT;

// Covers strings where $... syntax is used
$simpleSyntaxDoubleQuotedScalar = "Some double quoted string with scalar variable $simpleString.";
$simpleSyntaxDoubleQuotedArrayItem = "Some double quoted string with array item variable $array[1].";
$simpleSyntaxDoubleQuotedObject = "Some double quoted string with object variable $object->name.";

$simpleSyntaxSingleQuotedScalar = 'Some single quoted string with scalar variable $simpleString.';
$simpleSyntaxSingleQuotedArrayItem = 'Some single quoted string with array item variable $array[1].';
$simpleSyntaxSingleQuotedObject = 'Some single quoted string with object variable $object->name.';

$simpleSyntaxHereDoc = <<<EOT
Some heredoc line with scalar variable $simpleString
Some heredoc line with array item variable $array[1]
Some heredoc line with object variable $object->name
EOT;
8 changes: 8 additions & 0 deletions tests/Sniffs/Strings/data/disallowVariableParsingNoError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

$noVariablesDoubleQuoted = "Some double quoted string without variables";
$noVariablesSingleQuoted = 'Some single quoted string without variables';

$noVariablesHereDoc = <<<EOT
Some heredoc line without variables
EOT;

0 comments on commit 91ab3d3

Please sign in to comment.