ci(#464): guard against em-dash characters in lib/ and test/

Add .github/scripts/check_no_emdash.py and run it as the first step of
the analyze job, so it fails in seconds before the flutter setup. It
rejects the em-dash character (U+2014) in lib/ and test/ source (comments
and string literals) plus app_en.arb, excluding generated files, non-
English ARB, and the lone "no data" glyph placeholder ('—' as an entire
quoted value). Stops the regression that #457 and #462 had to sweep by
hand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/508/head
Strycher 2 months ago
parent 49b6bfe2f5
commit 7a2fca929a

@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Fail if an em-dash (U+2014) appears in source-authored text.
The em-dash reads as an AI tell, so it is kept out of the public repo (#457,
#462, #464). This guard runs in CI (the `analyze` job) and can also be wired
into a local pre-commit hook.
In scope:
- lib/**/*.dart and test/**/*.dart (code comments + string literals)
- lib/l10n/app_en.arb (English UI copy)
Out of scope (never flagged):
- Generated files: *.g.dart, lib/l10n/app_localizations*.dart
- Non-English ARB (lib/l10n/app_*.arb except app_en.arb): an em-dash may be
legitimate target-language punctuation.
- The lone "no data / not available" glyph placeholder: a dash that is the
entire quoted value, e.g. `return '—';`, `"key": "—"`, `text: '—'`. That is
a deliberate UI element, not prose.
Exit status: 0 when clean, 1 when any in-scope em-dash is found (each printed
as file:line: <content>).
"""
from __future__ import annotations
import os
import sys
EM_DASH = "—"
ROOTS = ("lib", "test")
def _has_prose_emdash(line: str) -> bool:
"""True if the line has an em-dash that is NOT a lone glyph placeholder.
The lone "no data" glyph is a dash that is the entire quoted value (`'—'`
or `"—"`). Strip those tokens first, so a line carrying both a glyph and a
real prose em-dash is still flagged.
"""
stripped = line.replace("'" + EM_DASH + "'", "").replace('"' + EM_DASH + '"', "")
return EM_DASH in stripped
def _in_scope(path: str) -> bool:
base = os.path.basename(path)
if base.endswith(".g.dart") or base.startswith("app_localizations"):
return False
if base.endswith(".dart"):
return True
# ARB: only the English source is in scope.
return base == "app_en.arb"
def find_violations(roots=ROOTS):
hits = []
for root in roots:
if not os.path.isdir(root):
continue
for dirpath, _dirs, filenames in os.walk(root):
for name in filenames:
if not (name.endswith(".dart") or name.endswith(".arb")):
continue
path = os.path.join(dirpath, name)
if not _in_scope(path):
continue
with open(path, encoding="utf-8") as fh:
for lineno, line in enumerate(fh, 1):
if _has_prose_emdash(line):
hits.append((path.replace(os.sep, "/"), lineno, line.rstrip()))
return hits
def main() -> int:
roots = tuple(sys.argv[1:]) or ROOTS
hits = find_violations(roots)
if not hits:
print("check_no_emdash: OK (no em-dash characters in source)")
return 0
print(
"check_no_emdash: found the em-dash character (U+2014) in source. "
"Replace it with a comma, colon, or period (see #464):",
file=sys.stderr,
)
for path, lineno, content in hits:
print(f" {path}:{lineno}: {content}", file=sys.stderr)
print(f"\n{len(hits)} em-dash(es) found.", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())

@ -14,6 +14,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Guard against em-dash characters (#464)
run: python3 .github/scripts/check_no_emdash.py
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:

Loading…
Cancel
Save

Powered by TurnKey Linux.