Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pkgcheck 0.10.40 (unreleased)
- StabilizationGroupsCheck: check for invalid and non-existant stabilization
groups (Arthur Zamarin)

- GlobalDeclareCheck: detect ``declare -A`` without ``-g`` in global scope
(Sv. Lockal, #628)


**Packaging:**

Expand Down
1 change: 1 addition & 0 deletions src/pkgcheck/bash/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def query(query_str: str):
var_assign_query = query("(variable_assignment) @assign")
var_expansion_query = query("(expansion) @exp")
var_query = query("(variable_name) @var")
decl_query = query("(declaration_command) @decl")


class ParseTree:
Expand Down
42 changes: 42 additions & 0 deletions src/pkgcheck/checks/codingstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1667,3 +1667,45 @@ def feed(self, pkg: bash.ParseTree):
if new_index < index:
yield VariableOrderWrong(first_var, self.variable_order[index], pkg=pkg)
index = new_index


class GlobalDeclareWithoutG(results.LineResult, results.Warning):
"""Call to ``declare -A`` without ``-g`` in global scope.

Associative arrays created with ``declare -A`` in global scope
are implicitly local when the ebuild is sourced inside a function
(as non-portage package managers may do).
Use ``declare -gA`` to ensure the variable is always in global scope.
"""

@property
def desc(self):
return f"line {self.lineno}: call to 'declare -A' without '-g' in global scope: {self.line}"


class GlobalDeclareCheck(Check):
"""Scan ebuilds for ``declare -A`` calls without ``-g`` in global scope."""

_source = sources.EbuildParseRepoSource
known_results = frozenset({GlobalDeclareWithoutG})

def feed(self, pkg: bash.ParseTree):
for node in pkg.global_query(bash.decl_query):
name_node = node.children[0]
if pkg.node_str(name_node) != "declare":
continue
has_A = False
has_g = False
for child in node.children:
if child.type == "word":
flag = pkg.node_str(child)
if flag.startswith("-"):
if "A" in flag:
has_A = True
if "g" in flag:
has_g = True
if has_A and not has_g:
lineno, _ = node.start_point
yield GlobalDeclareWithoutG(
line=pkg.node_str(node), lineno=lineno + 1, pkg=pkg
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"__class__": "GlobalDeclareWithoutG", "category": "GlobalDeclareCheck", "package": "GlobalDeclareWithoutG", "version": "0", "line": "declare -A ASSOC_ARRAY=(\n\t[a]=b\n\t[c]=d\n)", "lineno": 11}
{"__class__": "GlobalDeclareWithoutG", "category": "GlobalDeclareCheck", "package": "GlobalDeclareWithoutG", "version": "0", "line": "declare -rA READONLY_ASSOC=(\n\t[e]=f\n)", "lineno": 16}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
diff -Naur standalone/GlobalDeclareCheck/GlobalDeclareWithoutG/GlobalDeclareWithoutG-0.ebuild fixed/GlobalDeclareCheck/GlobalDeclareWithoutG/GlobalDeclareWithoutG-0.ebuild
--- standalone/GlobalDeclareCheck/GlobalDeclareWithoutG/GlobalDeclareWithoutG-0.ebuild
+++ fixed/GlobalDeclareCheck/GlobalDeclareWithoutG/GlobalDeclareWithoutG-0.ebuild
@@ -5,11 +5,11 @@
LICENSE="BSD"
SLOT="0"

-declare -A ASSOC_ARRAY=(
+declare -gA ASSOC_ARRAY=(
[a]=b
[c]=d
)

-declare -rA READONLY_ASSOC=(
+declare -grA READONLY_ASSOC=(
[e]=f
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

EAPI=8

DESCRIPTION="Ebuild with declare without -g in global scope"
HOMEPAGE="https://github.com/pkgcore/pkgcheck"
LICENSE="BSD"
SLOT="0"

declare -A ASSOC_ARRAY=(
[a]=b
[c]=d
)

declare -rA READONLY_ASSOC=(
[e]=f
)

declare -gA GOOD_ASSOC=(
[g]=h
)

declare -g -A ALSO_GOOD=(
[i]=j
)

declare -Ag YET_ANOTHER_GOOD=(
[k]=l
)

declare -rAg GOOD_READONLY=(
[m]=n
)

declare -r READONLY_VAR="foo"

src_prepare() {
declare -A LOCAL_VAR=(
[o]=p
)
}
1 change: 1 addition & 0 deletions testdata/repos/standalone/profiles/categories
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ EclassManualDepsCheck
EclassUsageCheck
EendMissingArgCheck
EqualVersionsCheck
GlobalDeclareCheck
GlobalUseCheck
GlobCheck
HomepageCheck
Expand Down