Run tree-sitter-backed C++/Qt rules on `src`. Returns one Finding per issue. `fence_mask[i]` is True when source line `i+1` sits inside a `// code-verify off` / `// code-verify on` fence and must be skipped. The rules implemented here mirror CLAUDE.md's "Common Mistakes" table and t
(src: bytes, path: Path, fence_mask: list[bool])
| 1438 | |
| 1439 | |
| 1440 | def _cpp_rules(src: bytes, path: Path, fence_mask: list[bool]) -> list[Finding]: |
| 1441 | """Run tree-sitter-backed C++/Qt rules on `src`. Returns one Finding per |
| 1442 | issue. `fence_mask[i]` is True when source line `i+1` sits inside a |
| 1443 | `// code-verify off` / `// code-verify on` fence and must be skipped. |
| 1444 | |
| 1445 | The rules implemented here mirror CLAUDE.md's "Common Mistakes" table |
| 1446 | and the NASA Power-of-Ten section: |
| 1447 | |
| 1448 | qt-bare-emit bare `emit` outside strings/comments |
| 1449 | qt-uppercase-signal-slot Q_SIGNALS: / Q_SLOTS: section labels |
| 1450 | qt-invokable-void Q_INVOKABLE void f() (use `public slots:`) |
| 1451 | qt-header-member-init int m_foo = 0; in a header class body |
| 1452 | qt-disconnect-nullptr disconnect(..., nullptr) as the slot arg |
| 1453 | qt-direct-jsengine-call parseFunction.call(...) without guardedCall |
| 1454 | cxx-function-too-long function definition over 100 lines |
| 1455 | cxx-nesting-too-deep control-flow nesting > 3 levels |
| 1456 | cxx-goto-or-jmp goto / setjmp / longjmp |
| 1457 | cxx-while-loop `while (cond)` (use bounded `for` instead) |
| 1458 | doc-missing-brief-cpp .cpp function definition without /** @brief */ |
| 1459 | doc-missing-brief-h header type-level definition without /** @brief */ |
| 1460 | doc-verbose-brief doxygen block carries @param/@return/blank-`*` |
| 1461 | paragraphs or wraps past 6 lines (a brief may |
| 1462 | absorb an in-body why, but stays <=6 lines) |
| 1463 | cxx-inbody-comment comment inside a function body (functions are |
| 1464 | short; the @brief above + the code carry it) |
| 1465 | cxx-trivial-function function with an empty / only-Q_UNUSED / single |
| 1466 | constant-return body (dead-code candidate) |
| 1467 | cxx-scattered-constant file-scope static/constexpr/const declared after |
| 1468 | the first function (hoist to the top section) |
| 1469 | hotpath-allocation allocation/append on a known hotpath method |
| 1470 | keys-hardcoded-literal raw "busType" etc. literal where Keys:: belongs |
| 1471 | cxx-anonymous-namespace helpers/types defined inside `namespace { ... }` |
| 1472 | """ |
| 1473 | if not HAS_TREE_SITTER: |
| 1474 | return [] |
| 1475 | |
| 1476 | out: list[Finding] = [] |
| 1477 | tree = _CPP_PARSER.parse(src) |
| 1478 | root = tree.root_node |
| 1479 | is_header = path.suffix in {".h", ".hpp", ".hxx"} |
| 1480 | |
| 1481 | def fenced(line: int) -> bool: |
| 1482 | idx = line - 1 |
| 1483 | return 0 <= idx < len(fence_mask) and fence_mask[idx] |
| 1484 | |
| 1485 | # ---- qt-bare-emit / qt-uppercase-signal-slot / cxx-goto / Q_INVOKABLE void |
| 1486 | # The fastest pass that catches text-level Qt mistakes is a token sweep |
| 1487 | # that respects strings/comments. We use a tiny per-line scan that |
| 1488 | # strips comments and string literals first. |
| 1489 | src_text = src.decode("utf-8", errors="replace") |
| 1490 | lines = src_text.split("\n") |
| 1491 | for i, raw in enumerate(lines, start=1): |
| 1492 | if fenced(i): |
| 1493 | continue |
| 1494 | scrubbed = _strip_strings_and_line_comments(raw) |
| 1495 | # Bare `emit` -- token must be word-isolated. |
| 1496 | if re.search(r"\bemit\b\s+[A-Za-z_]", scrubbed): |
| 1497 | out.append( |
no test coverage detected