diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7dd88..5419f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,27 @@ See [GitHub releases](https://github.com/mll-lab/php-utils/releases). ## Unreleased +## v6.0.0 + +### Changed + +- Breaking Change: Rename class `MLL\Utils\PHPStan\Rules\VariableNameIdToIDRule` to `MLL\Utils\PHPStan\Rules\NameIdToIDRule` +- Make `MLL\Utils\PHPStan\Rules\NameIdToIDRule` handle variable names, parameter names, method names, and class names for incorrect capitalization of `Id` +- Add `Idt` and `Identical` to the list of incorrect capitalizations for `Id` of `MLL\Utils\PHPStan\Rules\NameIdToIDRule` +- Add RuleIdentifier `mll.capitalizationOfID` to `MLL\Utils\PHPStan\Rules\NameIdToIDRule` +- Add PHPStan-rule `MLL\Utils\PHPStan\Rules\CanonicalCapitalizations` to check spelling of LabID + ## v5.8.0 ### Added -- Add PHPStan-Rule `MLL\Utils\PHPStan\Rules\ThrowableClassNameRule` +- Add PHPStan-rule `MLL\Utils\PHPStan\Rules\ThrowableClassNameRule` ## v5.7.0 ### Added -- Add PHPStan-Rule `MLL\Utils\PHPStan\Rules\VariableNameIdToIDRule` +- Add PHPStan-rule `MLL\Utils\PHPStan\Rules\VariableNameIdToIDRule` ## v5.6.0 diff --git a/src/PHPStan/NodeNameExtractor/ClassMethodNameExtractor.php b/src/PHPStan/NodeNameExtractor/ClassMethodNameExtractor.php new file mode 100644 index 0000000..11b1277 --- /dev/null +++ b/src/PHPStan/NodeNameExtractor/ClassMethodNameExtractor.php @@ -0,0 +1,18 @@ +name->toString(); + } + + return null; + } +} diff --git a/src/PHPStan/NodeNameExtractor/ClassNameExtractor.php b/src/PHPStan/NodeNameExtractor/ClassNameExtractor.php new file mode 100644 index 0000000..6c80cf3 --- /dev/null +++ b/src/PHPStan/NodeNameExtractor/ClassNameExtractor.php @@ -0,0 +1,22 @@ +name; + if ($name instanceof Identifier) { + return $name->toString(); + } + } + + return null; + } +} diff --git a/src/PHPStan/NodeNameExtractor/NodeNameExtractor.php b/src/PHPStan/NodeNameExtractor/NodeNameExtractor.php new file mode 100644 index 0000000..49d533b --- /dev/null +++ b/src/PHPStan/NodeNameExtractor/NodeNameExtractor.php @@ -0,0 +1,10 @@ +value; + } + + if ($node instanceof EncapsedStringPart) { + return $node->value; + } + + return null; + } +} diff --git a/src/PHPStan/NodeNameExtractor/VariableNameExtractor.php b/src/PHPStan/NodeNameExtractor/VariableNameExtractor.php new file mode 100644 index 0000000..8b9dc48 --- /dev/null +++ b/src/PHPStan/NodeNameExtractor/VariableNameExtractor.php @@ -0,0 +1,32 @@ +name; + if (is_string($name)) { + return $name; + } + } + + if ($node instanceof Param) { + $var = $node->var; + if ($var instanceof Variable) { + $name = $var->name; + if (is_string($name)) { + return $name; + } + } + } + + return null; + } +} diff --git a/src/PHPStan/Rules/CanonicalCapitalization.php b/src/PHPStan/Rules/CanonicalCapitalization.php new file mode 100644 index 0000000..0bd172f --- /dev/null +++ b/src/PHPStan/Rules/CanonicalCapitalization.php @@ -0,0 +1,100 @@ + */ +class CanonicalCapitalization implements Rule +{ + private const CANONICAL_CAPITALIZATIONS = [ + 'Lab ID' => [ + 'lab id', + 'Lab-ID', + 'LabID ', + ' LabID', + ' labID', + 'labID ', + 'LABID', + 'Labid', + 'lab-Id', + 'Lab Id', + ], + ]; + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $extractors = [ + new ClassMethodNameExtractor(), + new ClassNameExtractor(), + new VariableNameExtractor(), + new StringNameExtractor(), + ]; + + $nodeName = null; + foreach ($extractors as $extractor) { + $extractedName = $extractor->extract($node); + if ($extractedName !== null) { + $nodeName = $extractedName; + break; + } + } + + if ($nodeName === null) { + return []; + } + + $wrongCapitalization = CanonicalCapitalization::findWrongCapitalization($nodeName); + if ($wrongCapitalization === null) { + return []; + } + + [$correct, $incorrect] = $wrongCapitalization; + + $expectedName = self::fixIDCapitalization($nodeName, $correct, $incorrect); + + return [ + RuleErrorBuilder::message(<<getType()} "{$nodeName}" should use "{$correct}" instead of "{$incorrect}", rename it to "{$expectedName}". + TXT) + ->identifier('mll.canonicalCapitalization') + ->build(), + ]; + } + + /** @return array{0: string, 1: string}|null */ + public static function findWrongCapitalization(string $nodeName): ?array + { + foreach (self::CANONICAL_CAPITALIZATIONS as $correct => $incorrectVariants) { + foreach ($incorrectVariants as $incorrect) { + if (preg_match("/{$incorrect}/", $nodeName) === 1) { + return [$correct, $incorrect]; + } + } + } + + return null; + } + + public static function fixIDCapitalization(string $nodeName, string $correct, string $incorrect): string + { + $trimmedIncorrect = trim($incorrect); + $trimmedCorrect = trim($correct); + + return str_replace($trimmedIncorrect, $trimmedCorrect, $nodeName); + } +} diff --git a/src/PHPStan/Rules/CapitalizationOfIDRule.php b/src/PHPStan/Rules/CapitalizationOfIDRule.php new file mode 100644 index 0000000..66eaa9b --- /dev/null +++ b/src/PHPStan/Rules/CapitalizationOfIDRule.php @@ -0,0 +1,78 @@ + */ +class CapitalizationOfIDRule implements Rule +{ + /** Lists words or phrases that contain "Id" but are fine. */ + protected const FALSE_POSITIVES = [ + 'Identifier', + 'Identical', + 'Idt', // IDT is an abbreviation for the brand "Integrated DNA Technologies, Inc." + ]; + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $extractors = [ + new ClassMethodNameExtractor(), + new ClassNameExtractor(), + new VariableNameExtractor(), + ]; + $nodeName = null; + foreach ($extractors as $extractor) { + $extractedName = $extractor->extract($node); + if ($extractedName !== null) { + $nodeName = $extractedName; + break; + } + } + + if ($nodeName === null) { + return []; + } + + if (! self::containsWrongIDCapitalization($nodeName)) { + return []; + } + + $expectedName = self::fixIDCapitalization($nodeName); + + return [ + RuleErrorBuilder::message(<<getType()} "{$nodeName}" should use "ID" instead of "Id", rename it to "{$expectedName}". + TXT) + ->identifier('mll.capitalizationOfID') + ->build(), + ]; + } + + public static function containsWrongIDCapitalization(string $nodeName): bool + { + return \Safe\preg_match('/Id/', $nodeName) === 1 + && ! Str::contains($nodeName, self::FALSE_POSITIVES); + } + + public static function fixIDCapitalization(string $nodeName): string + { + if ($nodeName === 'Id') { + return 'id'; + } + + return str_replace('Id', 'ID', $nodeName); + } +} diff --git a/src/PHPStan/Rules/VariableNameIdToIDRule.php b/src/PHPStan/Rules/VariableNameIdToIDRule.php deleted file mode 100644 index 7968f00..0000000 --- a/src/PHPStan/Rules/VariableNameIdToIDRule.php +++ /dev/null @@ -1,56 +0,0 @@ - */ -class VariableNameIdToIDRule implements Rule -{ - /** Lists words or phrases that contain "Id" but are fine. */ - protected const FALSE_POSITIVES = ['Identifier']; - - public function getNodeType(): string - { - return Variable::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $nodeName = $node->name; - - if (is_string($nodeName) - && static::containsWrongIDCapitalization($nodeName) - ) { - $expectedName = static::fixIDCapitalization($nodeName); - - return [ - RuleErrorBuilder::message(<<build(), - ]; - } - - return []; - } - - public static function containsWrongIDCapitalization(string $nodeName): bool - { - return \Safe\preg_match('/Id/', $nodeName) === 1 - && ! Str::contains($nodeName, self::FALSE_POSITIVES); - } - - public static function fixIDCapitalization(string $nodeName): string - { - if ($nodeName === 'Id') { - return 'id'; - } - - return str_replace('Id', 'ID', $nodeName); - } -} diff --git a/tests/PHPStan/Rules/CanonicalCapitalization/CanonicalCapitalizationTest.php b/tests/PHPStan/Rules/CanonicalCapitalization/CanonicalCapitalizationTest.php new file mode 100644 index 0000000..0839034 --- /dev/null +++ b/tests/PHPStan/Rules/CanonicalCapitalization/CanonicalCapitalizationTest.php @@ -0,0 +1,59 @@ + */ + public static function wrongCapitalizationProvider(): iterable + { + yield 'Should correct capitalization for " LabID"' => ['The LabID', ['Lab ID', ' LabID', 'The Lab ID']]; + yield 'Should correct capitalization for " labID"' => ['The labID is incorrect', ['Lab ID', ' labID', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "LABID"' => ['The LABID is incorrect', ['Lab ID', 'LABID', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "Lab Id"' => ['The Lab Id is incorrect', ['Lab ID', 'Lab Id', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "Lab-ID"' => ['The Lab-ID is incorrect', ['Lab ID', 'Lab-ID', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "LabID "' => ['The LabID is incorrect', ['Lab ID', 'LabID ', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "Labid"' => ['The Labid is incorrect', ['Lab ID', 'Labid', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "lab id"' => ['The lab id is incorrect', ['Lab ID', 'lab id', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "lab-Id"' => ['The lab-Id is incorrect', ['Lab ID', 'lab-Id', self::EXPECTED_LAB_ID_IS_INCORRECT]]; + yield 'Should correct capitalization for "labID "' => ['labID is incorrect', ['Lab ID', 'labID ', 'Lab ID is incorrect']]; + } + + /** @dataProvider rightCapitalizationProvider */ + #[DataProvider('rightCapitalizationProvider')] + public function testFindWrongCapitalizationFalse(string $nodeName): void + { + self::assertNull(CanonicalCapitalization::findWrongCapitalization($nodeName)); + } + + /** @return iterable> */ + public static function rightCapitalizationProvider(): iterable + { + yield 'Should not be fixed because it might be in a non-space case written' => ['TheLabIDIsIncorrect']; + yield 'Should not be fixed because it might be a varName' => ['labID']; + } +} diff --git a/tests/PHPStan/Rules/VariableNameIdToIDRuleTest.php b/tests/PHPStan/Rules/CapitalizationOfIDRule/CapitalizationOfIDRuleTest.php similarity index 75% rename from tests/PHPStan/Rules/VariableNameIdToIDRuleTest.php rename to tests/PHPStan/Rules/CapitalizationOfIDRule/CapitalizationOfIDRuleTest.php index d6499ba..b423aae 100644 --- a/tests/PHPStan/Rules/VariableNameIdToIDRuleTest.php +++ b/tests/PHPStan/Rules/CapitalizationOfIDRule/CapitalizationOfIDRuleTest.php @@ -1,18 +1,18 @@ */ @@ -27,7 +27,7 @@ public static function wrongID(): iterable #[DataProvider('correctID')] public function testAllowsCorrectCapitalizations(string $variableName): void { - self::assertFalse(VariableNameIdToIDRule::containsWrongIDCapitalization($variableName)); + self::assertFalse(CapitalizationOfIDRule::containsWrongIDCapitalization($variableName)); } /** @return iterable */ @@ -39,13 +39,15 @@ public static function correctID(): iterable yield ['labID']; yield ['labIDs']; yield ['testIdentifier']; + yield ['openIdtAnalyses']; + yield ['isIdenticalThing']; } /** @dataProvider wrongToRight */ #[DataProvider('wrongToRight')] public function testFixIDCapitalization(string $wrong, string $right): void { - self::assertSame($right, VariableNameIdToIDRule::fixIDCapitalization($wrong)); + self::assertSame($right, CapitalizationOfIDRule::fixIDCapitalization($wrong)); } /** @return iterable */