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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
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)
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: sources._ParsedEclass):
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 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'
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()
}
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,
)
|