From 08370e2fc7e6c9040a6c1473c38a731bf131aad7 Mon Sep 17 00:00:00 2001
From: Alexander Schnitzler <git@alexanderschnitzler.de>
Date: Sat, 25 Nov 2017 16:25:02 +0100
Subject: [PATCH] [FEATURE] Replace @ignorevalidation with
 @Extbase\IgnoreValidation

This patch introduces the "TYPO3\CMS\Extbase\Annotation\IgnoreValidation"
annotation that replaces the @ignorevalidation annotation which is
deprecated from now on.

Releases: master
Resolves: #83094
Change-Id: Ic8b05d754a9d5da3097b971780f1c229b06ac1c8
Reviewed-on: https://review.typo3.org/54762
Reviewed-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Tested-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Reviewed-by: Henning Liebe <h.liebe@neusta.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 typo3/sysext/core/Classes/Core/Bootstrap.php  |  5 +-
 ...O3CMSExtbaseAnnotationIgnoreValidation.rst | 32 +++++++
 ...O3CMSExtbaseAnnotationIgnoreValidation.rst | 69 +++++++++++++++
 .../Classes/Annotation/IgnoreValidation.php   | 42 +++++++++
 .../Mvc/Controller/ActionController.php       | 10 +--
 .../sysext/extbase/Classes/Mvc/Dispatcher.php |  2 +-
 .../Classes/Reflection/ClassSchema.php        | 19 ++++-
 .../Tests/Unit/Reflection/ClassSchemaTest.php |  9 ++
 ...WithIgnoreValidationDoctrineAnnotation.php | 35 ++++++++
 .../Reflection/ClassSchemaTest.php            |  6 ++
 ...ntrollerWithIgnorevalidationAnnotation.php | 31 +++++++
 .../Classes/Controller/UpgradeController.php  |  5 ++
 .../Php/Matcher/MethodAnnotationMatcher.php   | 85 +++++++++++++++++++
 .../Php/MethodAnnotationMatcher.php           |  9 ++
 .../MethodAnnotationMatcherFixture.php        | 29 +++++++
 .../Matcher/MethodAnnotationMatcherTest.php   | 61 +++++++++++++
 16 files changed, 440 insertions(+), 9 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst
 create mode 100644 typo3/sysext/extbase/Classes/Annotation/IgnoreValidation.php
 create mode 100644 typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyControllerWithIgnoreValidationDoctrineAnnotation.php
 create mode 100644 typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithIgnorevalidationAnnotation.php
 create mode 100644 typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/MethodAnnotationMatcher.php
 create mode 100644 typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodAnnotationMatcher.php
 create mode 100644 typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/MethodAnnotationMatcherFixture.php
 create mode 100644 typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/MethodAnnotationMatcherTest.php

diff --git a/typo3/sysext/core/Classes/Core/Bootstrap.php b/typo3/sysext/core/Classes/Core/Bootstrap.php
index 25c542dd679b..a6649baddb5c 100644
--- a/typo3/sysext/core/Classes/Core/Bootstrap.php
+++ b/typo3/sysext/core/Classes/Core/Bootstrap.php
@@ -237,10 +237,13 @@ class Bootstrap
         AnnotationReader::addGlobalIgnoredName('cascade');
         AnnotationReader::addGlobalIgnoredName('ignorevalidation');
         AnnotationReader::addGlobalIgnoredName('cli');
-        AnnotationReader::addGlobalIgnoredName('flushesCashes');
+        AnnotationReader::addGlobalIgnoredName('flushesCaches');
         AnnotationReader::addGlobalIgnoredName('uuid');
         AnnotationReader::addGlobalIgnoredName('identity');
 
+        // Annotations used in unit tests
+        AnnotationReader::addGlobalIgnoredName('test');
+
         // Annotations that control the extension scanner
         AnnotationReader::addGlobalIgnoredName('extensionScannerIgnoreFile');
         AnnotationReader::addGlobalIgnoredName('extensionScannerIgnoreLine');
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst
new file mode 100644
index 000000000000..0b17b16907bf
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst
@@ -0,0 +1,32 @@
+.. include:: ../../Includes.txt
+
+===================================================================================================
+Deprecation: #83094 - Replace @ignorevalidation with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation
+===================================================================================================
+
+See :issue:`83094`
+
+Description
+===========
+
+The `@ignorevalidation` annotation has been deprecated and must be replaced with the doctrine annotation `@TYPO3\CMS\Extbase\Annotation\IgnoreValidation`.
+
+
+Impact
+======
+
+From version 9.0 on, `@ignorevalidation` is deprecated and will be removed in version 10.
+
+
+Affected Installations
+======================
+
+All extensions that use `@ignorevalidation`
+
+
+Migration
+=========
+
+Use `@TYPO3\CMS\Extbase\Annotation\IgnoreValidation` instead.
+
+.. index:: PHP-API, ext:extbase, FullyScanned
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst
new file mode 100644
index 000000000000..c1caf55172e7
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst
@@ -0,0 +1,69 @@
+.. include:: ../../Includes.txt
+
+===============================================================================================
+Feature: #83094 - Replace @ignorevalidation with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation
+===============================================================================================
+
+See :issue:`83094`
+
+Description
+===========
+
+As a successor to the `@ignorevalidation` annotation, the doctrine annotation `@TYPO3\CMS\Extbase\Annotation\IgnoreValidation` has been introduced.
+
+Example:
+
+.. code-block:: php
+
+	/**
+	 * @TYPO3\CMS\Extbase\Annotation\IgnoreValidation("param")
+	 */
+	public function method($param)
+	{
+	}
+
+Doctrine annotations are actual defined classes, therefore you can also use the annotation with a use statement.
+
+Example:
+
+.. code-block:: php
+
+	use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;
+
+.. code-block:: php
+
+	/**
+	 * @IgnoreValidation("param")
+	 */
+	public function method($param)
+	{
+	}
+
+Used annotations can also be aliased which the core will most likely be using a lot in the future.
+
+Example:
+
+.. code-block:: php
+
+	use TYPO3\CMS\Extbase\Annotation as Extbase;
+
+.. code-block:: php
+
+	/**
+	 * @Extbase\IgnoreValidation("param")
+	 */
+	public function method($param)
+	{
+	}
+
+.. tip::
+
+	Please mind that `@TYPO3\CMS\Extbase\Annotation\IgnoreValidation` does no longer accept parameter names prepended with dollar signs `$`.
+	Example: `@ignorevalidation $foo` becomes `@Extbase\IgnoreValidation("foo")`
+
+Impact
+======
+
+In 9.x there is no actual impact. Both the simple `@ignorevalidation` and `@TYPO3\CMS\Extbase\Annotation\IgnoreValidation` can be used side by side. However, `@ignorevalidation` is deprecated in 9.x and will be removed in version 10.
+
+.. index:: PHP-API, ext:extbase, FullyScanned
diff --git a/typo3/sysext/extbase/Classes/Annotation/IgnoreValidation.php b/typo3/sysext/extbase/Classes/Annotation/IgnoreValidation.php
new file mode 100644
index 000000000000..112edfe7f7ea
--- /dev/null
+++ b/typo3/sysext/extbase/Classes/Annotation/IgnoreValidation.php
@@ -0,0 +1,42 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Extbase\Annotation;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class IgnoreValidation
+{
+    /**
+     * @var string
+     */
+    public $argumentName;
+
+    /**
+     * @param array $values
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(array $values)
+    {
+        if (isset($values['value'])) {
+            $this->argumentName = $values['value'];
+        } elseif (isset($values['argumentName'])) {
+            $this->argumentName = $values['argumentName'];
+        }
+    }
+}
diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
index ebc0bba86111..d5af3a1a4d03 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
@@ -316,18 +316,16 @@ class ActionController extends AbstractController
             $actionResult = call_user_func_array([$this, $this->actionMethodName], $preparedArguments);
         } else {
             $methodTagsValues = $this->reflectionService->getMethodTagsValues(static::class, $this->actionMethodName);
-            $ignoreValidationAnnotations = [];
-            if (isset($methodTagsValues['ignorevalidation'])) {
-                $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'];
-            }
-            // if there exist errors which are not ignored with @ignorevalidation => call error method
+            $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'] ?? [];
+
+            // if there exist errors which are not ignored with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation => call error method
             // else => call action method
             $shouldCallActionMethod = true;
             foreach ($validationResult->getSubResults() as $argumentName => $subValidationResult) {
                 if (!$subValidationResult->hasErrors()) {
                     continue;
                 }
-                if (array_search('$' . $argumentName, $ignoreValidationAnnotations) !== false) {
+                if (in_array($argumentName, $ignoreValidationAnnotations, true)) {
                     continue;
                 }
                 $shouldCallActionMethod = false;
diff --git a/typo3/sysext/extbase/Classes/Mvc/Dispatcher.php b/typo3/sysext/extbase/Classes/Mvc/Dispatcher.php
index 6d82759185e6..5bd8709e3854 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Dispatcher.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Dispatcher.php
@@ -65,7 +65,7 @@ class Dispatcher implements \TYPO3\CMS\Core\SingletonInterface
         $dispatchLoopCount = 0;
         while (!$request->isDispatched()) {
             if ($dispatchLoopCount++ > 99) {
-                throw new \TYPO3\CMS\Extbase\Mvc\Exception\InfiniteLoopException('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations. Most probably, a @ignorevalidation annotation is missing on re-displaying a form with validation errors.', 1217839467);
+                throw new \TYPO3\CMS\Extbase\Mvc\Exception\InfiniteLoopException('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations. Most probably, a @' . \TYPO3\CMS\Extbase\Annotation\IgnoreValidation::class . ' annotation is missing on re-displaying a form with validation errors.', 1217839467);
             }
             $controller = $this->resolveController($request);
             try {
diff --git a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
index 8082e894b7fc..7b86160c8c69 100644
--- a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
+++ b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Extbase\Reflection;
 use Doctrine\Common\Annotations\AnnotationReader;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\ClassNamingUtility;
+use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;
 use TYPO3\CMS\Extbase\Annotation\Inject;
 use TYPO3\CMS\Extbase\Annotation\ORM\Cascade;
 use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
@@ -287,6 +288,8 @@ class ClassSchema
      */
     protected function reflectMethods(\ReflectionClass $reflectionClass)
     {
+        $annotationReader = new AnnotationReader();
+
         foreach ($reflectionClass->getMethods() as $reflectionMethod) {
             $methodName = $reflectionMethod->getName();
 
@@ -302,7 +305,21 @@ class ClassSchema
             $docCommentParser = new DocCommentParser(true);
             $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
             foreach ($docCommentParser->getTagsValues() as $tag => $values) {
-                $this->methods[$methodName]['tags'][$tag] = $values;
+                if ($tag === 'ignorevalidation') {
+                    trigger_error(
+                        'Tagging methods with @ignorevalidation is deprecated and will be removed in TYPO3 v10.0.',
+                        E_USER_DEPRECATED
+                    );
+                }
+                $this->methods[$methodName]['tags'][$tag] = array_map(function ($value) {
+                    return ltrim($value, '$');
+                }, $values);
+            }
+
+            foreach ($annotationReader->getMethodAnnotations($reflectionMethod) as $annotation) {
+                if ($annotation instanceof IgnoreValidation) {
+                    $this->methods[$methodName]['tags']['ignorevalidation'][] = $annotation->argumentName;
+                }
             }
 
             $this->methods[$methodName]['description'] = $docCommentParser->getDescription();
diff --git a/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php b/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php
index cd266a52e95a..db68e415a17f 100644
--- a/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php
@@ -276,6 +276,15 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         static::assertNull($propertyDefinition['annotations']['cascade']);
     }
 
+    public function testClassSchemaDetectsIgnoreValidationAnnotation()
+    {
+        $classSchema = new ClassSchema(Fixture\DummyControllerWithIgnoreValidationDoctrineAnnotation::class);
+        static::assertTrue(isset($classSchema->getMethod('someAction')['tags']['ignorevalidation']));
+        static::assertTrue(in_array('foo', $classSchema->getMethod('someAction')['tags']['ignorevalidation'], true));
+        static::assertTrue(in_array('bar', $classSchema->getMethod('someAction')['tags']['ignorevalidation'], true));
+        static::assertFalse(in_array('baz', $classSchema->getMethod('someAction')['tags']['ignorevalidation'], true));
+    }
+
     public function testClassSchemaDetectsTypeAndElementType()
     {
         $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
diff --git a/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyControllerWithIgnoreValidationDoctrineAnnotation.php b/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyControllerWithIgnoreValidationDoctrineAnnotation.php
new file mode 100644
index 000000000000..cff943601aae
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyControllerWithIgnoreValidationDoctrineAnnotation.php
@@ -0,0 +1,35 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Extbase\Annotation\IgnoreValidation;
+
+/**
+ * Dummy controller with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation annotation
+ */
+class DummyControllerWithIgnoreValidationDoctrineAnnotation
+{
+    /**
+     * @param $foo
+     * @param $bar
+     * @ignorevalidation("foo")
+     * @IgnoreValidation("bar")
+     */
+    public function someAction($foo, $bar)
+    {
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php
index 84fc91e184c8..92be1f1f7e25 100644
--- a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php
+++ b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php
@@ -54,4 +54,10 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $propertyDefinition = $classSchema->getProperty('propertyWithCascadeAnnotationWithoutVarAnnotation');
         static::assertNull($propertyDefinition['annotations']['cascade']);
     }
+
+    public function testClassSchemaDetectsIgnoreValidationAnnotation()
+    {
+        $classSchema = new ClassSchema(Fixture\DummyControllerWithIgnorevalidationAnnotation::class);
+        static::assertTrue(isset($classSchema->getMethod('someAction')['tags']['ignorevalidation']));
+    }
 }
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithIgnorevalidationAnnotation.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithIgnorevalidationAnnotation.php
new file mode 100644
index 000000000000..ec47c39cd63b
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithIgnorevalidationAnnotation.php
@@ -0,0 +1,31 @@
+<?php
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Fixture class with getters and setters
+ */
+class DummyControllerWithIgnorevalidationAnnotation
+{
+    /**
+     * @param $someArgument
+     * @ignorevalidation $someArgument
+     * @ignorevalidation $foo
+     * @ignorevalidation $foo $bar
+     */
+    public function someAction($someArgument)
+    {
+    }
+}
diff --git a/typo3/sysext/install/Classes/Controller/UpgradeController.php b/typo3/sysext/install/Classes/Controller/UpgradeController.php
index 9e84755e5578..fe3dd966bf59 100644
--- a/typo3/sysext/install/Classes/Controller/UpgradeController.php
+++ b/typo3/sysext/install/Classes/Controller/UpgradeController.php
@@ -42,6 +42,7 @@ use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ClassNameMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstantMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\FunctionCallMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\InterfaceMethodChangedMatcher;
+use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodAnnotationMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodArgumentDroppedMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodArgumentDroppedStaticMatcher;
 use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodArgumentRequiredMatcher;
@@ -104,6 +105,10 @@ class UpgradeController extends AbstractController
             'class' => PropertyAnnotationMatcher::class,
             'configurationFile' => 'EXT:install/Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php',
         ],
+        [
+            'class' => MethodAnnotationMatcher::class,
+            'configurationFile' => 'EXT:install/Configuration/ExtensionScanner/Php/MethodAnnotationMatcher.php',
+        ],
         [
             'class' => FunctionCallMatcher::class,
             'configurationFile' => 'EXT:install/Configuration/ExtensionScanner/Php/FunctionCallMatcher.php',
diff --git a/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/MethodAnnotationMatcher.php b/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/MethodAnnotationMatcher.php
new file mode 100644
index 000000000000..43013e7233b3
--- /dev/null
+++ b/typo3/sysext/install/Classes/ExtensionScanner/Php/Matcher/MethodAnnotationMatcher.php
@@ -0,0 +1,85 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use PhpParser\Comment\Doc;
+use PhpParser\Node;
+use PhpParser\Node\Stmt\ClassMethod;
+
+/**
+ * Find usages of method annotations
+ */
+class MethodAnnotationMatcher extends AbstractCoreMatcher
+{
+    /**
+     * Prepare $this->flatMatcherDefinitions once and validate config
+     *
+     * @param array $matcherDefinitions Incoming main configuration
+     */
+    public function __construct(array $matcherDefinitions)
+    {
+        $this->matcherDefinitions = $matcherDefinitions;
+        $this->validateMatcherDefinitions();
+    }
+
+    /**
+     * Called by PhpParser.
+     * Test for method annotations (strong match)
+     *
+     * @param Node $node
+     */
+    public function enterNode(Node $node)
+    {
+        if ($node instanceof ClassMethod
+            && ($docComment = $node->getDocComment()) instanceof Doc
+            && !$this->isFileIgnored($node)
+            && !$this->isLineIgnored($node)
+        ) {
+            $isPossibleMatch = false;
+            $match = [
+                'restFiles' => [],
+                'line' => $node->getAttribute('startLine'),
+                'indicator' => 'strong',
+            ];
+
+            $matches = [];
+            preg_match_all(
+                '/\s*\s@(?<annotations>[^\s.]*).*\n/',
+                $docComment->getText(),
+                $matches
+            );
+
+            foreach ($matches['annotations'] as $annotation) {
+                $annotation = '@' . $annotation;
+
+                if (!isset($this->matcherDefinitions[$annotation])) {
+                    continue;
+                }
+
+                $isPossibleMatch = true;
+                $match['message'] = 'Method "' . $node->name . '" uses an ' . $annotation . ' annotation.';
+                $match['restFiles'] = array_unique(array_merge(
+                    $match['restFiles'],
+                    $this->matcherDefinitions[$annotation]['restFiles']
+                ));
+            }
+
+            if ($isPossibleMatch) {
+                $this->matches[] = $match;
+            }
+        }
+    }
+}
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodAnnotationMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodAnnotationMatcher.php
new file mode 100644
index 000000000000..3aafed70fc04
--- /dev/null
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodAnnotationMatcher.php
@@ -0,0 +1,9 @@
+<?php
+return [
+    '@ignorevalidation' => [
+        'restFiles' => [
+            'Feature-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst',
+            'Deprecation-83094-ReplaceIgnorevalidationWithTYPO3CMSExtbaseAnnotationIgnoreValidation.rst',
+        ],
+    ],
+];
diff --git a/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/MethodAnnotationMatcherFixture.php b/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/MethodAnnotationMatcherFixture.php
new file mode 100644
index 000000000000..cc6f0450f7a4
--- /dev/null
+++ b/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/Fixtures/MethodAnnotationMatcherFixture.php
@@ -0,0 +1,29 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Tests\Unit\ExtensionScanner\Php\Matcher\Fixtures;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Fixture file
+ */
+class MethodAnnotationMatcherFixture
+{
+    /**
+     * @inject
+     */
+    public function foo()
+    {
+    }
+}
diff --git a/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/MethodAnnotationMatcherTest.php b/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/MethodAnnotationMatcherTest.php
new file mode 100644
index 000000000000..0aacd651e733
--- /dev/null
+++ b/typo3/sysext/install/Tests/Unit/ExtensionScanner/Php/Matcher/MethodAnnotationMatcherTest.php
@@ -0,0 +1,61 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Install\Tests\Unit\ExtensionScanner\Php\Matcher;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor\NameResolver;
+use PhpParser\ParserFactory;
+use TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\MethodAnnotationMatcher;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class MethodAnnotationMatcherTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function hitsFromFixtureAreFound()
+    {
+        $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
+        $fixtureFile = __DIR__ . '/Fixtures/MethodAnnotationMatcherFixture.php';
+        $statements = $parser->parse(file_get_contents($fixtureFile));
+
+        $traverser = new NodeTraverser();
+        $traverser->addVisitor(new NameResolver());
+
+        $configuration = [
+            '@inject' => [
+                'restFiles' => [
+                    'Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+                    'Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+                ],
+            ],
+        ];
+        $subject = new MethodAnnotationMatcher($configuration);
+        $traverser->addVisitor($subject);
+        $traverser->traverse($statements);
+        $expectedHitLineNumbers = [
+            26,
+        ];
+        $actualHitLineNumbers = [];
+        foreach ($subject->getMatches() as $hit) {
+            $actualHitLineNumbers[] = $hit['line'];
+        }
+        $this->assertEquals($expectedHitLineNumbers, $actualHitLineNumbers);
+    }
+}
-- 
GitLab