aboutsummaryrefslogtreecommitdiff
blob: a67d168366c35c61be08e1ec24840f2e5a18a8c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import re

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):
        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):
        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)


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}'


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

    def feed(self, eclass):
        for *args, _ in self._feed(eclass):
            yield EclassReservedName(*args, eclass=eclass.name)


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}'


class EbuildReservedCheck(_ReservedNameCheck):
    """Scan ebuilds for reserved function or variable names."""

    _source = sources.EbuildParseRepoSource
    known_results = frozenset([EbuildReservedName])

    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()
        }

    def feed(self, pkg):
        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
                )