aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.rst24
-rw-r--r--pyproject.toml4
-rw-r--r--src/pkgcheck/bash/__init__.py8
-rw-r--r--src/pkgcheck/checks/codingstyle.py66
-rw-r--r--src/pkgcheck/checks/eclass.py14
-rw-r--r--src/pkgcheck/checks/python.py14
-rw-r--r--src/pkgcheck/checks/reserved.py8
-rw-r--r--src/pkgcheck/checks/rust.py4
-rw-r--r--testdata/data/repos/standalone/MetadataVarCheck/EmptyGlobalAssignment/expected.json1
9 files changed, 84 insertions, 59 deletions
diff --git a/NEWS.rst b/NEWS.rst
index dfb748cf..c5161619 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -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}