Repository: FriendsOfPHP/PHP-CS-Fixer (php)

Issue:
Regex in general_phpdoc_annotation_remove
## Support question

We want to forbid an annotation `@group`.
We want to use the PHP-CS-Fixer `general_phpdoc_annotation_remove` rule for this.

```php
'general_phpdoc_annotation_remove' => [
    'annotations' => [
        'group',
    ],
],
```

```php
/**
 * @group example
 * @GROup example
/*
function example() { ... }
```

The first annotation get removed, the second not. The rule is not case insensitive. 

Can I, how can I use regex for this rule to catch all annotations?

Pull request description:
feature: general_phpdoc_annotation_remove - allow add case_sensitive option


Closes #6657

Patch:
diff --git a/doc/list.rst b/doc/list.rst
index 16b29e810..f82ee28e4 100644
--- a/doc/list.rst
+++ b/doc/list.rst
@@ -848,6 +848,10 @@ List of Available Rules
      | List of annotations to remove, e.g. `["author"]`.
      | Allowed types: ``array``
      | Default value: ``[]``
+   - | ``case_sensitive``
+     | Should annotations be case sensitive.
+     | Allowed types: ``bool``
+     | Default value: ``true``
 
 
    `Source PhpCsFixer\\Fixer\\Phpdoc\\GeneralPhpdocAnnotationRemoveFixer <./../src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php>`_
diff --git a/doc/rules/phpdoc/general_phpdoc_annotation_remove.rst b/doc/rules/phpdoc/general_phpdoc_annotation_remove.rst
index 38c26974c..d99c7b378 100644
--- a/doc/rules/phpdoc/general_phpdoc_annotation_remove.rst
+++ b/doc/rules/phpdoc/general_phpdoc_annotation_remove.rst
@@ -16,6 +16,15 @@ Allowed types: ``array``
 
 Default value: ``[]``
 
+``case_sensitive``
+~~~~~~~~~~~~~~~~~~
+
+Should annotations be case sensitive.
+
+Allowed types: ``bool``
+
+Default value: ``true``
+
 Examples
 --------
 
@@ -32,12 +41,30 @@ With configuration: ``['annotations' => ['author']]``.
     /**
      * @internal
    - * @author John Doe
+     * @AuThOr Jane Doe
      */
     function foo() {}
 
 Example #2
 ~~~~~~~~~~
 
+With configuration: ``['annotations' => ['author'], 'case_sensitive' => false]``.
+
+.. code-block:: diff
+
+   --- Original
+   +++ New
+    <?php
+    /**
+     * @internal
+   - * @author John Doe
+   - * @AuThOr Jane Doe
+     */
+    function foo() {}
+
+Example #3
+~~~~~~~~~~
+
 With configuration: ``['annotations' => ['package', 'subpackage']]``.
 
 .. code-block:: diff
diff --git a/src/DocBlock/DocBlock.php b/src/DocBlock/DocBlock.php
index e1a25ff09..5a0b2f40a 100644
--- a/src/DocBlock/DocBlock.php
+++ b/src/DocBlock/DocBlock.php
@@ -185,15 +185,14 @@ public function getAnnotation(int $pos): ?Annotation
      */
     public function getAnnotationsOfType($types): array
     {
+        $typesToSearchFor = (array) $types;
+
         $annotations = [];
-        $types = (array) $types;
 
         foreach ($this->getAnnotations() as $annotation) {
-            $tag = $annotation->getTag()->getName();
-            foreach ($types as $type) {
-                if ($type === $tag) {
-                    $annotations[] = $annotation;
-                }
+            $tagName = $annotation->getTag()->getName();
+            if (\in_array($tagName, $typesToSearchFor, true)) {
+                $annotations[] = $annotation;
             }
         }
 
diff --git a/src/Fixer/LanguageConstruct/IsNullFixer.php b/src/Fixer/LanguageConstruct/IsNullFixer.php
index 9068abfe3..673c558bb 100644
--- a/src/Fixer/LanguageConstruct/IsNullFixer.php
+++ b/src/Fixer/LanguageConstruct/IsNullFixer.php
@@ -135,11 +135,11 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
                 }
             }
 
-            // edge cases: is_null() followed/preceded by ==, ===, !=, !==, <>
+            // edge cases: is_null() followed/preceded by ==, ===, !=, !==, <>, (int-or-other-casting)
             $parentLeftToken = $tokens[$tokens->getPrevMeaningfulToken($isNullIndex)];
             $parentRightToken = $tokens[$tokens->getNextMeaningfulToken($referenceEnd)];
             $parentOperations = [T_IS_EQUAL, T_IS_NOT_EQUAL, T_IS_IDENTICAL, T_IS_NOT_IDENTICAL];
-            $wrapIntoParentheses = $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations);
+            $wrapIntoParentheses = $parentLeftToken->isCast() || $parentLeftToken->isGivenKind($parentOperations) || $parentRightToken->isGivenKind($parentOperations);
 
             // possible trailing comma removed
             $prevIndex = $tokens->getPrevMeaningfulToken($referenceEnd);
diff --git a/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php b/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php
index 8920d5626..f8b7211ae 100644
--- a/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php
+++ b/src/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixer.php
@@ -15,6 +15,7 @@
 namespace PhpCsFixer\Fixer\Phpdoc;
 
 use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\DocBlock\Annotation;
 use PhpCsFixer\DocBlock\DocBlock;
 use PhpCsFixer\Fixer\ConfigurableFixerInterface;
 use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
@@ -45,6 +46,7 @@ public function getDefinition(): FixerDefinitionInterface
 /**
  * @internal
  * @author John Doe
+ * @AuThOr Jane Doe
  */
 function foo() {}
 ',
@@ -52,6 +54,17 @@ function foo() {}
                 ),
                 new CodeSample(
                     '<?php
+/**
+ * @internal
+ * @author John Doe
+ * @AuThOr Jane Doe
+ */
+function foo() {}
+',
+                    ['annotations' => ['author'], 'case_sensitive' => false]
+                ),
+                new CodeSample(
+                    '<?php
 /**
  * @author John Doe
  * @package ACME API
@@ -100,7 +113,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
             }
 
             $doc = new DocBlock($token->getContent());
-            $annotations = $doc->getAnnotationsOfType($this->configuration['annotations']);
+            $annotations = $this->getAnnotationsToRemove($doc);
 
             // nothing to do if there are no annotations
             if (0 === \count($annotations)) {
@@ -129,6 +142,35 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn
                 ->setAllowedTypes(['array'])
                 ->setDefault([])
                 ->getOption(),
+            (new FixerOptionBuilder('case_sensitive', 'Should annotations be case sensitive.'))
+                ->setAllowedTypes(['bool'])
+                ->setDefault(true)
+                ->getOption(),
         ]);
     }
+
+    /**
+     * @return list<Annotation>
+     */
+    private function getAnnotationsToRemove(DocBlock $doc): array
+    {
+        if (true === $this->configuration['case_sensitive']) {
+            return $doc->getAnnotationsOfType($this->configuration['annotations']);
+        }
+
+        $typesToSearchFor = array_map(function (string $type): string {
+            return strtolower($type);
+        }, $this->configuration['annotations']);
+
+        $annotations = [];
+
+        foreach ($doc->getAnnotations() as $annotation) {
+            $tagName = strtolower($annotation->getTag()->getName());
+            if (\in_array($tagName, $typesToSearchFor, true)) {
+                $annotations[] = $annotation;
+            }
+        }
+
+        return $annotations;
+    }
 }


Test patch:
diff --git a/tests/Fixer/LanguageConstruct/IsNullFixerTest.php b/tests/Fixer/LanguageConstruct/IsNullFixerTest.php
index 83f2881e0..eb09d0b07 100644
--- a/tests/Fixer/LanguageConstruct/IsNullFixerTest.php
+++ b/tests/Fixer/LanguageConstruct/IsNullFixerTest.php
@@ -237,6 +237,10 @@ public function provideFixCases(): array
                 '<?php $a === (null === ($a ? $x : $y ));',
                 '<?php $a === is_null($a ? $x : $y, );',
             ],
+            [
+                '<?php $a === (int) (null === $x) + (int) (null !== $y);',
+                '<?php $a === (int) is_null($x) + (int) !is_null($y);',
+            ],
         ];
     }
 }
diff --git a/tests/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixerTest.php b/tests/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixerTest.php
index ca2b647dc..390307d47 100644
--- a/tests/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixerTest.php
+++ b/tests/Fixer/Phpdoc/GeneralPhpdocAnnotationRemoveFixerTest.php
@@ -181,6 +181,38 @@ function hello($name)
 ',
                 ['annotations' => ['noinspection']],
             ],
+
+            [
+                '<?php
+/**
+* @internal
+* @AuThOr Jane Doe
+*/
+function foo() {}',
+                '<?php
+/**
+* @internal
+* @author John Doe
+* @AuThOr Jane Doe
+*/
+function foo() {}',
+                ['annotations' => ['author'], 'case_sensitive' => true],
+            ],
+            [
+                '<?php
+/**
+* @internal
+*/
+function foo() {}',
+                '<?php
+/**
+* @internal
+* @author John Doe
+* @AuThOr Jane Doe
+*/
+function foo() {}',
+                ['annotations' => ['author'], 'case_sensitive' => false],
+            ],
         ];
     }
 }