Source code for pkgcheck.checks.reserved

import re
import string

from pkgcore.ebuild.eapi import EAPI

from .. import addons, bash, results, sources
from . import Check


class _ReservedNameCheck(Check):
    reserved_prefixes = ("__", "abort", "dyn", "prep")
    reserved_substrings = ("hook", "paludis", "portage")  # 'ebuild' is special case
    reserved_ebuild_regex = re.compile(r"(.*[^a-zA-Z])?ebuild.*")

    """Portage variables whose use is half-legitimate and harmless if the package manager doesn't support them."""
    special_whitelist = (
        "EBUILD_DEATH_HOOKS",
        "EBUILD_SUCCESS_HOOKS",
        "PORTAGE_QUIET",
        "PORTAGE_ACTUAL_DISTDIR",
    )

    """Approved good exceptions to using of variables."""
    variables_usage_whitelist = {"EBUILD_PHASE", "EBUILD_PHASE_FUNC"}

    def _check(self, used_type: str, used_names: dict[str, tuple[int, int]]):
        for used_name, (lineno, _) in used_names.items():
            if used_name in self.special_whitelist:
                continue
            test_name = used_name.lower()
            for reserved in self.reserved_prefixes:
                if test_name.startswith(reserved):
                    yield used_name, used_type, reserved, "prefix", lineno + 1
            for reserved in self.reserved_substrings:
                if reserved in test_name:
                    yield used_name, used_type, reserved, "substring", lineno + 1
            if self.reserved_ebuild_regex.match(test_name):
                yield used_name, used_type, "ebuild", "substring", lineno + 1

    def _feed(self, item: bash.ParseTree):
        yield from self._check(
            "function",
            {
                item.node_str(node.child_by_field_name("name")): node.start_point
                for node, _ in bash.func_query.captures(item.tree.root_node)
            },
        )
        used_variables = {
            item.node_str(node.child_by_field_name("name")): node.start_point
            for node, _ in bash.var_assign_query.captures(item.tree.root_node)
        }
        for node, _ in bash.var_query.captures(item.tree.root_node):
            if (name := item.node_str(node)) not in self.variables_usage_whitelist:
                used_variables.setdefault(name, node.start_point)
        yield from self._check("variable", used_variables)


[docs] class EclassReservedName(results.EclassResult, results.Warning): """Eclass uses reserved variable or function name for package manager.""" def __init__( self, used_name: str, used_type: str, reserved_word: str, reserved_type: str, **kwargs ): super().__init__(**kwargs) self.used_name = used_name self.used_type = used_type self.reserved_word = reserved_word self.reserved_type = reserved_type @property def desc(self): return f'{self.eclass}: {self.used_type} name "{self.used_name}" is disallowed because "{self.reserved_word}" is a reserved {self.reserved_type}'
[docs] class EclassReservedCheck(_ReservedNameCheck): """Scan eclasses for reserved function or variable names.""" _source = sources.EclassParseRepoSource known_results = frozenset([EclassReservedName]) required_addons = (addons.eclass.EclassAddon,) def __init__(self, *args, eclass_addon): super().__init__(*args) self.eclass_cache = eclass_addon.eclasses
[docs] def feed(self, eclass: sources._ParsedEclass): for *args, _ in self._feed(eclass): yield EclassReservedName(*args, eclass=eclass.name)
[docs] class EbuildReservedName(results.LineResult, results.Warning): """Ebuild uses reserved variable or function name for package manager.""" def __init__(self, used_type: str, reserved_word: str, reserved_type: str, **kwargs): super().__init__(**kwargs) self.used_type = used_type self.reserved_word = reserved_word self.reserved_type = reserved_type @property def desc(self): return f'line {self.lineno}: {self.used_type} name "{self.line}" is disallowed because "{self.reserved_word}" is a reserved {self.reserved_type}'
[docs] class EbuildSemiReservedName(results.LineResult, results.Warning): """Ebuild uses semi-reserved variable or function name. Ebuild is using in global scope semi-reserved variable or function names, which is likely to clash with future EAPIs. Currently it include single-letter uppercase variables, and ``[A-Z]DEPEND`` variables. """ def __init__(self, used_type: str, **kwargs): super().__init__(**kwargs) self.used_type = used_type @property def desc(self): return f'line {self.lineno}: uses semi-reserved {self.used_type} name "{self.line}", likely to clash with future EAPIs'
[docs] class EbuildReservedCheck(_ReservedNameCheck): """Scan ebuilds for reserved function or variable names.""" _source = sources.EbuildParseRepoSource known_results = frozenset({EbuildReservedName, EbuildSemiReservedName}) global_reserved = ( frozenset(string.ascii_uppercase) .union(c + "DEPEND" for c in string.ascii_uppercase) .difference(("CDEPEND",)) ) def __init__(self, options, **kwargs): super().__init__(options, **kwargs) self.phases_hooks = { eapi_name: { f"{prefix}_{phase}" for phase in eapi.phases.values() for prefix in ("pre", "post") } for eapi_name, eapi in EAPI.known_eapis.items() }
[docs] def feed(self, pkg: sources._ParsedPkg): for used_name, *args, lineno in self._feed(pkg): yield EbuildReservedName(*args, lineno=lineno, line=used_name, pkg=pkg) for node, _ in bash.func_query.captures(pkg.tree.root_node): used_name = pkg.node_str(node.child_by_field_name("name")) if used_name in self.phases_hooks[str(pkg.eapi)]: lineno, _ = node.start_point yield EbuildReservedName( "function", used_name, "phase hook", lineno=lineno + 1, line=used_name, pkg=pkg ) current_global_reserved = self.global_reserved.difference( pkg.eapi.eclass_keys, pkg.eapi.dep_keys ) for node in pkg.global_query(bash.var_assign_query): used_name = pkg.node_str(node.child_by_field_name("name")) if used_name in current_global_reserved: lineno, _ = node.start_point yield EbuildSemiReservedName( "variable", lineno=lineno + 1, line=used_name, pkg=pkg, )