diff options
-rw-r--r-- | NEWS.rst | 24 | ||||
-rw-r--r-- | pyproject.toml | 4 | ||||
-rw-r--r-- | src/pkgcheck/bash/__init__.py | 8 | ||||
-rw-r--r-- | src/pkgcheck/checks/codingstyle.py | 66 | ||||
-rw-r--r-- | src/pkgcheck/checks/eclass.py | 14 | ||||
-rw-r--r-- | src/pkgcheck/checks/python.py | 14 | ||||
-rw-r--r-- | src/pkgcheck/checks/reserved.py | 8 | ||||
-rw-r--r-- | src/pkgcheck/checks/rust.py | 4 | ||||
-rw-r--r-- | testdata/data/repos/standalone/MetadataVarCheck/EmptyGlobalAssignment/expected.json | 1 |
9 files changed, 84 insertions, 59 deletions
@@ -3,6 +3,30 @@ Release Notes ============= ----------------------------- +pkgcheck 0.10.31 (2024-09-06) +----------------------------- + +- fix compatibility with tree-sitter 0.23 (Arthur Zamarin) + +- bash completion: improve path handling (Arthur Zamarin) + +**New Checks:** + +- OldPackageNameDep: check for dependency using previously moved package's name + (Arthur Zamarin, #659, #694) + +- BadDependency: catch ``:=`` slot operator in PDEPEND (Arthur Zamarin, #660) + +**Checks Updates:** + +- DistutilsNonPEP517Build: handle false positives when defined by eclass + (Arthur Zamarin) + +- PythonCompatCheck: add whitelist for backports (Arthur Zamarin) + +- EmptyGlobalAssignment: ignore empty KEYWORDS (Arthur Zamarin, #695) + +----------------------------- pkgcheck 0.10.30 (2024-05-18) ----------------------------- diff --git a/pyproject.toml b/pyproject.toml index 0b7e20fd..4e7d2d82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ "lazy-object-proxy", "lxml", "pathspec", - "tree-sitter>=0.21.0", + "tree-sitter>=0.23.0", "tree-sitter-bash>=0.21.0", "snakeoil~=0.10.8", "pkgcore~=0.12.25", @@ -46,7 +46,7 @@ dependencies = [ "lazy-object-proxy", "lxml", "pathspec", - "tree-sitter>=0.22.2", + "tree-sitter>=0.23.0", "tree-sitter-bash>=0.21.0", "snakeoil~=0.10.8", "pkgcore~=0.12.25", diff --git a/src/pkgcheck/bash/__init__.py b/src/pkgcheck/bash/__init__.py index 0c8a7a13..1b47881c 100644 --- a/src/pkgcheck/bash/__init__.py +++ b/src/pkgcheck/bash/__init__.py @@ -1,5 +1,7 @@ """bash parsing support""" +from itertools import chain + import tree_sitter_bash from tree_sitter import Language, Parser, Query @@ -31,13 +33,11 @@ class ParseTree: for x in self.tree.root_node.children: # skip nodes in function scope if x.type != "function_definition": - for node, _ in query.captures(x): - yield node + yield from chain.from_iterable(query.captures(x).values()) def func_query(self, query: Query): """Run a given parse tree query returning only those nodes in function scope.""" for x in self.tree.root_node.children: # only return nodes in function scope if x.type == "function_definition": - for node, _ in query.captures(x): - yield node + yield from chain.from_iterable(query.captures(x).values()) diff --git a/src/pkgcheck/checks/codingstyle.py b/src/pkgcheck/checks/codingstyle.py index cfc7f8da..20628481 100644 --- a/src/pkgcheck/checks/codingstyle.py +++ b/src/pkgcheck/checks/codingstyle.py @@ -89,8 +89,8 @@ class BadCommandsCheck(Check): ) def feed(self, pkg): - for func_node, _ in bash.func_query.captures(pkg.tree.root_node): - for node, _ in bash.cmd_query.captures(func_node): + for func_node in bash.func_query.captures(pkg.tree.root_node).get("func", ()): + for node in bash.cmd_query.captures(func_node).get("call", ()): call = pkg.node_str(node) name = pkg.node_str(node.child_by_field_name("name")) lineno, _colno = node.start_point @@ -134,8 +134,8 @@ class EendMissingArgCheck(Check): known_results = frozenset([EendMissingArg]) def feed(self, pkg): - for func_node, _ in bash.func_query.captures(pkg.tree.root_node): - for node, _ in bash.cmd_query.captures(func_node): + for func_node in bash.func_query.captures(pkg.tree.root_node).get("func", ()): + for node in bash.cmd_query.captures(func_node).get("call", ()): line = pkg.node_str(node) if line == "eend": lineno, _ = node.start_point @@ -634,10 +634,12 @@ class MetadataVarCheck(Check): # mapping between registered variables and verification methods known_variables = {} + empty_vars_whitelist = frozenset({"KEYWORDS"}) + @verify_vars("HOMEPAGE", "KEYWORDS") def _raw_text(self, var, node, value, pkg): matches = [] - for var_node, _ in bash.var_query.captures(node): + for var_node in bash.var_query.captures(node).get("var", ()): matches.append(pkg.node_str(var_node.parent)) if matches: yield ReferenceInMetadataVar(var, stable_unique(matches), pkg=pkg) @@ -645,7 +647,7 @@ class MetadataVarCheck(Check): @verify_vars("LICENSE") def _raw_text_license(self, var, node, value, pkg): matches = [] - for var_node, _ in bash.var_query.captures(node): + for var_node in bash.var_query.captures(node).get("var", ()): var_str = pkg.node_str(var_node.parent).strip() if var_str in ["$LICENSE", "${LICENSE}"]: continue # LICENSE in LICENSE is ok @@ -710,10 +712,13 @@ class MetadataVarCheck(Check): value_str = self.canonicalize_assign(pkg.node_str(value_node)) if value_node else "" if name in pkg.eapi.eclass_keys: if not value_str: - lineno, _ = node.start_point - yield EmptyGlobalAssignment(line=pkg.node_str(node), lineno=lineno + 1, pkg=pkg) + if name not in self.empty_vars_whitelist: + lineno, _ = node.start_point + yield EmptyGlobalAssignment( + line=pkg.node_str(node), lineno=lineno + 1, pkg=pkg + ) elif pkg.node_str(value_node.prev_sibling) == "=": - for var_node, _ in bash.var_query.captures(value_node): + for var_node in bash.var_query.captures(value_node).get("var", ()): if ( pkg.node_str(var_node) == name and self.canonicalize_assign(pkg.node_str(var_node.parent)) == value_str @@ -873,12 +878,12 @@ class InheritsCheck(Check): # collect globally defined functions in ebuild defined_funcs = { pkg.node_str(func_node.child_by_field_name("name")) - for func_node, _ in bash.func_query.captures(pkg.tree.root_node) + for func_node in bash.func_query.captures(pkg.tree.root_node).get("func", ()) } # register variables assigned in ebuilds assigned_vars = dict() - for node, _ in bash.var_assign_query.captures(pkg.tree.root_node): + for node in bash.var_assign_query.captures(pkg.tree.root_node).get("assign", ()): name = pkg.node_str(node.child_by_field_name("name")) if eclass := self.get_eclass(name, pkg): assigned_vars[name] = eclass @@ -887,7 +892,7 @@ class InheritsCheck(Check): weak_used_eclasses = set() # match captured commands with eclasses used = defaultdict(list) - for node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): call = pkg.node_str(node) name = pkg.node_str(node.child_by_field_name("name")) if name == "inherit": @@ -909,7 +914,7 @@ class InheritsCheck(Check): weak_used_eclasses.add(eclass) # match captured variables with eclasses - for node, _ in bash.var_query.captures(pkg.tree.root_node): + for node in bash.var_query.captures(pkg.tree.root_node).get("var", ()): name = pkg.node_str(node) if node.parent.type == "unset_command": continue @@ -1109,12 +1114,12 @@ class VariableScopeCheck(Check): scoped_vars.setdefault(eapi, {}).setdefault(phase, set()).add(variable) scoped_vars = ImmutableDict(scoped_vars) - def feed(self, pkg): - for func_node, _ in bash.func_query.captures(pkg.tree.root_node): + def feed(self, pkg: bash.ParseTree): + for func_node in bash.func_query.captures(pkg.tree.root_node).get("func", ()): func_name = pkg.node_str(func_node.child_by_field_name("name")) if variables := self.scoped_vars[pkg.eapi].get(func_name): usage = defaultdict(set) - for var_node, _ in bash.var_query.captures(func_node): + for var_node in bash.var_query.captures(func_node).get("var", ()): var_name = pkg.node_str(var_node) if var_name in variables: lineno, _colno = var_node.start_point @@ -1125,7 +1130,7 @@ class VariableScopeCheck(Check): global_usage = defaultdict(set) for global_node in pkg.tree.root_node.children: if global_node.type not in ("function_definition", "ERROR"): - for var_node, _ in bash.var_query.captures(global_node): + for var_node in bash.var_query.captures(global_node).get("var", ()): var_name = pkg.node_str(var_node) if var_name in self.not_global_scope: lineno, _colno = var_node.start_point @@ -1266,14 +1271,14 @@ class _UnquotedVariablesCheck(Check): # Default: The variable should be quoted return True - def _feed(self, item): + def _feed(self, item: bash.ParseTree): if item.tree.root_node.has_error: # Do not run this check if the parse tree contains errors, as it # might result in false positives. This check appears to be quite # expensive though... return hits = defaultdict(set) - for var_node, _ in bash.var_query.captures(item.tree.root_node): + for var_node in bash.var_query.captures(item.tree.root_node).get("var", ()): var_name = item.node_str(var_node) if var_name in self.var_names: if self._var_needs_quotes(item, var_node): @@ -1385,7 +1390,7 @@ class DoCompressedFilesCheck(Check): ) def feed(self, pkg): - for node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): call_name = pkg.node_str(node.child_by_field_name("name")) if call_name not in self.functions: continue @@ -1441,7 +1446,7 @@ class NonPosixCheck(Check): prev_arg = arg def feed(self, pkg): - for call_node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for call_node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): call_name = pkg.node_str(call_node.child_by_field_name("name")) if call_name in ("head", "tail"): yield from self.check_head_tail(pkg, call_node, call_name) @@ -1471,14 +1476,13 @@ class GlobCheck(Check): self.glob_query = bash.query('(concatenation (word) @word (.match? @word "[*?]")) @usage') def feed(self, pkg): - for node, capture in self.glob_query.captures(pkg.tree.root_node): - if capture == "usage": - for var_node, _ in bash.var_query.captures(node): - var_name = pkg.node_str(var_node) - if var_name == "DISTDIR": - lineno, _colno = node.start_point - yield GlobDistdir(line=pkg.node_str(node), lineno=lineno + 1, pkg=pkg) - break + for node in self.glob_query.captures(pkg.tree.root_node).get("usage", ()): + for var_node in bash.var_query.captures(node).get("var", ()): + var_name = pkg.node_str(var_node) + if var_name == "DISTDIR": + lineno, _colno = node.start_point + yield GlobDistdir(line=pkg.node_str(node), lineno=lineno + 1, pkg=pkg) + break class VariableShadowed(results.LinesResult, results.Warning): @@ -1523,7 +1527,7 @@ class DeclarationShadowedCheck(Check): if value_node := node.child_by_field_name("value"): if any( pkg.node_str(node) == used_name - for node, _ in bash.var_query.captures(value_node) + for node in bash.var_query.captures(value_node).get("var", ()) ): continue var_assigns[used_name].append(node) @@ -1565,7 +1569,7 @@ class SandboxCallCheck(Check): functions = frozenset({"addread", "addwrite", "adddeny", "addpredict"}) def feed(self, pkg: bash.ParseTree): - for node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): name = pkg.node_str(node.child_by_field_name("name")) if name in self.functions: args = node.children_by_field_name("argument") diff --git a/src/pkgcheck/checks/eclass.py b/src/pkgcheck/checks/eclass.py index 394ef192..52bcea6e 100644 --- a/src/pkgcheck/checks/eclass.py +++ b/src/pkgcheck/checks/eclass.py @@ -164,7 +164,7 @@ class EclassUsageCheck(Check): # scan for any misplaced @PRE_INHERIT variables if pre_inherits: - for node, _ in bash.var_assign_query.captures(pkg.tree.root_node): + for node in bash.var_assign_query.captures(pkg.tree.root_node).get("assign", ()): var_name = pkg.node_str(node.child_by_field_name("name")) lineno, _colno = node.start_point if var_name in pre_inherits and lineno > pre_inherits[var_name]: @@ -184,7 +184,7 @@ class EclassUsageCheck(Check): # scan for usage of @USER_VARIABLE variables if user_variables: - for node, _ in bash.var_assign_query.captures(pkg.tree.root_node): + for node in bash.var_assign_query.captures(pkg.tree.root_node).get("assign", ()): var_name = pkg.node_str(node.child_by_field_name("name")) if var_name in user_variables: lineno, _colno = node.start_point @@ -205,7 +205,7 @@ class EclassUsageCheck(Check): # scan for usage of @DEPRECATED variables if deprecated: - for node, _ in bash.var_query.captures(pkg.tree.root_node): + for node in bash.var_query.captures(pkg.tree.root_node).get("var", ()): var_name = pkg.node_str(node) if var_name in deprecated: lineno, _colno = node.start_point @@ -230,7 +230,7 @@ class EclassUsageCheck(Check): # scan for usage of @DEPRECATED functions if deprecated: - for node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): func_name = pkg.node_str(node.child_by_field_name("name")) if func_name in deprecated: lineno, _colno = node.start_point @@ -258,7 +258,7 @@ class EclassUsageCheck(Check): if pkg.inherit: inherited: set[str] = set() inherits: list[tuple[list[str], int]] = [] - for node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): name = pkg.node_str(node.child_by_field_name("name")) if name == "inherit": call = pkg.node_str(node) @@ -342,14 +342,14 @@ class EclassParseCheck(Check): def feed(self, eclass): func_prefix = f"{eclass.name}_" - for func_node, _ in bash.func_query.captures(eclass.tree.root_node): + for func_node in bash.func_query.captures(eclass.tree.root_node).get("func", ()): func_name = eclass.node_str(func_node.child_by_field_name("name")) if not func_name.startswith(func_prefix): continue phase = func_name[len(func_prefix) :] if variables := self.eclass_phase_vars(eclass, phase): usage = defaultdict(set) - for var_node, _ in bash.var_query.captures(func_node): + for var_node in bash.var_query.captures(func_node).get("var", ()): var_name = eclass.node_str(var_node) if var_name in variables: lineno, _colno = var_node.start_point diff --git a/src/pkgcheck/checks/python.py b/src/pkgcheck/checks/python.py index 13ec6311..2c46885a 100644 --- a/src/pkgcheck/checks/python.py +++ b/src/pkgcheck/checks/python.py @@ -1,7 +1,7 @@ -import itertools import re import typing from collections import defaultdict +from itertools import takewhile from operator import attrgetter from pkgcore import fetch @@ -358,7 +358,7 @@ class PythonCheck(Check): uses_setuptools_scm = False pep517_value = None - for var_node, _ in bash.var_assign_query.captures(pkg.tree.root_node): + for var_node in bash.var_assign_query.captures(pkg.tree.root_node).get("assign", ()): var_name = pkg.node_str(var_node.child_by_field_name("name")) if var_name == "DISTUTILS_OPTIONAL": @@ -407,7 +407,7 @@ class PythonCheck(Check): for var_node in pkg.global_query(bash.var_assign_query): name = pkg.node_str(var_node.child_by_field_name("name")) if name in {"DEPEND", "BDEPEND"}: - for call_node, _ in bash.cmd_query.captures(var_node): + for call_node in bash.cmd_query.captures(var_node).get("call", ()): call_name = pkg.node_str(call_node.child_by_field_name("name")) if call_name == any_dep_func and len(call_node.children) > 1: check_deps[name].update( @@ -457,7 +457,7 @@ class PythonCheck(Check): def check_python_check_deps(self, pkg, func_node, python_check_deps, any_dep_func): has_version_checked_deps = defaultdict(set) has_version_lines = set() - for node, _ in bash.cmd_query.captures(func_node): + for node in bash.cmd_query.captures(func_node).get("call", ()): call_name = pkg.node_str(node.child_by_field_name("name")) if call_name == "has_version": lineno, _ = node.start_point @@ -548,7 +548,7 @@ class PythonCheck(Check): any_dep_func = self.eclass_any_dep_func[eclass] python_check_deps = self.build_python_gen_any_dep_calls(pkg, any_dep_func) - for func_node, _ in bash.func_query.captures(pkg.tree.root_node): + for func_node in bash.func_query.captures(pkg.tree.root_node).get("func", ()): func_name = pkg.node_str(func_node.child_by_field_name("name")) if func_name == "python_check_deps": yield from self.check_python_check_deps( @@ -664,9 +664,7 @@ class PythonCompatCheck(Check): return # determine python impls to target - targets = set( - itertools.takewhile(lambda x: x != latest_target, reversed(available_targets)) - ) + targets = set(takewhile(lambda x: x != latest_target, reversed(available_targets))) if targets: try: diff --git a/src/pkgcheck/checks/reserved.py b/src/pkgcheck/checks/reserved.py index f99a9050..78f4e357 100644 --- a/src/pkgcheck/checks/reserved.py +++ b/src/pkgcheck/checks/reserved.py @@ -42,14 +42,14 @@ class _ReservedNameCheck(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) + for node in bash.func_query.captures(item.tree.root_node).get("func", ()) }, ) 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_assign_query.captures(item.tree.root_node).get("assign", ()) } - for node, _ in bash.var_query.captures(item.tree.root_node): + for node in bash.var_query.captures(item.tree.root_node).get("var", ()): 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) @@ -144,7 +144,7 @@ class EbuildReservedCheck(_ReservedNameCheck): 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): + for node in bash.func_query.captures(pkg.tree.root_node).get("func", ()): 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 diff --git a/src/pkgcheck/checks/rust.py b/src/pkgcheck/checks/rust.py index 397738b9..7a5cbdad 100644 --- a/src/pkgcheck/checks/rust.py +++ b/src/pkgcheck/checks/rust.py @@ -60,7 +60,7 @@ class RustCheck(Check): return def _verify_cargo_crate_uris(self, pkg: bash.ParseTree): - for node, _ in bash.cmd_query.captures(pkg.tree.root_node): + for node in bash.cmd_query.captures(pkg.tree.root_node).get("call", ()): call_name = pkg.node_str(node.child_by_field_name("name")) if call_name == "cargo_crate_uris": row, _ = node.start_point @@ -69,7 +69,7 @@ class RustCheck(Check): node.child_count == 2 and any( pkg.node_str(var_node) == "CRATES" - for var_node, _ in bash.var_query.captures(node.children[1]) + for var_node in bash.var_query.captures(node.children[1]).get("var", ()) ) ): yield SuboptimalCratesURICall( diff --git a/testdata/data/repos/standalone/MetadataVarCheck/EmptyGlobalAssignment/expected.json b/testdata/data/repos/standalone/MetadataVarCheck/EmptyGlobalAssignment/expected.json index 7cab9376..c5c90f2d 100644 --- a/testdata/data/repos/standalone/MetadataVarCheck/EmptyGlobalAssignment/expected.json +++ b/testdata/data/repos/standalone/MetadataVarCheck/EmptyGlobalAssignment/expected.json @@ -1,3 +1,2 @@ {"__class__": "EmptyGlobalAssignment", "category": "DescriptionCheck", "package": "BadDescription", "version": "1", "line": "DESCRIPTION=\"\"", "lineno": 1} -{"__class__": "EmptyGlobalAssignment", "category": "MetadataVarCheck", "package": "MultipleKeywordsLines", "version": "0", "line": "KEYWORDS=\"\"", "lineno": 8} {"__class__": "EmptyGlobalAssignment", "category": "MetadataVarCheck", "package": "ReferenceInMetadataVar", "version": "3", "line": "LICENSE=\"\"", "lineno": 4} |