(path: Path, fix: bool)
| 1675 | |
| 1676 | |
| 1677 | def process_file(path: Path, fix: bool) -> tuple[list[Violation], str | None]: |
| 1678 | # Read as bytes first so CRLF detection isn't masked by Python's universal |
| 1679 | # newline translation in text mode — read_text() silently rewrites \r\n |
| 1680 | # and bare \r to \n, hiding the very thing we want to flag. |
| 1681 | raw_bytes = path.read_bytes() |
| 1682 | raw_text = raw_bytes.decode("utf-8") |
| 1683 | raw_lines = raw_text.splitlines() |
| 1684 | |
| 1685 | first_party = _is_first_party(path) |
| 1686 | is_qml = first_party and path.suffix == ".qml" |
| 1687 | violations: list[Violation] = [] |
| 1688 | new_raws: list[str] = list(raw_lines) |
| 1689 | qml_changed = False |
| 1690 | id_blanks: list[int] = [] |
| 1691 | |
| 1692 | fence_mask = _compute_fence_mask(raw_lines) |
| 1693 | |
| 1694 | # CRLF/CR line endings get normalized to LF on rewrite. `splitlines()` |
| 1695 | # already discards \r\n / \r / \n uniformly, so the round-trip via |
| 1696 | # "\n".join() at the bottom of this function strips them — we just need |
| 1697 | # to register the change so the file actually gets written out. |
| 1698 | crlf_changed = b"\r" in raw_bytes |
| 1699 | if crlf_changed: |
| 1700 | violations.append( |
| 1701 | Violation(path, 1, "line-endings", "CRLF/CR line endings normalized to LF") |
| 1702 | ) |
| 1703 | |
| 1704 | # Comment-style and AI-narration checks apply to first-party C/C++/QML |
| 1705 | # only — vendored sources keep their upstream comment style. |
| 1706 | if first_party and path.suffix in _BRACE_FREE_SUFFIXES: |
| 1707 | violations.extend(find_comment_style_violations(raw_lines, path, fence_mask)) |
| 1708 | violations.extend(find_ai_narration_violations(raw_lines, path, fence_mask)) |
| 1709 | violations.extend(find_non_ascii_violations(raw_lines, path, fence_mask)) |
| 1710 | violations.extend( |
| 1711 | find_qml_inline_comment_violations(raw_lines, path, fence_mask) |
| 1712 | ) |
| 1713 | violations.extend( |
| 1714 | find_qml_underscore_property_violations(raw_lines, path, fence_mask) |
| 1715 | ) |
| 1716 | violations.extend(find_interrupt_guard_violations(raw_lines, path, fence_mask)) |
| 1717 | violations.extend(find_todouble_violations(raw_lines, path, fence_mask)) |
| 1718 | |
| 1719 | # Static-analysis rules (Qt/C++ semantic checks + QML conventions). |
| 1720 | # The rules module degrades gracefully when tree-sitter is missing. |
| 1721 | if _SEMANTIC_RULES is not None: |
| 1722 | for f in _SEMANTIC_RULES.analyze(path, raw_text, fence_mask): |
| 1723 | violations.append(Violation(path, f.line, f.kind, f.message)) |
| 1724 | |
| 1725 | # Christmas-tree property sort + id-placement check are QML-specific. |
| 1726 | if is_qml: |
| 1727 | lines = tokenize(raw_lines) |
| 1728 | id_violations, id_blanks = check_id_placement(lines, path) |
| 1729 | violations.extend(id_violations) |
| 1730 | |
| 1731 | runs = find_property_runs(lines) |
| 1732 | sortable_runs: list[tuple[int, int]] = [] |
| 1733 | for start, end in runs: |
| 1734 | run_lines = lines[start:end] |
no test coverage detected