Download bin/external.py from DBax127/nexa: direct link, hf CLI and curl.
- Browser
- Download file 8.36 kB
-
https://huggingface.co/DBax127/nexa/resolve/main/bin/external.py
- Command line
-
hf download hf://DBax127/nexa/bin/external.py
-
curl -L -o external.py https://huggingface.co/DBax127/nexa/resolve/main/bin/external.py
8.36 kB
| #!/usr/bin/env python3 | |
| """Somebody else's analyser, normalised into findings this repository can measure. | |
| WHY THIS EXISTS | |
| --------------- | |
| `crosswalk` compares this corpus against another ruleset by reading the other | |
| ruleset's PATTERNS. It says so in its own docstring, and says why: "Semgrep is | |
| not installed here and adding it would make an offline tool need a package | |
| index." That reasoning was about a dependency, and it conflated two things. | |
| Running another analyser costs `stdlib only`. It does not cost OFFLINE. pylint | |
| runs on this machine; OpenSPP's lint scripts are Python in their own tree. | |
| Nothing leaves the laptop either way, which is the promise that matters. | |
| So this module shells out rather than importing. subprocess and json are stdlib, | |
| the analyser is optional, and an absent one is a GAP rather than an error -- | |
| the same shape as the OpenTelemetry bridge in telemetry.py, for the same reason. | |
| WHAT IT IS FOR, AND IT IS NOT COVERAGE | |
| -------------------------------------- | |
| Measured on OpenSPP, the two tool sets barely overlap. Their seven checkers | |
| found 748 things: N+1 queries, naming conventions, UI patterns, ACL files, PII | |
| in logs. Not one of them checks @api.depends completeness, @api.constrains field | |
| lists, ondelete or check_company. A production Odoo team wrote their own | |
| tooling from scratch and did not build what this corpus is. | |
| So wrapping them is not how this gets better at what it already does. It is two | |
| other things: | |
| ONE TOOL an Odoo shop runs one command instead of three, and the findings | |
| arrive ranked together with a single suppression convention. | |
| AIM `recall --aim` can be pointed at THEIR rules. performance.n_plus_one | |
| is 445 of their 748 findings -- 60% from a single check -- and | |
| nobody has ever published its false-positive rate or how often it | |
| lands on a line somebody actually fixed. This repository has the | |
| harness to say. That is the part worth having. | |
| An external finding arrives at likelihood "unmeasured" by definition. That is | |
| not a placeholder, it is the accurate value: nobody has measured it yet, and | |
| measuring it is the point. | |
| Stdlib only. | |
| """ | |
| import io | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from layers import SourceHit, SUPPRESSION_MARKERS # noqa: E402 | |
| # Their words for how bad it is, in ours. Deliberately conservative: an external | |
| # rule nobody here has classified does not get to claim `critical`, because the | |
| # severity axis in this corpus means "silently ships bad data to production" and | |
| # that is a judgement about the rule, not a label it arrives wearing. | |
| SEVERITY = { | |
| "error": "high", "fatal": "high", "critical": "high", | |
| "warning": "medium", "warn": "medium", "convention": "medium", | |
| "refactor": "medium", "info": "medium", "information": "medium", | |
| "note": "medium", | |
| } | |
| def _relative(path, root): | |
| try: | |
| return os.path.relpath(path, root).replace(os.sep, "/") | |
| except ValueError: # different drive on Windows | |
| return path.replace(os.sep, "/") | |
| def _hit(namespace, rule_id, severity, line, message, body_lines): | |
| """One external finding as a SourceHit. | |
| The rule id is namespaced so an external id can never be mistaken for one | |
| of ours, in a report or in an artifact somebody diffs six months from now. | |
| """ | |
| marked = False | |
| if body_lines and 0 < line <= len(body_lines): | |
| marked = any(m in body_lines[line - 1] for m in SUPPRESSION_MARKERS) | |
| return SourceHit( | |
| "{0}:{1}".format(namespace, rule_id), | |
| SEVERITY.get(str(severity).lower(), "medium"), | |
| line, message, marked, False, "unmeasured") | |
| def from_json(path, root, namespace="external"): | |
| """Findings from an analyser that wrote JSON, as {relative path: [SourceHit]}. | |
| Reads the shape OpenSPP's lint emits and pylint's --output-format=json, which | |
| agree on everything that matters: a file, a line, an id, a message, and a | |
| word for severity. Keys are tried in order rather than required, so an | |
| analyser that spells one of them differently still lands. | |
| """ | |
| try: | |
| with io.open(path, encoding="utf-8-sig") as handle: | |
| payload = json.load(handle) | |
| except (OSError, ValueError): | |
| return {} | |
| rows = payload.get("violations") or payload.get("results") or payload \ | |
| if isinstance(payload, dict) else payload | |
| if not isinstance(rows, list): | |
| return {} | |
| out = {} | |
| bodies = {} | |
| for row in rows: | |
| if not isinstance(row, dict): | |
| continue | |
| where = row.get("file") or row.get("path") or row.get("filename") | |
| rule = (row.get("rule_id") or row.get("message-id") | |
| or row.get("symbol") or row.get("check") or "?") | |
| if not where: | |
| continue | |
| rel = _relative(where, root) if os.path.isabs(where) else \ | |
| where.replace(os.sep, "/") | |
| if rel not in bodies: | |
| bodies[rel] = _read_lines(os.path.join(root, rel.replace("/", os.sep))) | |
| out.setdefault(rel, []).append(_hit( | |
| namespace, rule, row.get("severity") or row.get("type") or "warning", | |
| int(row.get("line") or 0) or 1, | |
| (row.get("message") or "").strip(), bodies[rel])) | |
| return out | |
| def _read_lines(path): | |
| try: | |
| with io.open(path, encoding="utf-8", errors="ignore") as handle: | |
| return handle.read().splitlines() | |
| except OSError: | |
| return [] | |
| def run_pylint(root, plugin="pylint_odoo", timeout=900): | |
| """pylint with a plugin, if this machine has it. {} if it does not. | |
| Shelled out rather than imported, so nexa stays stdlib-only and pylint stays | |
| something you either have or do not. Absent is reported by the caller as a | |
| gap, never as a failure: a review that could not run somebody else's checker | |
| is still a review, and saying which one was missing is the whole contract | |
| the layers already hold to. | |
| """ | |
| command = [sys.executable, "-m", "pylint", "--output-format=json", | |
| "--load-plugins", plugin, "--disable=all", | |
| "--enable=odoolint", root] | |
| try: | |
| proc = subprocess.run(command, capture_output=True, text=True, | |
| encoding="utf-8", errors="replace", timeout=timeout) | |
| except (OSError, subprocess.SubprocessError): | |
| return {} | |
| if not (proc.stdout or "").strip(): | |
| return {} | |
| try: | |
| rows = json.loads(proc.stdout) | |
| except ValueError: | |
| return {} | |
| out, bodies = {}, {} | |
| for row in rows: | |
| where = row.get("path") or "" | |
| if not where: | |
| continue | |
| rel = _relative(where, root) if os.path.isabs(where) else \ | |
| where.replace(os.sep, "/") | |
| if rel not in bodies: | |
| bodies[rel] = _read_lines(os.path.join(root, rel.replace("/", os.sep))) | |
| out.setdefault(rel, []).append(_hit( | |
| "pylint-odoo", row.get("symbol") or row.get("message-id") or "?", | |
| row.get("type") or "warning", int(row.get("line") or 1), | |
| (row.get("message") or "").strip(), bodies[rel])) | |
| return out | |
| def available(plugin="pylint_odoo"): | |
| """Is the pylint plugin here? Cheap enough to ask before promising anything.""" | |
| try: | |
| proc = subprocess.run( | |
| [sys.executable, "-m", "pylint", "--version"], | |
| capture_output=True, text=True, timeout=30) | |
| return proc.returncode == 0 | |
| except (OSError, subprocess.SubprocessError): | |
| return False | |
| def describe(findings): | |
| """One line per external analyser's contribution, for a report to print.""" | |
| rules = {hit.rule.split(":", 1)[0] for hits in findings.values() for hit in hits} | |
| total = sum(len(hits) for hits in findings.values()) | |
| return "{0} finding(s) from {1}, all at likelihood 'unmeasured'".format( | |
| total, ", ".join(sorted(rules)) or "no external analyser") | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 3: | |
| print(__doc__) | |
| sys.exit(2) | |
| found = from_json(sys.argv[1], sys.argv[2], | |
| sys.argv[3] if len(sys.argv) > 3 else "external") | |
| print(describe(found)) | |
| for rel in sorted(found)[:10]: | |
| for hit in found[rel][:2]: | |
| print(" {0}:{1} {2}".format(rel, hit.line, hit.rule)) | |