📝 docs(tsl): clarify syntax constraints and enhance test infrastructure
- Add TSF file naming constraint to syntax/02_core_model.md - Clarify semicolon rules: syntax facts vs style preferences - Separate control flow end semicolon rules (syntax allows both) - Add function body semicolon requirements to syntax/05_functions_and_calls.md - Move style preferences to code_style.md (control flow end semicolons) - Remove cross-references from syntax docs to maintain independence - Enhance Gitea workflow emoji for better CI output readability - Fix CI test path from tests/ to test/ - Organize agent test results under test/agent/result/ directory - Add complete Chinese translation of test cases (test_cases_zh.md) - Clean up .gitignore to use unified test/agent/result/ directory - Remove obsolete agent test artifacts (REPORTS_LOCATION.md, old results) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user