Repository: nhoad/flake8-unused-arguments

Issue:
(Feature) Ignore overridden methods in subclasses
It would be great if there were a way to ignore inherited arguments in overridden methods in a subclass. For example, suppose we have the following code:

```py
class BaseClass:
    def execute(self, context):
        ...

class MyClass(BaseClass):
    def execute(self, context):
        # Implementation doesn't use `context`
```

`BaseClass` is defined in a third-party library and I have no control over it. Currently, `context` is invoked by the third-party code as a keyword argument, which means that I can't name the argument `_context` because it'd trigger an exception.

Of course, I can use `noqa: U100` on that line to suppress the error. It's tedious if you have a lot of these, but it's not a blocker. However, there's one case where we don't want to do that, when we add arguments in a compatible way:

```py
class MyClass2(BaseClass):
    def execute(self, context, some_flag=False):
        # Implementation doesn't use `context`
        ...
```

Here, we don't care if the implementation doesn't use arguments defined in the superclass because we need to preserve the signature. _However_, we should still care about arguments that _we_ define that aren't being used.

I'll be the first to admit this is non-trivial. The first implementation I can think of off the top of my head would require you to traverse the MRO and find the method in its parents, then compare call signatures. Proper caching will mitigate the performance impact on large codebases, but it'll still be a pain.

Discussion:

Patch:
diff --git a/README.md b/README.md
index dd58c30..5f892ea 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ This package adds the following warnings:
 Configuration options also exist:
  - `unused-arguments-ignore-abstract-functions` - don't show warnings for abstract functions.
  - `unused-arguments-ignore-overload-functions` - don't show warnings for overload functions.
+ - `unused-arguments-ignore-override-functions` - don't show warnings for overridden functions.
  - `unused-arguments-ignore-stub-functions` - don't show warnings for empty functions.
  - `unused-arguments-ignore-variadic-names` - don't show warnings for unused *args and **kwargs.
  - `unused-arguments-ignore-lambdas` - don't show warnings for all lambdas.
diff --git a/flake8_unused_arguments.py b/flake8_unused_arguments.py
index 88074c7..518bce1 100644
--- a/flake8_unused_arguments.py
+++ b/flake8_unused_arguments.py
@@ -16,6 +16,7 @@ class Plugin:
 
     ignore_abstract = False
     ignore_overload = False
+    ignore_override = False
     ignore_stubs = False
     ignore_variadic_names = False
     ignore_lambdas = False
@@ -45,6 +46,15 @@ class Plugin:
             help="If provided, then unused arguments for functions decorated with overload will be ignored.",
         )
 
+        option_manager.add_option(
+            "--unused-arguments-ignore-override-functions",
+            action="store_true",
+            parse_from_config=True,
+            default=cls.ignore_override,
+            dest="unused_arguments_ignore_override_functions",
+            help="If provided, then unused arguments for functions decorated with override will be ignored.",
+        )
+
         option_manager.add_option(
             "--unused-arguments-ignore-stub-functions",
             action="store_true",
@@ -100,6 +110,7 @@ class Plugin:
     def parse_options(cls, options: optparse.Values) -> None:
         cls.ignore_abstract = options.unused_arguments_ignore_abstract_functions
         cls.ignore_overload = options.unused_arguments_ignore_overload_functions
+        cls.ignore_override = options.unused_arguments_ignore_override_functions
         cls.ignore_stubs = options.unused_arguments_ignore_stub_functions
         cls.ignore_variadic_names = options.unused_arguments_ignore_variadic_names
         cls.ignore_lambdas = options.unused_arguments_ignore_lambdas
@@ -117,6 +128,10 @@ class Plugin:
             if self.ignore_overload and "overload" in decorator_names:
                 continue
 
+            # ignore overridden functions
+            if self.ignore_override and "override" in decorator_names:
+                continue
+
             # ignore abstractmethods, it's not a surprise when they're empty
             if self.ignore_abstract and "abstractmethod" in decorator_names:
                 continue


Test patch:
diff --git a/test_unused_arguments.py b/test_unused_arguments.py
index 9fa6446..caf5d62 100644
--- a/test_unused_arguments.py
+++ b/test_unused_arguments.py
@@ -196,6 +196,24 @@ def test_is_stub_function(function, expected_result):
         ),
         (
             """
+    @override
+    def foo(a):
+        pass
+    """,
+            {"ignore_override": False},
+            [(3, 8, "U100 Unused argument 'a'", "unused argument")],
+        ),
+        (
+            """
+    @override
+    def foo(a):
+        pass
+    """,
+            {"ignore_override": True},
+            [],
+        ),
+        (
+            """
     def foo(a):
         pass
     """,