From bcf2ba892b478f19ccfefefa091ff4cbecbac2a4 Mon Sep 17 00:00:00 2001 From: Johan Vlaar Date: Tue, 20 May 2025 09:50:47 +0200 Subject: [PATCH] [Symfony 7.3] Add RegexToSlugConstraintRector --- config/sets/symfony/symfony7/symfony73.php | 1 + .../symfony73/symfony73-validator.php | 11 ++ .../Fixture/replace.php.inc | 31 ++++ .../Fixture/replace_all_params.php.inc | 31 ++++ .../replace_param_array_syntax.php.inc | 31 ++++ .../replace_start_and_end_tags.php.inc | 31 ++++ .../Fixture/replace_with_alias.php.inc | 31 ++++ .../Fixture/skip_diffrent_regex.php.inc | 15 ++ .../Fixture/skip_variable_regex.php.inc | 15 ++ .../RegexToSlugConstraintRectorTest.php | 28 +++ .../config/configured_rule.php | 10 + .../New_/RegexToSlugConstraintRector.php | 173 ++++++++++++++++++ 12 files changed, 408 insertions(+) create mode 100644 config/sets/symfony/symfony7/symfony73/symfony73-validator.php create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_all_params.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_param_array_syntax.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_start_and_end_tags.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_with_alias.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_diffrent_regex.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_variable_regex.php.inc create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/RegexToSlugConstraintRectorTest.php create mode 100644 rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/config/configured_rule.php create mode 100644 rules/Symfony73/Rector/New_/RegexToSlugConstraintRector.php diff --git a/config/sets/symfony/symfony7/symfony73.php b/config/sets/symfony/symfony7/symfony73.php index de11a936..b01bf298 100644 --- a/config/sets/symfony/symfony7/symfony73.php +++ b/config/sets/symfony/symfony7/symfony73.php @@ -9,4 +9,5 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->import(__DIR__ . '/symfony73/symfony73-console.php'); $rectorConfig->import(__DIR__ . '/symfony73/symfony73-twig-bundle.php'); + $rectorConfig->import(__DIR__ . '/symfony73/symfony73-validator.php'); }; diff --git a/config/sets/symfony/symfony7/symfony73/symfony73-validator.php b/config/sets/symfony/symfony7/symfony73/symfony73-validator.php new file mode 100644 index 00000000..c559f72d --- /dev/null +++ b/config/sets/symfony/symfony7/symfony73/symfony73-validator.php @@ -0,0 +1,11 @@ +rule(RegexToSlugConstraintRector::class); +}; diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace.php.inc new file mode 100644 index 00000000..5cd67406 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_all_params.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_all_params.php.inc new file mode 100644 index 00000000..a959abba --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_all_params.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_param_array_syntax.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_param_array_syntax.php.inc new file mode 100644 index 00000000..76fe8c9e --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_param_array_syntax.php.inc @@ -0,0 +1,31 @@ + '/^[a-z0-9]+(?:-[a-z0-9]+)*$/']); + } +} + +?> +----- + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_start_and_end_tags.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_start_and_end_tags.php.inc new file mode 100644 index 00000000..e6aa84a8 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_start_and_end_tags.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_with_alias.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_with_alias.php.inc new file mode 100644 index 00000000..31e255d7 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/replace_with_alias.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_diffrent_regex.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_diffrent_regex.php.inc new file mode 100644 index 00000000..5c841532 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_diffrent_regex.php.inc @@ -0,0 +1,15 @@ + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_variable_regex.php.inc b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_variable_regex.php.inc new file mode 100644 index 00000000..46186cf6 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/Fixture/skip_variable_regex.php.inc @@ -0,0 +1,15 @@ + diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/RegexToSlugConstraintRectorTest.php b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/RegexToSlugConstraintRectorTest.php new file mode 100644 index 00000000..6bbb0dc9 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/RegexToSlugConstraintRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/config/configured_rule.php b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/config/configured_rule.php new file mode 100644 index 00000000..92e04970 --- /dev/null +++ b/rules-tests/Symfony73/Rector/New_/RegexToSlugConstraintRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RegexToSlugConstraintRector::class); +}; diff --git a/rules/Symfony73/Rector/New_/RegexToSlugConstraintRector.php b/rules/Symfony73/Rector/New_/RegexToSlugConstraintRector.php new file mode 100644 index 00000000..2f74b303 --- /dev/null +++ b/rules/Symfony73/Rector/New_/RegexToSlugConstraintRector.php @@ -0,0 +1,173 @@ +> + */ + public function getNodeTypes(): array + { + return [New_::class]; + } + + /** + * @param New_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isRegexConstraint($node)) { + return null; + } + + $patternValue = $this->getPatternValue($node); + if (! $patternValue instanceof String_) { + return null; + } + + if (! $this->isSlugPattern($patternValue->value)) { + return null; + } + + $newArgs = $this->getExpectedArgs($node); + + return new New_(new Name('Symfony\Component\Validator\Constraints\Slug'), $newArgs); + } + + private function isRegexConstraint(New_ $node): bool + { + if (! $node->class instanceof Name) { + return false; + } + + $className = $this->getName($node->class); + if ($className === 'Symfony\Component\Validator\Constraints\Regex') { + return true; + } + + return $className === 'Assert\Regex'; + } + + private function getPatternValue(New_ $node): ?Node + { + foreach ($node->args as $arg) { + if (! $arg instanceof Arg) { + continue; + } + + if ($arg->name !== null && $this->isName($arg->name, 'pattern')) { + return $arg->value; + } + + if ($arg->name === null) { + if ($arg->value instanceof String_) { + return $arg->value; + } + + if ($arg->value instanceof Node\Expr\Array_) { + foreach ($arg->value->items as $item) { + if ($item === null) { + continue; + } + + if ($item->key instanceof String_ && $item->key->value === 'pattern') { + return $item->value; + } + } + } + } + } + + return null; + } + + private function isSlugPattern(string $pattern): bool + { + if (preg_match(self::SLUG_PATTERN, $pattern)) { + return true; + } + + $unanchoredPattern = trim($pattern, '/^$'); + return $unanchoredPattern === '[a-z0-9]+(?:-[a-z0-9]+)*'; + } + + /** + * Get only the message, groups, payload, and options arguments from the original node + * + * @return array + */ + private function getExpectedArgs(New_ $node): array + { + $newArgs = []; + foreach ($node->args as $arg) { + if (! $arg instanceof Arg) { + continue; + } + + if ($arg->name !== null) { + if ($this->isName($arg->name, 'message') || + $this->isName($arg->name, 'groups') || + $this->isName($arg->name, 'payload') || + $this->isName($arg->name, 'options')) { + $newArgs[] = $arg; + } + } + } + + return $newArgs; + } +}