diff --git a/src/pkgcheck/checks/reserved.py b/src/pkgcheck/checks/reserved.py new file mode 100644 index 000000000..3543a1fb2 --- /dev/null +++ b/src/pkgcheck/checks/reserved.py @@ -0,0 +1,94 @@ +import re +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') + + def _check(self, used_type: str, used_names): + for used_name, node_start in used_names.items(): + if used_name in self.special_whitelist: + continue + test_name = used_name.lower() + lineno, _ = node_start + for reserved in self.reserved_prefixes: + if test_name.startswith(reserved): + yield used_name, used_type, reserved, 'prefix', lineno + for reserved in self.reserved_substrings: + if reserved in test_name: + yield used_name, used_type, reserved, 'substring', lineno + if self.reserved_ebuild_regex.match(test_name): + yield used_name, used_type, 'ebuild', 'substring', lineno + + 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) + }) + yield from self._check('variable', { + item.node_str(node.child_by_field_name('name')): node.start_point + for node, _ in bash.var_assign_query.captures(item.tree.root_node) + }) + + +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_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'line {self.lineno}: {self.used_type} name "{self.used_name}" 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 feed(self, pkg): + for *args, lineno in self._feed(pkg): + yield EbuildReservedName(*args, lineno=lineno, line='', pkg=pkg) diff --git a/testdata/data/repos/eclass/EclassReservedCheck/EclassReservedName/expected.json b/testdata/data/repos/eclass/EclassReservedCheck/EclassReservedName/expected.json new file mode 100644 index 000000000..3e0e439a5 --- /dev/null +++ b/testdata/data/repos/eclass/EclassReservedCheck/EclassReservedName/expected.json @@ -0,0 +1,2 @@ +{"__class__": "EclassReservedName", "eclass": "reserved", "used_name": "prepare_locale", "used_type": "function", "reserved_word": "prep", "reserved_type": "prefix"} +{"__class__": "EclassReservedName", "eclass": "reserved", "used_name": "EBUILD_TEST", "used_type": "variable", "reserved_word": "ebuild", "reserved_type": "substring"} diff --git a/testdata/data/repos/standalone/EbuildReservedCheck/EbuildReservedName/expected.json b/testdata/data/repos/standalone/EbuildReservedCheck/EbuildReservedName/expected.json new file mode 100644 index 000000000..1dd73cac9 --- /dev/null +++ b/testdata/data/repos/standalone/EbuildReservedCheck/EbuildReservedName/expected.json @@ -0,0 +1,5 @@ +{"__class__": "EbuildReservedName", "category": "EbuildReservedCheck", "package": "EbuildReservedName", "version": "0", "used_name": "prepare_locale", "used_type": "function", "reserved_word": "prep", "reserved_type": "prefix", "lineno": 5, "line": ""} +{"__class__": "EbuildReservedName", "category": "EbuildReservedCheck", "package": "EbuildReservedName", "version": "0", "used_name": "DYNAMIC_DEPS", "used_type": "variable", "reserved_word": "dyn", "reserved_type": "prefix", "lineno": 6, "line": ""} +{"__class__": "EbuildReservedName", "category": "EbuildReservedCheck", "package": "EbuildReservedName", "version": "0", "used_name": "_hook_prepare", "used_type": "variable", "reserved_word": "hook", "reserved_type": "substring", "lineno": 7, "line": ""} +{"__class__": "EbuildReservedName", "category": "EbuildReservedCheck", "package": "EbuildReservedName", "version": "0", "used_name": "__ORIG_CC", "used_type": "variable", "reserved_word": "__", "reserved_type": "prefix", "lineno": 10, "line": ""} +{"__class__": "EbuildReservedName", "category": "EbuildReservedCheck", "package": "EbuildReservedName", "version": "0", "used_name": "EBUILD_TEST", "used_type": "variable", "reserved_word": "ebuild", "reserved_type": "substring", "lineno": 12, "line": ""} diff --git a/testdata/data/repos/standalone/EbuildReservedCheck/EbuildReservedName/fix.patch b/testdata/data/repos/standalone/EbuildReservedCheck/EbuildReservedName/fix.patch new file mode 100644 index 000000000..146e025e0 --- /dev/null +++ b/testdata/data/repos/standalone/EbuildReservedCheck/EbuildReservedName/fix.patch @@ -0,0 +1,19 @@ +--- standalone/EbuildReservedCheck/EbuildReservedName/EbuildReservedName-0.ebuild ++++ standalone/EbuildReservedCheck/EbuildReservedName/EbuildReservedName-0.ebuild +@@ -3,12 +3,11 @@ HOMEPAGE="https://github.com/pkgcore/pkgcheck" + SLOT="0" + LICENSE="BSD" + +-prepare_locale() { +- DYNAMIC_DEPS="2" +- _hook_prepare="3" ++my_prepare_locale() { ++ MY_DYNAMIC_DEPS="2" ++ _my_prepare="3" + } + +-__ORIG_CC="STUB" ++MY_ORIG_CC="STUB" + EBUILD_SUCCESS_HOOKS="true" +-EBUILD_TEST="1" + REBUILD_ALL="1" diff --git a/testdata/repos/eclass/eclass/reserved.eclass b/testdata/repos/eclass/eclass/reserved.eclass new file mode 100644 index 000000000..5c29e6658 --- /dev/null +++ b/testdata/repos/eclass/eclass/reserved.eclass @@ -0,0 +1,21 @@ +# @ECLASS: reserved.eclass +# @MAINTAINER: +# Larry the Cow +# @AUTHOR: +# Larry the Cow +# @BLURB: Stub eclass. + +# @FUNCTION: prepare_locale +# @USAGE: +# @DESCRIPTION: +# Public stub function. +prepare_locale() { + local DYNAMIC_DEPS + local prepered + export EBUILD_DEATH_HOOKS="die" +} + +# @ECLASS_VARIABLE: EBUILD_TEST +# @DESCRIPTION: +# Public stub function. +EBUILD_TEST="1" diff --git a/testdata/repos/standalone/EbuildReservedCheck/EbuildReservedName/EbuildReservedName-0.ebuild b/testdata/repos/standalone/EbuildReservedCheck/EbuildReservedName/EbuildReservedName-0.ebuild new file mode 100644 index 000000000..420f4dac7 --- /dev/null +++ b/testdata/repos/standalone/EbuildReservedCheck/EbuildReservedName/EbuildReservedName-0.ebuild @@ -0,0 +1,14 @@ +DESCRIPTION="Ebuild with reserved names" +HOMEPAGE="https://github.com/pkgcore/pkgcheck" +SLOT="0" +LICENSE="BSD" + +prepare_locale() { + DYNAMIC_DEPS="2" + _hook_prepare="3" +} + +__ORIG_CC="STUB" +EBUILD_SUCCESS_HOOKS="true" +EBUILD_TEST="1" +REBUILD_ALL="1"