From 91ab3d31e8aba1d1aabe3103297317fbd521f657 Mon Sep 17 00:00:00 2001 From: ekimik Date: Mon, 27 Feb 2023 15:12:34 +0100 Subject: [PATCH] SlevomatCodingStandard.Strings.DisallowVariableParsing: New sniff to disallow variable parsing inside strings --- README.md | 1 + .../Strings/DisallowVariableParsingSniff.php | 103 ++++++++++ doc/strings.md | 11 ++ .../DisallowVariableParsingSniffTest.php | 183 ++++++++++++++++++ .../data/disallowVariableParsingErrors.php | 49 +++++ .../data/disallowVariableParsingNoError.php | 8 + 6 files changed, 355 insertions(+) create mode 100644 SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php create mode 100644 doc/strings.md create mode 100644 tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php create mode 100644 tests/Sniffs/Strings/data/disallowVariableParsingErrors.php create mode 100644 tests/Sniffs/Strings/data/disallowVariableParsingNoError.php diff --git a/README.md b/README.md index 53e9aa8a8..cc48950e4 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php b/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php new file mode 100644 index 000000000..4a8b8994b --- /dev/null +++ b/SlevomatCodingStandard/Sniffs/Strings/DisallowVariableParsingSniff.php @@ -0,0 +1,103 @@ +]+}~'; + private const SIMPLE_SYNTAX_PATTERN = '~(?]+(?!})~'; + + /** @var bool */ + public $disallowDollarCurlySyntax = true; + + /** @var bool */ + public $disallowCurlyDollarSyntax = false; + + /** @var bool */ + public $disallowSimpleSyntax = false; + + /** + * @return array + */ + 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 + ); + } + } + } + +} diff --git a/doc/strings.md b/doc/strings.md new file mode 100644 index 000000000..17fcc7cc4 --- /dev/null +++ b/doc/strings.md @@ -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. diff --git a/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php b/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php new file mode 100644 index 000000000..d9f95df89 --- /dev/null +++ b/tests/Sniffs/Strings/DisallowVariableParsingSniffTest.php @@ -0,0 +1,183 @@ + 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, + ]); + } + +} diff --git a/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php b/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php new file mode 100644 index 000000000..037de62e4 --- /dev/null +++ b/tests/Sniffs/Strings/data/disallowVariableParsingErrors.php @@ -0,0 +1,49 @@ +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 = <<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 = <<name +EOT; diff --git a/tests/Sniffs/Strings/data/disallowVariableParsingNoError.php b/tests/Sniffs/Strings/data/disallowVariableParsingNoError.php new file mode 100644 index 000000000..612048510 --- /dev/null +++ b/tests/Sniffs/Strings/data/disallowVariableParsingNoError.php @@ -0,0 +1,8 @@ +