import ast import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCRIPT_PATHS = sorted((ROOT / "scripts").glob("*.py")) + sorted( (ROOT / ".gitea" / "ci").glob("*.py") ) class ScriptLineEndingTests(unittest.TestCase): def test_script_text_writes_pin_lf_newlines(self): offenders: list[str] = [] for path in SCRIPT_PATHS: tree = ast.parse(path.read_text(encoding="utf-8")) for node in ast.walk(tree): if not isinstance(node, ast.Call): continue if is_write_text_without_lf_newline(node): offenders.append(f"{path.relative_to(ROOT)}:{node.lineno}") elif is_text_open_for_writing_without_newline(node): offenders.append(f"{path.relative_to(ROOT)}:{node.lineno}") self.assertEqual(offenders, []) def has_lf_newline_keyword(node: ast.Call) -> bool: newline = next( (keyword for keyword in node.keywords if keyword.arg == "newline"), None, ) return ( newline is not None and isinstance(newline.value, ast.Constant) and newline.value.value == "\n" ) def is_write_text_without_lf_newline(node: ast.Call) -> bool: func = node.func return ( isinstance(func, ast.Attribute) and func.attr == "write_text" and not has_lf_newline_keyword(node) ) def is_text_open_for_writing_without_newline(node: ast.Call) -> bool: func = node.func if isinstance(func, ast.Name): is_open = func.id == "open" elif isinstance(func, ast.Attribute): is_open = func.attr == "open" else: is_open = False if not is_open: return False mode_node = None if len(node.args) >= 2: mode_node = node.args[1] for keyword in node.keywords: if keyword.arg == "mode": mode_node = keyword.value break if mode_node is None: return False if not isinstance(mode_node, ast.Constant) or not isinstance(mode_node.value, str): return False mode = mode_node.value writes_text = any(flag in mode for flag in ("w", "a", "x")) and "b" not in mode return writes_text and not has_lf_newline_keyword(node) if __name__ == "__main__": unittest.main()