mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-07 03:15:56 +00:00
3529 invalid comments in 528 files, grandfathered in while the linter only saw the working-tree diff. Removal ran through the linter's own collectors, so detection and deletion cannot drift: a comment whose opener line is blank before it and whose closer line is blank after it loses all its lines, anything else loses only the comment region, and a line left holding a bare delimiter goes with it. Five files keep their comments behind a nocheck marker instead. Four are upstream configs whose inline docs are upstream's own (mautrix, systemd journald, nextcloud-oidc-login, MediaWiki). In littlejs the commented-out entries ARE the deactivation mechanism for an example tile, so deleting them would delete configuration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
177 lines
6.5 KiB
Python
177 lines
6.5 KiB
Python
"""Lint guard: redundant boolean expressions in Jinja2 / Ansible YAML.
|
|
|
|
Two related anti-patterns are flagged here. Each is independently
|
|
suppressible via its own marker so a legitimate exception can opt out
|
|
of one without disabling the other.
|
|
|
|
1. ``redundant-bool-ternary`` — ``<bool-expr> | ternary('true','false')``
|
|
for any boolean-producing left side: the ``bool`` filter, an ``in``
|
|
/ ``not in`` / ``is`` / ``is not`` / comparison operator, a Jinja
|
|
``is defined`` / ``is none`` test, etc. The ``ternary`` is a no-op
|
|
on a value that is already a boolean — its only effect is to swap
|
|
``True`` / ``False`` for the lowercase string tokens
|
|
``'true'`` / ``'false'``, and every consumer the project actually
|
|
feeds these to (PHP ``filter_var(..., FILTER_VALIDATE_BOOLEAN)``,
|
|
JS ``String(x).toLowerCase() === "true"``, dotenv-style flags,
|
|
Ansible ``bool``-comparing conditionals) accepts ``True`` / ``False``
|
|
just as well. Convention::
|
|
|
|
{{ FOO | bool }}
|
|
{{ 'web-app-lam' in group_names }}
|
|
{{ (A == B) }}
|
|
|
|
The detector keys on the literal ``'true'`` / ``'false'`` payload
|
|
inside ``ternary(…)``. Real string conversions that happen to use
|
|
``ternary`` use other labels (``'yes'`` / ``'no'``, ``'1'`` / ``'0'``,
|
|
…) and are NOT matched. ``... | bool | string | lower`` is also NOT
|
|
matched — it lower-cases the output to the literal token
|
|
``true`` / ``false``, which IS sometimes required by Rails / Elixir
|
|
consumers.
|
|
|
|
2. ``redundant-bool-comparison`` — ``... | bool == True/False`` and the
|
|
lower-case / quoted variants (``== true``, ``== "false"``, …).
|
|
Comparing a boolean against a boolean literal is a tautology that
|
|
hides intent. Convention::
|
|
|
|
when: FOO | bool # truthy branch
|
|
when: not (FOO | bool) # falsy branch
|
|
|
|
Per-line opt-out::
|
|
|
|
# nocheck: redundant-bool-ternary
|
|
{# nocheck: redundant-bool-comparison #}
|
|
|
|
The marker grammar is documented at
|
|
``docs/contributing/actions/testing/suppression.md``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.annotations.suppress import suppressed_line_numbers
|
|
from utils.cache.files import PROJECT_ROOT, iter_project_files, read_text
|
|
|
|
_PATTERN_TERNARY: re.Pattern[str] = re.compile(
|
|
r"""\|\s*ternary\(\s*['"]true['"]\s*,\s*['"]false['"]\s*\)""",
|
|
)
|
|
|
|
_PATTERN_COMPARISON: re.Pattern[str] = re.compile(
|
|
r"""\|\s*bool\s*==\s*(?:['"]?(?:true|false)['"]?)""",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
_SCAN_EXTENSIONS: tuple[str, ...] = (".j2", ".yml", ".yaml")
|
|
|
|
|
|
def _scan_paths() -> list[Path]:
|
|
"""Iterate every production template / YAML file via the shared
|
|
file-walk cache (tests dir excluded)."""
|
|
out: list[Path] = []
|
|
for s in iter_project_files(extensions=_SCAN_EXTENSIONS, exclude_tests=True):
|
|
p = Path(s)
|
|
try:
|
|
p.relative_to(PROJECT_ROOT)
|
|
except ValueError:
|
|
continue
|
|
out.append(p)
|
|
return sorted(out)
|
|
|
|
|
|
def _file_offenders(path: Path, pattern: re.Pattern[str], rule: str) -> list[str]:
|
|
"""Return ``[]`` if the file is clean, or a list of human-readable
|
|
``line N: <snippet>`` strings for each match of ``pattern`` on a
|
|
line not annotated with ``noqa: <rule>``.
|
|
"""
|
|
try:
|
|
src = read_text(str(path))
|
|
except (OSError, UnicodeDecodeError):
|
|
return []
|
|
|
|
lines = src.splitlines()
|
|
noqa_lines = suppressed_line_numbers(lines, rule)
|
|
|
|
offenders: list[str] = []
|
|
for idx, line in enumerate(lines, start=1):
|
|
if idx in noqa_lines:
|
|
continue
|
|
if pattern.search(line) is None:
|
|
continue
|
|
offenders.append(f"line {idx}: {line.strip()}")
|
|
return offenders
|
|
|
|
|
|
def _collect(pattern: re.Pattern[str], rule: str) -> dict[Path, list[str]]:
|
|
out: dict[Path, list[str]] = {}
|
|
for path in _scan_paths():
|
|
issues = _file_offenders(path, pattern, rule)
|
|
if issues:
|
|
out[path] = issues
|
|
return out
|
|
|
|
|
|
def _format_failure(
|
|
headline: str,
|
|
offenders: dict[Path, list[str]],
|
|
fix: str,
|
|
) -> str:
|
|
rel = lambda p: p.relative_to(PROJECT_ROOT) # noqa: E731
|
|
lines = [headline]
|
|
for path, issues in sorted(offenders.items()):
|
|
lines.append(f" - {rel(path)}:")
|
|
lines.extend(f" * {issue}" for issue in issues)
|
|
lines.append("")
|
|
lines.append(fix)
|
|
return "\n".join(lines)
|
|
|
|
|
|
class TestNoRedundantBoolPatterns(unittest.TestCase):
|
|
"""Reject ``| bool | ternary(…)`` and ``| bool == <bool-literal>``."""
|
|
|
|
def test_no_redundant_bool_ternary(self) -> None:
|
|
offenders = _collect(_PATTERN_TERNARY, "redundant-bool-ternary")
|
|
if not offenders:
|
|
return
|
|
self.fail(
|
|
_format_failure(
|
|
f"{len(offenders)} file(s) end a boolean expression with "
|
|
f"`| ternary('true','false')`. That ternary is a no-op on "
|
|
f"a value that is already a boolean — its only effect is "
|
|
f"to swap `True`/`False` for the lowercase string tokens "
|
|
f"`'true'`/`'false'`, which every consumer the project "
|
|
f"actually feeds them to accepts unchanged:",
|
|
offenders,
|
|
"Fix: drop the `| ternary('true','false')` segment. The "
|
|
"preceding expression already evaluates to the truth value "
|
|
"you want — `FOO | bool`, `'web-app-X' in group_names`, "
|
|
"`(A == B)`, `FOO is defined`, etc. If the consumer "
|
|
"truly requires the literal lowercase tokens 'true'/'false', "
|
|
"use `... | bool | string | lower` and annotate the line "
|
|
"with `noqa: redundant-bool-ternary` (with a one-line "
|
|
"comment explaining why).",
|
|
)
|
|
)
|
|
|
|
def test_no_redundant_bool_comparison(self) -> None:
|
|
offenders = _collect(_PATTERN_COMPARISON, "redundant-bool-comparison")
|
|
if not offenders:
|
|
return
|
|
self.fail(
|
|
_format_failure(
|
|
f"{len(offenders)} file(s) compare `... | bool` against a "
|
|
f"bool literal. The comparison is a no-op — `... | bool` "
|
|
f"already produces the boolean you are testing for:",
|
|
offenders,
|
|
"Fix: replace `FOO | bool == True` with `FOO | bool` and "
|
|
"`FOO | bool == False` with `not (FOO | bool)`. If a real "
|
|
"tri-state guard is intended, annotate the line with "
|
|
"`noqa: redundant-bool-comparison` and document why.",
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
unittest.main()
|