3"""Declarative syntax catalog for repository suppression directives."""
5from __future__
import annotations
8from pathlib
import Path
10from lint_coverage_rules
import EXEMPT_PREFIXES, EXT_CLASS, PATH_CLASS
12VENDOR_PREFIXES = tuple(
13 prefix
for prefix, reason
in EXEMPT_PREFIXES
if "vendored" in reason
or "SOUP" in reason
15GENERATED_PREFIXES = tuple(
16 prefix
for prefix, reason
in EXEMPT_PREFIXES
if "generated" in reason
or "emitted" in reason
18GENERATED_CLASSES = frozenset({
"generated-source"})
19BINARY_SUFFIXES = frozenset(suffix
for suffix, cls
in EXT_CLASS.items()
if cls ==
"binary")
20REQUIRED_FAMILIES = frozenset(
27 "checker-nonfatal-control",
28 "checker-scope-control",
29 "ci-parity-exemption",
36 "documentation-control",
43 "ansible-lint-config",
44 "security-analysis-control",
45 "other-language-control",
50THREADX_FIRST_PARTY_FILES = frozenset(
52 "port/threadx/inc/ra8_threadx.h",
53 "port/threadx/inc/tx_user.h",
54 "port/threadx/src/cortex_m85/tx_systick_ready.c",
55 "port/threadx/src/cortex_m85/tx_systick_retune.c",
59KNOWN_PROJECT_MARKERS = frozenset(
74KNOWN_CPPCHECK_RULES = frozenset(
77 "constParameterCallback",
78 "constParameterPointer",
79 "constVariablePointer",
80 "duplicateExpression",
81 "knownConditionTrueFalse",
83 "redundantAssignment",
121C_FAMILY_SUFFIXES = frozenset(
122 {
".c",
".cc",
".cpp",
".cxx",
".h",
".hh",
".hpp",
".hxx",
".inc",
".m",
".mm"}
124PYTHON_SUFFIXES = frozenset({
".py",
".pyi"})
125HASH_COMMENT_SUFFIXES = frozenset(
126 {
".bash",
".cmake",
".ini",
".just",
".mk",
".sh",
".toml",
".yaml",
".yml",
".zsh"}
128HASH_COMMENT_NAMES = frozenset(
129 {
"CMakeLists.txt",
"Dockerfile",
"GNUmakefile",
"Justfile",
"Makefile",
"justfile"}
131BUILD_CONTROL_SUFFIXES = frozenset(
132 {
".bash",
".cmake",
".just",
".mk",
".sh",
".yaml",
".yml",
".zsh"}
134BUILD_CONTROL_NAMES = HASH_COMMENT_NAMES
136NOLINT_RE = re.compile(
137 r"^NOLINT(?P<scope>NEXTLINE|BEGIN|END)?(?:\((?P<rules>[^)]*)\))?"
140NOLINT_RAW_RE = re.compile(
141 r"NOLINT(?P<scope>NEXTLINE|BEGIN|END)?"
142 r"(?:\((?P<rules>[^)\r\n]*)\)|\[(?P<bracket_rules>[^]\r\n]*)\])?"
146 r"^noqa(?:\s*:\s*(?P<rules>[A-Za-z0-9_.-]+(?:\s*,\s*[A-Za-z0-9_.-]+)*))?"
147 r"(?P<tail>\s*(?:(?:--|#|-\s).*)?|\s*\(.*\))$",
150TYPE_IGNORE_RE = re.compile(
151 r"^type\s*:\s*ignore(?:\[(?P<rules>[^]]*)\])?"
152 r"(?P<tail>\s*(?:--.*|#.*)?)$",
155PYTHON_COVERAGE_RE = re.compile(
156 r"^pragma\s*:\s*no\s+cover(?P<tail>\s*(?:--.*)?)$",
159PYLINT_RE = re.compile(
160 r"^pylint\s*:\s*(?P<control>disable|enable|skip-file)"
161 r"(?:\s*=\s*(?P<rules>[A-Za-z0-9_.-]+(?:\s*,\s*[A-Za-z0-9_.-]+)*))?"
162 r"(?P<tail>\s*(?:--.*)?)$",
166 r"^mypy\s*:\s*(?P<control>ignore-errors|disable-error-code|enable-error-code)"
167 r"(?:\s*=\s*[\"']?\[?(?P<rules>[A-Za-z0-9_.-]+(?:\s*,\s*[A-Za-z0-9_.-]+)*)"
168 r"\]?[\"']?)?(?P<tail>\s*(?:--.*)?)$",
171PYRIGHT_RE = re.compile(
172 r"^pyright\s*:\s*(?:(?P<ignore>ignore)(?:\[(?P<rules>[A-Za-z0-9_.-]+"
173 r"(?:\s*,\s*[A-Za-z0-9_.-]+)*)\])?|(?P<setting>report[A-Za-z0-9_]+)"
174 r"\s*=\s*(?P<value>true|false|none|information|warning|error))"
175 r"(?P<tail>\s*(?:--.*)?)$",
178BANDIT_RE = re.compile(
179 r"^(?:(?P<nosec>nosec)(?:\s+(?P<nosec_rules>[A-Za-z0-9_.-]+"
180 r"(?:\s*,\s*[A-Za-z0-9_.-]+)*))?|bandit\s*:\s*skip\s*=\s*"
181 r"(?P<bandit_rules>[A-Za-z0-9_.-]+(?:\s*,\s*[A-Za-z0-9_.-]+)*))"
182 r"(?P<tail>\s*(?:--.*)?)$",
185PYTHON_FORMATTER_RE = re.compile(
186 r"^(?:(?P<ruff>ruff\s*:\s*noqa)(?:\s*:\s*(?P<ruff_rules>[A-Za-z0-9_.-]+"
187 r"(?:\s*,\s*[A-Za-z0-9_.-]+)*))?|(?P<fmt>fmt)\s*:\s*(?P<fmt_control>off|on|skip)|"
188 r"(?P<isort>isort)\s*:\s*(?P<isort_control>off|on|skip|skip_file))"
189 r"(?P<tail>\s*(?:--.*)?)$",
192SHELLCHECK_RE = re.compile(
193 r"^shellcheck\s+(?P<control>disable|enable|source|source-path|shell|external-sources)"
194 r"\s*=\s*(?P<value>\S+(?:\s*,\s*\S+)*)"
195 r"(?P<tail>\s*(?:#.*)?)$",
198SHELL_STATUS_MASK_RE = re.compile(
r"\|\|\s*(?:true\b|:(?![A-Za-z0-9_]))")
199SHELLCHECK_GLOBAL_EXCLUDE_RE = re.compile(
200 r"(?:^|\s)(?:-e|--exclude)(?:=|\s+)(?P<rules>SC\d+(?:\s*,\s*SC\d+)*)"
202COVERAGE_RE = re.compile(
203 r"^(?P<marker>(?:GCOVR|LCOV)_EXCL(?:_BR)?_(?:LINE|START|STOP))"
204 r"(?P<tail>\s*(?:(?:--|:).*)?)$"
206CPPCHECK_RE = re.compile(
207 r"^cppcheck-suppress(?P<scope>-(?:begin|end|file))?\s+"
208 r"\[?(?P<rule>[A-Za-z0-9_.-]+)\]?(?P<tail>\s*(?:(?:--|;).*)?)$"
210ALLOC_ALLOW_RE = re.compile(
211 r"\balloc-allow\s*:\s*(?P<reason>\S(?:.*\S)?)\s*$",
214PROJECT_MARKER_RE = re.compile(
215 r"^(?P<marker>[A-Z][A-Z0-9_-]*-OK)"
216 r"(?:(?:\s*:\s*|\s+--\s*)(?P<reason>.*))?$"
218UNKNOWN_DIRECTIVE_RE = re.compile(
219 r"^(?:NOLINT[A-Z]*(?:\(|\s*(?:--|$))|noqa(?:\s*:|\s*--|\s*$)|"
220 r"type\s*:\s*ignore\b|pragma\s*:\s*no\s+cover\b|"
221 r"pylint\s*:|mypy\s*:|pyright\s*:|nosec\b|bandit\s*:|"
222 r"ruff\s*:\s*noqa|fmt\s*:|isort\s*:|shellcheck\s+\S+|"
223 r"(?:GCOVR|LCOV)_EXCL[A-Z_]*(?:\s*--|\s*$)|cppcheck-suppress\S*|"
224 r"clang-format\s+(?:off|on)\b|yamllint\s+(?:disable|enable)|"
225 r"hadolint\s+(?:global\s+)?ignore\s*=|cmake-(?:lint|format)\s*:|"
226 r"shfmt\s*:|prettier-ignore\b|markdownlint-[A-Za-z-]+|"
227 r"(?:@|\\)(?:cond|endcond)\b|IWYU\s+pragma\s*:|"
228 r"[A-Z][A-Z0-9_-]*-OK(?:\s*:|\s+--|\s*$))"
230WARNING_FLAG_RE = re.compile(
r"(?<![A-Za-z0-9_])(?P<flag>-Wno-[A-Za-z0-9_.=+-]+)(?![A-Za-z0-9_])")
231BLANKET_WARNING_RE = re.compile(
r"(?<![A-Za-z0-9_])(?P<flag>-w)(?![A-Za-z0-9_])")
232SUPPORTED_HINT_RE = re.compile(
233 r"(?:NOLINT|noqa|type\s*:\s*ignore|pragma\s*:\s*no\s+cover|"
234 r"pylint\s*:|mypy\s*:|pyright\s*:|nosec\b|bandit\s*:|"
235 r"ruff\s*:\s*noqa|fmt\s*:|isort\s*:|shellcheck|(?:GCOVR|LCOV)_EXCL|"
236 r"cppcheck-suppress|clang-format|(?:@|\\)(?:cond|endcond)\b|"
237 r"IWYU\s+pragma\s*:|[A-Z][A-Z0-9_-]*-OK|-Wno-|"
238 r"(?<![A-Za-z0-9_])-w(?![A-Za-z0-9_]))",
245UNSUPPORTED_CATEGORIES: tuple[tuple[str, re.Pattern[str]], ...] = ()
248def ownership(path: str) -> str:
249 """Classify ownership from the repository's canonical path evidence."""
250 if PATH_CLASS.get(path)
in GENERATED_CLASSES:
252 if path
in THREADX_FIRST_PARTY_FILES:
254 if path.startswith(VENDOR_PREFIXES):
256 if path.startswith(
"port/threadx/")
and Path(path).suffix.lower()
in C_FAMILY_SUFFIXES:
258 if path.startswith(GENERATED_PREFIXES):
263def language(path: str, first_line: str =
"") -> str:
264 """Classify comment syntax from path and an optional shebang."""
266 if item.suffix.lower()
in C_FAMILY_SUFFIXES:
268 if item.suffix.lower()
in PYTHON_SUFFIXES:
271 item.name
in HASH_COMMENT_NAMES
272 or item.name.startswith(
"Dockerfile.")
273 or item.suffix.lower()
in HASH_COMMENT_SUFFIXES
276 if first_line.startswith(
"#!")
and any(word
in first_line
for word
in (
"sh",
"bash",
"zsh")):
281def is_build_control(path: str, first_line: str =
"") -> bool:
282 """Return whether compiler warning flags in this file can be active."""
284 if item.name
in BUILD_CONTROL_NAMES
or item.suffix.lower()
in BUILD_CONTROL_SUFFIXES:
286 return first_line.startswith(
"#!")
and any(word
in first_line
for word
in (
"sh",
"bash",
"zsh"))
289def is_shell_control(path: str, first_line: str =
"") -> bool:
290 """Return whether hash comments follow shell word-boundary rules."""
292 suffix = item.suffix.lower()
293 if suffix
in {
".bash",
".sh",
".zsh"}:
295 if suffix ==
".just" or item.name ==
"justfile":
297 return first_line.startswith(
"#!")
and any(word
in first_line
for word
in (
"sh",
"bash",
"zsh"))