From e07d020ab5edbc3c0277e2baf22ecdcc71aea342 Mon Sep 17 00:00:00 2001 From: brovatten Date: Wed, 10 Jun 2026 21:36:01 +0200 Subject: [PATCH] chore: add black pre-commit formatting --- .github/workflows/test.yml | 7 +++ .pre-commit-config.yaml | 10 ++++ pyproject.toml | 3 + scripts/cb_engine.py | 8 ++- scripts/diff_to_mermaid.py | 48 +++++++++++----- tests/test_build_cta.py | 3 +- tests/test_cb_engine.py | 103 +++++++++++++++++++++++----------- tests/test_diff_to_mermaid.py | 45 ++++++++++++--- 8 files changed, 166 insertions(+), 61 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 pyproject.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea9ddb1..a2a53d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,13 +27,20 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.13' - uses: actions/setup-go@v5 with: go-version: '1.22' + - name: Install pre-commit + run: python -m pip install pre-commit==3.8.0 - name: Install shellcheck run: sudo apt-get update && sudo apt-get install -y shellcheck - name: Install actionlint run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 + - name: Run pre-commit + run: pre-commit run --all-files - name: Run actionlint run: actionlint - name: Run shellcheck diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..38c1823 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +minimum_pre_commit_version: "3.8.0" + +default_language_version: + python: python3 + +repos: + - repo: https://github.com/psf/black + rev: 25.9.0 + hooks: + - id: black diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..38b5a01 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 120 +target-version = ["py313"] diff --git a/scripts/cb_engine.py b/scripts/cb_engine.py index d260437..4101233 100644 --- a/scripts/cb_engine.py +++ b/scripts/cb_engine.py @@ -121,7 +121,9 @@ def run_seed(repo: str, out: str, source_sha: str) -> None: print(f"Seeded static-analysis baseline in {out} (clusters: {summary or 'none'})") -def run_head(repo: str, out: str, name: str, run_id: str, depth: int, base_ref: str, target_ref: str, source_sha: str) -> None: +def run_head( + repo: str, out: str, name: str, run_id: str, depth: int, base_ref: str, target_ref: str, source_sha: str +) -> None: from codeboarding_workflows.analysis import BaselineUnavailableError, run_full, run_incremental from diagram_analysis.exceptions import IncrementalCacheMissingError @@ -243,7 +245,9 @@ def main(argv=None) -> int: elif args.cmd == "seed": run_seed(args.repo, args.out, args.source_sha) elif args.cmd == "head": - run_head(args.repo, args.out, args.name, args.run_id, args.depth, args.base_ref, args.target_ref, args.source_sha) + run_head( + args.repo, args.out, args.name, args.run_id, args.depth, args.base_ref, args.target_ref, args.source_sha + ) elif args.cmd == "health": Path(args.issues_out).write_text(str(run_health(args.artifact_dir, args.repo, args.name))) elif args.cmd == "validate-base": diff --git a/scripts/diff_to_mermaid.py b/scripts/diff_to_mermaid.py index 0696d3f..6908661 100644 --- a/scripts/diff_to_mermaid.py +++ b/scripts/diff_to_mermaid.py @@ -30,8 +30,8 @@ # GitHub's mermaid config caps (config.schema.yaml defaults; NOT raisable on # GitHub). Exceeding either renders a red error box with no diagram, so we stay # comfortably under and degrade to a changed-only / text fallback instead. -MAX_EDGES = 480 # hard cap 500 -MAX_TEXT = 45_000 # hard cap 50000 chars +MAX_EDGES = 480 # hard cap 500 +MAX_TEXT = 45_000 # hard cap 50000 chars # Primer-ish fills that read on both light and dark GitHub backgrounds. White # label text is set explicitly so it survives dark mode. @@ -89,8 +89,7 @@ def _has_method_changes(base: dict, current: dict) -> bool: base_by_file = _methods_by_file(base) current_by_file = _methods_by_file(current) return any( - base_by_file.get(fp, set()) != current_by_file.get(fp, set()) - for fp in set(base_by_file) | set(current_by_file) + base_by_file.get(fp, set()) != current_by_file.get(fp, set()) for fp in set(base_by_file) | set(current_by_file) ) @@ -122,7 +121,11 @@ def _diff_relations(base_rels: list, current_rels: list) -> list: continue if len(base_group) == 1 and len(current_group) == 1: - status = "unchanged" if (base_group[0].get("relation") or "") == (current_group[0].get("relation") or "") else "modified" + status = ( + "unchanged" + if (base_group[0].get("relation") or "") == (current_group[0].get("relation") or "") + else "modified" + ) result.append({**current_group[0], "diff_status": status}) continue @@ -227,9 +230,17 @@ def _sanitize(name: str) -> str: # node label and breaks the whole diagram, so escape the shape chars too — not # just ``#`` and ``"``. _ESC_MAP = { - "&": "#amp;", '"': "#quot;", "<": "#lt;", ">": "#gt;", - "[": "#91;", "]": "#93;", "(": "#40;", ")": "#41;", - "{": "#123;", "}": "#125;", "|": "#124;", + "&": "#amp;", + '"': "#quot;", + "<": "#lt;", + ">": "#gt;", + "[": "#91;", + "]": "#93;", + "(": "#40;", + ")": "#41;", + "{": "#123;", + "}": "#125;", + "|": "#124;", } @@ -338,8 +349,7 @@ def touches(r: dict, side_id: str, side_name: str) -> bool: rels = [ r for r in relations - if r.get("diff_status") in CHANGED - or (touches(r, "src_id", "src_name") and touches(r, "dst_id", "dst_name")) + if r.get("diff_status") in CHANGED or (touches(r, "src_id", "src_name") and touches(r, "dst_id", "dst_name")) ] return kept, rels @@ -379,8 +389,7 @@ def _count_changed_components(components: list) -> int: def _has_changed_relations(components: list, relations: list) -> bool: """Recursively: is any relation (at any nesting level) added/modified/deleted?""" return _has_changes([], relations) or any( - _has_changed_relations(c.get("components") or [], c.get("components_relations") or []) - for c in components or [] + _has_changed_relations(c.get("components") or [], c.get("components_relations") or []) for c in components or [] ) @@ -522,9 +531,18 @@ def main() -> int: p.add_argument("--out", required=True, type=Path, help="Where to write the ```mermaid block") p.add_argument("--direction", default="LR", choices=["LR", "TD", "TB", "RL", "BT"]) p.add_argument("--changed-only", action="store_true", help="Render only changed components + incident edges") - p.add_argument("--no-edge-labels", dest="edge_labels", action="store_false", help="Draw arrows without relation labels") - p.add_argument("--render-depth", type=int, default=1, help="Component levels to draw: 1=top-level flat, 2=+one nesting level, ...") - p.add_argument("--font-size", type=int, default=None, help="Node label font size in px (bigger label ⇒ bigger node)") + p.add_argument( + "--no-edge-labels", dest="edge_labels", action="store_false", help="Draw arrows without relation labels" + ) + p.add_argument( + "--render-depth", + type=int, + default=1, + help="Component levels to draw: 1=top-level flat, 2=+one nesting level, ...", + ) + p.add_argument( + "--font-size", type=int, default=None, help="Node label font size in px (bigger label ⇒ bigger node)" + ) p.add_argument("--node-padding", type=int, default=None, help="Interior padding around each node label") p.add_argument("--node-spacing", type=int, default=None, help="Space between nodes in the same rank") p.add_argument("--rank-spacing", type=int, default=None, help="Space between ranks") diff --git a/tests/test_build_cta.py b/tests/test_build_cta.py index 1f37f6f..1373315 100644 --- a/tests/test_build_cta.py +++ b/tests/test_build_cta.py @@ -43,7 +43,8 @@ def test_no_proxy_links_editor_to_https_listing_no_get_extension(self): def test_no_proxy_vscode_marketplace_https_no_banner_at_zero(self): out = bc.build_cta("", "o", "r", "1", repo_with()) # neither dir, no issues self.assertIn( - "[**Open in VS Code →**](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding)", out + "[**Open in VS Code →**](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding)", + out, ) self.assertNotIn("vscode:extension", out) # custom scheme stripped by GitHub self.assertNotIn("Get the extension", out) diff --git a/tests/test_cb_engine.py b/tests/test_cb_engine.py index 3d8b7b8..1c9e84f 100644 --- a/tests/test_cb_engine.py +++ b/tests/test_cb_engine.py @@ -16,10 +16,15 @@ import cb_engine # noqa: E402 _STUBBED = [ - "codeboarding_workflows", "codeboarding_workflows.analysis", - "diagram_analysis", "diagram_analysis.exceptions", - "health", "health.models", "health.runner", - "static_analyzer", "static_analyzer.analysis_cache", + "codeboarding_workflows", + "codeboarding_workflows.analysis", + "diagram_analysis", + "diagram_analysis.exceptions", + "health", + "health.models", + "health.runner", + "static_analyzer", + "static_analyzer.analysis_cache", "static_analyzer.cluster_helpers", ] @@ -85,30 +90,46 @@ def test_base_calls_run_full(self): def test_main_parses_depth_as_int(self): rf = _Rec() self._install(run_full=rf) - cb_engine.main([ - "base", - "--repo", "/repo", - "--out", "/out", - "--name", "myrepo", - "--run-id", "rid-base", - "--depth", "2", - "--source-sha", "abc123", - ]) + cb_engine.main( + [ + "base", + "--repo", + "/repo", + "--out", + "/out", + "--name", + "myrepo", + "--run-id", + "rid-base", + "--depth", + "2", + "--source-sha", + "abc123", + ] + ) self.assertEqual(rf.calls[0]["depth_level"], 2) def test_main_sets_github_action_source(self): rf = _Rec() self._install(run_full=rf) with patch.dict(os.environ, {}, clear=True): - cb_engine.main([ - "base", - "--repo", "/repo", - "--out", "/out", - "--name", "myrepo", - "--run-id", "rid-base", - "--depth", "2", - "--source-sha", "abc123", - ]) + cb_engine.main( + [ + "base", + "--repo", + "/repo", + "--out", + "/out", + "--name", + "myrepo", + "--run-id", + "rid-base", + "--depth", + "2", + "--source-sha", + "abc123", + ] + ) self.assertEqual(os.environ["CODEBOARDING_SOURCE"], "github_action") def test_main_rejects_invalid_depth(self): @@ -116,15 +137,23 @@ def test_main_rejects_invalid_depth(self): with self.subTest(depth=depth): with redirect_stderr(StringIO()): with self.assertRaises(SystemExit): - cb_engine.main([ - "base", - "--repo", "/repo", - "--out", "/out", - "--name", "myrepo", - "--run-id", "rid-base", - "--depth", depth, - "--source-sha", "abc123", - ]) + cb_engine.main( + [ + "base", + "--repo", + "/repo", + "--out", + "/out", + "--name", + "myrepo", + "--run-id", + "rid-base", + "--depth", + depth, + "--source-sha", + "abc123", + ] + ) def test_head_uses_incremental(self): ri, rf = _Rec(), _Rec() @@ -239,7 +268,9 @@ def save(self, res, source_sha=None): log.append(("save", res, source_sha)) sa = _mod("static_analyzer", get_static_analysis=get_static_analysis) - sa.cluster_helpers = _mod("static_analyzer.cluster_helpers", build_all_cluster_results=build_all_cluster_results) + sa.cluster_helpers = _mod( + "static_analyzer.cluster_helpers", build_all_cluster_results=build_all_cluster_results + ) sa.analysis_cache = _mod("static_analyzer.analysis_cache", StaticAnalysisCache=_Cache) return log, results @@ -288,9 +319,13 @@ def get(self): _mod("health.models", Severity=Severity) _mod("health.runner", run_health_checks=lambda sa, repo_name, repo_path: report) - _mod("health", ) + _mod( + "health", + ) _mod("static_analyzer.analysis_cache", StaticAnalysisCache=_Cache) - _mod("static_analyzer", ) + _mod( + "static_analyzer", + ) return Severity def test_counts_warning_and_critical(self): diff --git a/tests/test_diff_to_mermaid.py b/tests/test_diff_to_mermaid.py index 1b14daf..a089c97 100644 --- a/tests/test_diff_to_mermaid.py +++ b/tests/test_diff_to_mermaid.py @@ -76,8 +76,14 @@ def test_parallel_relation_deletion_is_not_label_modification(self): class TestRender(unittest.TestCase): def _diff(self): - base = {"components": [comp("A"), comp("B"), comp("Gone")], "components_relations": [rel("A", "B"), rel("A", "Gone")]} - head = {"components": [comp("A", {"x.py": ["f"]}), comp("B"), comp("New")], "components_relations": [rel("A", "B"), rel("A", "New")]} + base = { + "components": [comp("A"), comp("B"), comp("Gone")], + "components_relations": [rel("A", "B"), rel("A", "Gone")], + } + head = { + "components": [comp("A", {"x.py": ["f"]}), comp("B"), comp("New")], + "components_relations": [rel("A", "B"), rel("A", "New")], + } return dm.build_diff(base, head) def test_flat_default_has_no_subgraphs(self): @@ -88,8 +94,14 @@ def test_flat_default_has_no_subgraphs(self): self.assertTrue(linkstyle_indices_in_range(text)) def test_nested_subgraphs_balanced_and_valid(self): - base = {"components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[rel("c1", "c2")])], "components_relations": []} - head = {"components": [comp("P", subs=[comp("c1"), comp("c3")], subrels=[rel("c1", "c3")])], "components_relations": []} + base = { + "components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[rel("c1", "c2")])], + "components_relations": [], + } + head = { + "components": [comp("P", subs=[comp("c1"), comp("c3")], subrels=[rel("c1", "c3")])], + "components_relations": [], + } text, _ = dm.render_mermaid(dm.build_diff(base, head), render_depth=2) sg = sum(1 for line in text.splitlines() if line.strip().startswith("subgraph ")) en = sum(1 for line in text.splitlines() if line.strip() == "end") @@ -164,8 +176,14 @@ def test_nested_method_change_highlights_collapsed_parent(self): self.assertIn("class n_P modified;", text) def test_nested_relation_change_highlights_collapsed_parent(self): - base = {"components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[rel("c1", "c2", "uses")])], "components_relations": []} - head = {"components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[rel("c1", "c2", "calls")])], "components_relations": []} + base = { + "components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[rel("c1", "c2", "uses")])], + "components_relations": [], + } + head = { + "components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[rel("c1", "c2", "calls")])], + "components_relations": [], + } text, meta = dm.render_mermaid(dm.build_diff(base, head), render_depth=1) self.assertEqual(meta["n_changed"], 0) self.assertTrue(meta["changed"]) @@ -173,7 +191,10 @@ def test_nested_relation_change_highlights_collapsed_parent(self): def test_changed_only_keeps_nested_change(self): base = {"components": [comp("P", subs=[comp("c1"), comp("c2")], subrels=[])], "components_relations": []} - head = {"components": [comp("P", subs=[comp("c1", {"x.py": ["f"]}), comp("c2")], subrels=[])], "components_relations": []} + head = { + "components": [comp("P", subs=[comp("c1", {"x.py": ["f"]}), comp("c2")], subrels=[])], + "components_relations": [], + } text, meta = dm.render_mermaid(dm.build_diff(base, head), render_depth=2, changed_only=True) self.assertIsNotNone(text) self.assertTrue(meta["changed"]) @@ -183,8 +204,14 @@ def test_changed_only_keeps_nested_change(self): self.assertNotIn('n_c2["c2"]', text) def test_changed_only_prunes_unchanged_children_of_modified_parent(self): - base = {"components": [comp("P", {"p.py": ["old"]}, subs=[comp("c1"), comp("c2")], subrels=[])], "components_relations": []} - head = {"components": [comp("P", {"p.py": ["old", "new"]}, subs=[comp("c1"), comp("c2")], subrels=[])], "components_relations": []} + base = { + "components": [comp("P", {"p.py": ["old"]}, subs=[comp("c1"), comp("c2")], subrels=[])], + "components_relations": [], + } + head = { + "components": [comp("P", {"p.py": ["old", "new"]}, subs=[comp("c1"), comp("c2")], subrels=[])], + "components_relations": [], + } text, meta = dm.render_mermaid(dm.build_diff(base, head), render_depth=2, changed_only=True) self.assertIsNotNone(text) self.assertTrue(meta["changed"])