commit:     4247e10d9c266ac1f6aac48b0c67f1092dde1d78
Author:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
AuthorDate: Fri Dec 30 19:27:23 2022 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Mon Jan  2 20:11:43 2023 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/pkgcheck.git/commit/?id=4247e10d

ProvidedEclassInherit: new check for inheriting provided eclases

Resolves: https://github.com/pkgcore/pkgcheck/issues/504
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 src/pkgcheck/checks/eclass.py                      | 103 ++++++++++++++-------
 .../ProvidedEclassInherit/expected.json            |   1 +
 .../ProvidedEclassInherit/fix.patch                |  10 ++
 .../ProvidedEclassInherit-0.ebuild                 |  11 +++
 4 files changed, 93 insertions(+), 32 deletions(-)

diff --git a/src/pkgcheck/checks/eclass.py b/src/pkgcheck/checks/eclass.py
index 5c4f205f..862dbb91 100644
--- a/src/pkgcheck/checks/eclass.py
+++ b/src/pkgcheck/checks/eclass.py
@@ -98,18 +98,36 @@ class MisplacedEclassVar(results.LineResult, results.Error):
         return f"invalid pre-inherit placement, line {self.lineno}: 
{self.line!r}"
 
 
+class ProvidedEclassInherit(results.LineResult, results.Style):
+    """Ebuild inherits an eclass which is already provided by another eclass.
+
+    When inheriting an eclass which declares ``@PROVIDES``, those referenced
+    eclasses are guaranteed to be provided by the eclass. Therefore, inheriting
+    them in ebuilds is redundant and should be removed.
+    """
+
+    def __init__(self, provider, **kwargs):
+        super().__init__(**kwargs)
+        self.provider = provider
+
+    @property
+    def desc(self):
+        return f"line {self.lineno}: redundant eclass inherit {self.line!r}, 
provided by {self.provider!r}"
+
+
 class EclassUsageCheck(Check):
     """Scan packages for various eclass-related issues."""
 
     _source = sources.EbuildParseRepoSource
     known_results = frozenset(
-        [
+        {
             DeprecatedEclass,
             DeprecatedEclassVariable,
             DeprecatedEclassFunction,
             DuplicateEclassInherit,
             MisplacedEclassVar,
-        ]
+            ProvidedEclassInherit,
+        }
     )
     required_addons = (addons.eclass.EclassAddon,)
 
@@ -118,15 +136,16 @@ class EclassUsageCheck(Check):
         self.deprecated_eclasses = eclass_addon.deprecated
         self.eclass_cache = eclass_addon.eclasses
 
-    def check_pre_inherits(self, pkg, inherits):
+    def check_pre_inherits(self, pkg, inherits: list[tuple[list[str], int]]):
         """Check for invalid @PRE_INHERIT variable placement."""
-        pre_inherits = {}
         # determine if any inherited eclasses have @PRE_INHERIT variables
-        for eclasses, lineno in inherits:
-            for eclass in eclasses:
-                for var in self.eclass_cache[eclass].variables:
-                    if var.pre_inherit:
-                        pre_inherits[var.name] = lineno
+        pre_inherits = {
+            var.name: lineno
+            for eclasses, lineno in inherits
+            for eclass in eclasses
+            for var in self.eclass_cache[eclass].variables
+            if var.pre_inherit
+        }
 
         # scan for any misplaced @PRE_INHERIT variables
         if pre_inherits:
@@ -137,22 +156,23 @@ class EclassUsageCheck(Check):
                     line = pkg.node_str(node)
                     yield MisplacedEclassVar(var_name, line=line, 
lineno=lineno + 1, pkg=pkg)
 
-    def check_deprecated_variables(self, pkg, inherits):
-        """Check for usage of @DEPRECATED variables or functions."""
-        deprecated = {}
+    def check_deprecated_variables(self, pkg, inherits: list[tuple[list[str], 
int]]):
+        """Check for usage of @DEPRECATED variables."""
         # determine if any inherited eclasses have @DEPRECATED variables
-        for eclasses, _ in inherits:
-            for eclass in eclasses:
-                for var in self.eclass_cache[eclass].variables:
-                    if var.deprecated:
-                        deprecated[var.name] = var.deprecated
+        deprecated = {
+            var.name: var.deprecated
+            for eclasses, _ in inherits
+            for eclass in eclasses
+            for var in self.eclass_cache[eclass].variables
+            if var.deprecated
+        }
 
         # scan for usage of @DEPRECATED variables
         if deprecated:
             for node, _ in bash.var_query.captures(pkg.tree.root_node):
                 var_name = pkg.node_str(node)
-                lineno, _colno = node.start_point
                 if var_name in deprecated:
+                    lineno, _colno = node.start_point
                     line = pkg.node_str(node)
                     replacement = deprecated[var_name]
                     if not isinstance(replacement, str):
@@ -161,22 +181,23 @@ class EclassUsageCheck(Check):
                         var_name, replacement, line=line, lineno=lineno + 1, 
pkg=pkg
                     )
 
-    def check_deprecated_functions(self, pkg, inherits):
-        """Check for usage of @DEPRECATED variables or functions."""
-        deprecated = {}
-        # determine if any inherited eclasses have @DEPRECATED variables or 
functions
-        for eclasses, _ in inherits:
-            for eclass in eclasses:
-                for func in self.eclass_cache[eclass].functions:
-                    if func.deprecated:
-                        deprecated[func.name] = func.deprecated
+    def check_deprecated_functions(self, pkg, inherits: list[tuple[list[str], 
int]]):
+        """Check for usage of @DEPRECATED functions."""
+        # determine if any inherited eclasses have @DEPRECATED functions
+        deprecated = {
+            func.name: func.deprecated
+            for eclasses, _ in inherits
+            for eclass in eclasses
+            for func in self.eclass_cache[eclass].functions
+            if func.deprecated
+        }
 
         # scan for usage of @DEPRECATED functions
         if deprecated:
             for node, _ in bash.cmd_query.captures(pkg.tree.root_node):
                 func_name = pkg.node_str(node.child_by_field_name("name"))
-                lineno, _colno = node.start_point
                 if func_name in deprecated:
+                    lineno, _colno = node.start_point
                     line = pkg.node_str(node)
                     replacement = deprecated[func_name]
                     if not isinstance(replacement, str):
@@ -185,10 +206,22 @@ class EclassUsageCheck(Check):
                         func_name, replacement, line=line, lineno=lineno + 1, 
pkg=pkg
                     )
 
+    def check_provided_eclasses(self, pkg, inherits: list[tuple[list[str], 
int]]):
+        """Check for usage of eclasses (i.e. redundant inherits) that are
+        provided by another inherited eclass."""
+        provided_eclasses = {
+            provided: (eclass, lineno + 1)
+            for eclasses, lineno in inherits
+            for eclass in eclasses
+            for provided in 
pkg.inherit.intersection(self.eclass_cache[eclass].provides)
+        }
+        for provided, (eclass, lineno) in provided_eclasses.items():
+            yield ProvidedEclassInherit(eclass, pkg=pkg, line=provided, 
lineno=lineno)
+
     def feed(self, pkg):
         if pkg.inherit:
             inherited = set()
-            inherits = []
+            inherits: list[tuple[list[str], int]] = []
             for node, _ in bash.cmd_query.captures(pkg.tree.root_node):
                 name = pkg.node_str(node.child_by_field_name("name"))
                 if name == "inherit":
@@ -207,6 +240,7 @@ class EclassUsageCheck(Check):
                                     eclass, line=call, lineno=lineno + 1, 
pkg=pkg
                                 )
 
+            yield from self.check_provided_eclasses(pkg, inherits)
             # verify @PRE_INHERIT variable placement
             yield from self.check_pre_inherits(pkg, inherits)
             # verify @DEPRECATED variables or functions
@@ -281,7 +315,7 @@ class EclassParseCheck(Check):
                 for var_node, _ in bash.var_query.captures(func_node):
                     var_name = eclass.node_str(var_node)
                     if var_name in variables:
-                        lineno, colno = var_node.start_point
+                        lineno, _colno = var_node.start_point
                         usage[var_name].add(lineno + 1)
                 for var, lines in sorted(usage.items()):
                     yield EclassVariableScope(
@@ -369,7 +403,12 @@ class EclassCheck(Check):
 
     _source = sources.EclassRepoSource
     known_results = frozenset(
-        [EclassBashSyntaxError, EclassDocError, EclassDocMissingFunc, 
EclassDocMissingVar]
+        [
+            EclassBashSyntaxError,
+            EclassDocError,
+            EclassDocMissingFunc,
+            EclassDocMissingVar,
+        ]
     )
 
     def __init__(self, *args):
@@ -393,7 +432,7 @@ class EclassCheck(Check):
             lineno = 0
             error = []
             for line in p.stderr.splitlines():
-                path, line, msg = line.split(": ", 2)
+                _path, line, msg = line.split(": ", 2)
                 lineno = line[5:]
                 error.append(msg.strip("\n"))
             error = ": ".join(error)

diff --git 
a/testdata/data/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/expected.json
 
b/testdata/data/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/expected.json
new file mode 100644
index 00000000..397c0644
--- /dev/null
+++ 
b/testdata/data/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/expected.json
@@ -0,0 +1 @@
+{"__class__": "ProvidedEclassInherit", "category": "EclassUsageCheck", 
"package": "ProvidedEclassInherit", "version": "0", "line": "inherit", 
"lineno": 2, "provider": "deep-provided-inherit"}

diff --git 
a/testdata/data/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/fix.patch 
b/testdata/data/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/fix.patch
new file mode 100644
index 00000000..607e8caf
--- /dev/null
+++ 
b/testdata/data/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/fix.patch
@@ -0,0 +1,10 @@
+diff -Naur 
eclass/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild 
fixed/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild
+--- 
eclass/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild    
   2021-05-23 20:23:16.423009026 -0600
++++ 
fixed/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild     
   2021-05-23 20:23:43.734588313 -0600
+@@ -1,5 +1,5 @@
+ EAPI=7
+-inherit inherit deep-provided-inherit
++inherit deep-provided-inherit
+ DESCRIPTION="Ebuild inheriting provided eclass"
+ HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+ SLOT="0"

diff --git 
a/testdata/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild
 
b/testdata/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild
new file mode 100644
index 00000000..cd8585ea
--- /dev/null
+++ 
b/testdata/repos/eclass/EclassUsageCheck/ProvidedEclassInherit/ProvidedEclassInherit-0.ebuild
@@ -0,0 +1,11 @@
+EAPI=7
+inherit inherit deep-provided-inherit
+DESCRIPTION="Ebuild inheriting provided eclass"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+SLOT="0"
+LICENSE="BSD"
+
+src_prepare() {
+    inherit_public_func
+    deep-provided-inherit_public_func
+}

Reply via email to