From 66a582800093ce4136766a5901caf3a9882d0aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ismail=20=C3=96zg=C3=BCn=20Turan?= Date: Mon, 14 Oct 2024 10:04:41 +0200 Subject: [PATCH] Create a sniff to ensure constants being typed by native types. --- .../MissingNativeConstantTypeSniff.php | 144 ++++++++++++++++++ .../MissingNativeConstantTypeSniffTest.php | 68 +++++++++ .../missingConstantTypeHintDisabled.fixed.php | 55 +++++++ .../data/missingConstantTypeHintDisabled.php | 55 +++++++ .../missingConstantTypeHintErrors.fixed.php | 63 ++++++++ .../data/missingConstantTypeHintErrors.php | 63 ++++++++ .../missingConstantTypeHintIgnoreErrors.php | 23 +++ .../data/missingConstantTypeHintNoErrors.php | 55 +++++++ 8 files changed, 526 insertions(+) create mode 100644 SlevomatCodingStandard/Sniffs/TypeHints/MissingNativeConstantTypeSniff.php create mode 100644 tests/Sniffs/TypeHints/MissingNativeConstantTypeSniffTest.php create mode 100644 tests/Sniffs/TypeHints/data/missingConstantTypeHintDisabled.fixed.php create mode 100644 tests/Sniffs/TypeHints/data/missingConstantTypeHintDisabled.php create mode 100644 tests/Sniffs/TypeHints/data/missingConstantTypeHintErrors.fixed.php create mode 100644 tests/Sniffs/TypeHints/data/missingConstantTypeHintErrors.php create mode 100644 tests/Sniffs/TypeHints/data/missingConstantTypeHintIgnoreErrors.php create mode 100644 tests/Sniffs/TypeHints/data/missingConstantTypeHintNoErrors.php diff --git a/SlevomatCodingStandard/Sniffs/TypeHints/MissingNativeConstantTypeSniff.php b/SlevomatCodingStandard/Sniffs/TypeHints/MissingNativeConstantTypeSniff.php new file mode 100644 index 000000000..ce7474e83 --- /dev/null +++ b/SlevomatCodingStandard/Sniffs/TypeHints/MissingNativeConstantTypeSniff.php @@ -0,0 +1,144 @@ + 'float', + self::T_LNUMBER => 'int', + T_NULL => 'null', + T_TRUE => 'true', + T_FALSE => 'false', + T_OPEN_SHORT_ARRAY => 'array', + self::T_CONSTANT_ENCAPSED_STRING => 'string', + ]; + + /** @var bool */ + public $enable = true; + + /** + * @return array + */ + public function register(): array + { + return [ + T_CONST, + ]; + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + * @param int $stackPtr + */ + public function process(File $phpcsFile, $stackPtr): void + { + $this->enable = SniffSettingsHelper::isEnabledByPhpVersion($this->enable, 80300); + + if (!$this->enable) { + return; + } + + if (SuppressHelper::isSniffSuppressed($phpcsFile, $stackPtr, self::NAME)) { + return; + } + + $tokens = $phpcsFile->getTokens(); + + /** @var int $classPointer */ + $classPointer = array_keys($tokens[$stackPtr]['conditions'])[count($tokens[$stackPtr]['conditions']) - 1]; + $typePointer = TokenHelper::findNextEffective($phpcsFile, $stackPtr + 1); + if (in_array($tokens[$typePointer]['code'], [T_NULL, T_TRUE, T_FALSE], true)) { + return; + } + + if ( + $tokens[$typePointer]['code'] === self::T_STRING + && in_array($tokens[$typePointer]['content'], ['int', 'string', 'float', 'double', 'array', 'object'], true) + ) { + return; + } + + $equalSignPointer = TokenHelper::findNext($phpcsFile, T_EQUAL, $stackPtr + 1); + $namePointer = TokenHelper::findPreviousEffective($phpcsFile, $equalSignPointer - 1); + + if ( + $tokens[$typePointer]['code'] === self::T_STRING + && $namePointer !== $typePointer + ) { + $className = NamespaceHelper::resolveClassName($phpcsFile, $tokens[$typePointer]['content'], $typePointer); + if (enum_exists($className)) { + return; + } + } + + $assignedValuePointer = TokenHelper::findNextEffective($phpcsFile, $equalSignPointer + 1); + if ($tokens[$assignedValuePointer]['code'] === T_MINUS) { + $assignedValuePointer = TokenHelper::findNextEffective($phpcsFile, $assignedValuePointer + 1); + } + + $fixableType = self::TOKEN_TO_TYPE_MAP[$tokens[$assignedValuePointer]['code']] ?? null; + if ($fixableType === null) { + $className = NamespaceHelper::resolveClassName($phpcsFile, $tokens[$assignedValuePointer]['content'], $assignedValuePointer); + if (enum_exists($className)) { + $fixableType = $tokens[$assignedValuePointer]['content']; + } + } + + if ($fixableType !== null) { + $message = sprintf( + 'Constant %s::%s is missing a type (%s).', + ClassHelper::getFullyQualifiedName($phpcsFile, $classPointer), + $tokens[$namePointer]['content'], + $fixableType + ); + + $fix = $phpcsFile->addFixableError($message, $typePointer, self::CODE_MISSING_CONSTANT_TYPE); + if ($fix) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore($typePointer, $fixableType . ' '); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + $message = sprintf( + 'Constant %s::%s is missing a type.', + ClassHelper::getFullyQualifiedName($phpcsFile, $classPointer), + $tokens[$namePointer]['content'] + ); + + $phpcsFile->addError($message, $stackPtr, self::CODE_MISSING_CONSTANT_TYPE); + } + +} diff --git a/tests/Sniffs/TypeHints/MissingNativeConstantTypeSniffTest.php b/tests/Sniffs/TypeHints/MissingNativeConstantTypeSniffTest.php new file mode 100644 index 000000000..3060300e9 --- /dev/null +++ b/tests/Sniffs/TypeHints/MissingNativeConstantTypeSniffTest.php @@ -0,0 +1,68 @@ +getErrorCount()); + + for ($i = 8; $i < 16; $i++) { + self::assertSniffError($report, $i, MissingNativeConstantTypeSniff::CODE_MISSING_CONSTANT_TYPE); + } + + for ($i = 23; $i < 31; $i++) { + self::assertSniffError($report, $i, MissingNativeConstantTypeSniff::CODE_MISSING_CONSTANT_TYPE); + } + + for ($i = 38; $i < 46; $i++) { + self::assertSniffError($report, $i, MissingNativeConstantTypeSniff::CODE_MISSING_CONSTANT_TYPE); + } + + for ($i = 53; $i < 61; $i++) { + self::assertSniffError($report, $i, MissingNativeConstantTypeSniff::CODE_MISSING_CONSTANT_TYPE); + } + + self::assertAllFixedInFile($report); + } + + public function testIgnoredBySuppress(): void + { + $report = self::checkFile(__DIR__ . '/data/missingConstantTypeHintIgnoreErrors.php'); + + self::assertSame(0, $report->getErrorCount()); + } + + public function testWithEnableConfigEnabled(): void + { + $report = self::checkFile( + __DIR__ . '/data/missingConstantTypeHintErrors.php', + ['enable' => true], + [MissingNativeConstantTypeSniff::CODE_MISSING_CONSTANT_TYPE] + ); + self::assertAllFixedInFile($report); + } + + public function testWithEnableConfigDisabled(): void + { + $report = self::checkFile( + __DIR__ . '/data/missingConstantTypeHintDisabled.php', + ['enable' => false], + [MissingNativeConstantTypeSniff::CODE_MISSING_CONSTANT_TYPE] + ); + self::assertAllFixedInFile($report); + } + +} diff --git a/tests/Sniffs/TypeHints/data/missingConstantTypeHintDisabled.fixed.php b/tests/Sniffs/TypeHints/data/missingConstantTypeHintDisabled.fixed.php new file mode 100644 index 000000000..17f0476f9 --- /dev/null +++ b/tests/Sniffs/TypeHints/data/missingConstantTypeHintDisabled.fixed.php @@ -0,0 +1,55 @@ +