ra8-firmware
0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
check_pointer_boilerplate.py
Go to the documentation of this file.
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: MIT
3
# Copyright (c) 2026 Brighton Sikarskie
4
"""Reject generated pointer-only definition comments in apps and examples.
5
6
Application and example definitions inherit their contracts from declarations.
7
The exact ``see header for the documented contract`` sentence adds no useful
8
information and was emitted repeatedly during the source-layout migration.
9
Legacy library wording is outside this narrow regression guard.
10
"""
11
12
from
__future__
import
annotations
13
14
import
re
15
import
shutil
16
import
subprocess
17
import
sys
18
from
pathlib
import
Path
19
20
REPO_ROOT = Path(__file__).resolve().parents[2]
21
SCOPED_PREFIXES = (
"apps/"
,
"examples/"
)
22
SOURCE_SUFFIXES = frozenset({
".c"
,
".cc"
,
".cpp"
,
".cxx"
,
".h"
,
".hh"
,
".hpp"
,
".hxx"
,
".m"
,
".mm"
})
23
MIN_SCOPED_FILES = 850
24
BANNED_RE = re.compile(
25
r"^\s*/\*\s*see (?:the )?(?:internal )?header for the documented contract\.\s*\*/\s*$"
,
26
re.IGNORECASE,
27
)
28
29
30
def
is_banned(line: str) -> bool:
31
"""Return whether one source line is the generated pointer-only comment."""
32
return
BANNED_RE.fullmatch(line)
is
not
None
33
34
35
def
scoped_files() -> list[str]:
36
"""Return present tracked and untracked application/example source files."""
37
git = shutil.which(
"git"
)
or
"git"
38
result = subprocess.run(
# noqa: S603 -- resolved Git executable, fixed arguments
39
[git,
"ls-files"
,
"--cached"
,
"--others"
,
"--exclude-standard"
,
"-z"
],
40
cwd=REPO_ROOT,
41
check=
True
,
42
capture_output=
True
,
43
)
44
selected = set()
45
for
rel
in
result.stdout.decode(
"utf-8"
, errors=
"strict"
).split(
"\0"
):
46
path = REPO_ROOT / rel
47
if
(
48
rel.startswith(SCOPED_PREFIXES)
49
and
path.suffix.lower()
in
SOURCE_SUFFIXES
50
and
path.is_file()
51
):
52
selected.add(rel)
53
return
sorted(selected)
54
55
56
def
scan(rels: list[str]) -> list[str]:
57
"""Return every path and line carrying the banned generated sentence."""
58
findings: list[str] = []
59
for
rel
in
rels:
60
text = (REPO_ROOT / rel).read_text(encoding=
"utf-8"
)
61
findings.extend(scan_text(rel, text))
62
return
findings
63
64
65
def
scan_text(rel: str, text: str) -> list[str]:
66
"""Run the CI scanner over one named source text."""
67
return
[
68
f
"{rel}:{number}"
69
for
number, line
in
enumerate(text.splitlines(), start=1)
70
if
is_banned(line)
71
]
72
73
74
def
selftest() -> int:
75
"""Drive the CI scanner through must-fire and must-stay-quiet source texts."""
76
cases = (
77
(
"/* see header for the documented contract. */"
,
True
,
"plain generated form fires"
),
78
(
79
"/* See the internal header for the documented contract. */"
,
80
True
,
81
"internal-header generated form fires"
,
82
),
83
(
"/* see header for full description */"
,
False
,
"legacy wording stays quiet"
),
84
(
85
"/* See header for the documented contract -- bounded scan. */"
,
86
False
,
87
"an implementation-specific note stays quiet"
,
88
),
89
(
90
'const char* text = "see header for the documented contract.";'
,
91
False
,
92
"a string literal stays quiet"
,
93
),
94
)
95
failures = [
96
label
97
for
line, expected, label
in
cases
98
if
bool(scan_text(
"apps/example/src/main.c"
, line)) != expected
99
]
100
if
failures:
101
for
failure
in
failures:
102
print(f
"check_pointer_boilerplate.py --selftest: FAIL: {failure}"
, file=sys.stderr)
103
return
1
104
print(f
"check_pointer_boilerplate.py --selftest: PASS ({len(cases)} both-direction cases)"
)
105
return
0
106
107
108
def
main
() -> int:
109
"""Run the detector self-test or scan the live source tree."""
110
if
sys.argv[1:] == [
"--selftest"
]:
111
return
selftest()
112
if
sys.argv[1:]:
113
print(
"usage: check_pointer_boilerplate.py [--selftest]"
, file=sys.stderr)
114
return
2
115
try
:
116
rels = scoped_files()
117
findings = scan(rels)
118
except
(OSError, subprocess.CalledProcessError, UnicodeError)
as
exc:
119
print(f
"check_pointer_boilerplate.py: cannot scan source tree: {exc}"
, file=sys.stderr)
120
return
2
121
if
len(rels) < MIN_SCOPED_FILES:
122
print(
123
f
"check_pointer_boilerplate.py: scope collapsed to {len(rels)} file(s); "
124
f
"expected at least {MIN_SCOPED_FILES}"
,
125
file=sys.stderr,
126
)
127
return
2
128
if
findings:
129
print(
"Generated pointer-only definition comment(s):"
, file=sys.stderr)
130
for
finding
in
findings:
131
print(f
" {finding}"
, file=sys.stderr)
132
print(
"Delete the comment; the declaration owns the contract."
, file=sys.stderr)
133
return
1
134
print(f
"check_pointer_boilerplate.py: clean ({len(rels)} app/example source files)"
)
135
return
0
136
137
138
if
__name__ ==
"__main__"
:
139
raise
SystemExit(
main
())
main
void main(void)
The application entry point Reset_Handler hands control to.
Definition
main.c:298
scripts
checks
check_pointer_boilerplate.py
Generated by
1.16.1