From 06784da18c9f97813cc05e48fd9f9a178f9f94f5 Mon Sep 17 00:00:00 2001 From: Shrayan Sarkar Date: Wed, 1 Jul 2026 03:31:43 +0530 Subject: [PATCH] feat: detect Go and Rust during init --- src/becwright/cli.py | 44 ++++++++++++++++++++++++++++--- tests/test_init.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/becwright/cli.py b/src/becwright/cli.py index 506aadd..14dd29e 100644 --- a/src/becwright/cli.py +++ b/src/becwright/cli.py @@ -138,31 +138,53 @@ def _cmd_list(_: argparse.Namespace) -> int: _SKIP_DIRS = {".git", "node_modules", ".venv", "venv", "__pycache__", "dist", "build", ".tox"} -_EXT_LANG = {".py": "python", ".js": "js", ".ts": "ts"} +_EXT_LANG = { + ".py": "python", + ".js": "js", + ".ts": "ts", + ".go": "go", + ".rs": "rust", +} def _detect_languages(root: Path) -> list[str]: found: set[str] = set() + for path in root.rglob("*"): if not path.is_file(): continue if any(part in _SKIP_DIRS for part in path.relative_to(root).parts): continue + lang = _EXT_LANG.get(path.suffix) if lang: found.add(lang) - return [lang for lang in ("python", "js", "ts") if lang in found] + + return [lang for lang in ("python", "js", "ts", "go", "rust") if lang in found] def _starter_rules(langs: list[str]) -> list[dict]: - source_globs = [g for lang, g in (("python", "**/*.py"), ("js", "**/*.js"), ("ts", "**/*.ts")) if lang in langs] + source_globs = [ + g + for lang, g in ( + ("python", "**/*.py"), + ("js", "**/*.js"), + ("ts", "**/*.ts"), + ("go", "**/*.go"), + ("rust", "**/*.rs"), + ) + if lang in langs + ] + rules: list[dict] = [] + if source_globs: rules.append(dict( id="no-hardcoded-secrets", paths=source_globs, severity="blocking", check="becwright run hardcoded_secrets", intent="No secret (key, token, password) should be hardcoded in the code.", why="A secret in the repo stays in git history forever and is visible to anyone with access to the code.")) + if "python" in langs: rules.append(dict( id="no-debug-remnants", paths=["**/*.py"], severity="blocking", @@ -174,6 +196,7 @@ def _starter_rules(langs: list[str]) -> list[dict]: check="becwright run dangerous_eval", intent="Avoid eval and exec, which run arbitrary code.", why="Dynamic eval/exec on untrusted input is remote code execution.")) + if "js" in langs or "ts" in langs: js_globs = [g for g in source_globs if g.endswith((".js", ".ts"))] rules.append(dict( @@ -186,6 +209,21 @@ def _starter_rules(langs: list[str]) -> list[dict]: check="becwright run forbid --pattern 'console\\.log\\s*\\('", intent="Avoid 'console.log(...)' in JavaScript/TypeScript code.", why="Debug console.log statements clutter production output.")) + + if "go" in langs: + rules.append(dict( + id="no-debug-go", paths=["**/*.go"], severity="blocking", + check=r"becwright run forbid --pattern 'fmt\.Println\s*\(|panic\s*\('", + intent="Do not leave debug output or panic calls in Go code.", + why="Debug statements and unexpected panic calls should not reach production.")) + + if "rust" in langs: + rules.append(dict( + id="no-debug-rust", paths=["**/*.rs"], severity="blocking", + check=r"becwright run forbid --pattern 'dbg!\s*\(|println!\s*\('", + intent="Do not leave debug output in Rust code.", + why="Debug macros and leftover println! calls should not reach production.")) + return rules diff --git a/tests/test_init.py b/tests/test_init.py index c300d82..4994b7c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -32,6 +32,16 @@ def test_detect_languages_none(tmp_path): (tmp_path / "README.md").write_text("# hi\n", encoding="utf-8") assert cli._detect_languages(tmp_path) == [] +def test_detect_languages_go(tmp_path): + (tmp_path / "main.go").write_text("package main\n", encoding="utf-8") + + assert cli._detect_languages(tmp_path) == ["go"] + + +def test_detect_languages_rust(tmp_path): + (tmp_path / "main.rs").write_text("fn main() {}\n", encoding="utf-8") + + assert cli._detect_languages(tmp_path) == ["rust"] # --- starter rules --- @@ -44,6 +54,42 @@ def test_starter_rules_js(): ids = [r["id"] for r in cli._starter_rules(["js"])] assert ids == ["no-hardcoded-secrets", "no-debugger-js", "no-console-log-js"] +def test_starter_rules_go(): + rules = cli._starter_rules(["go"]) + + assert [rule["id"] for rule in rules] == [ + "no-hardcoded-secrets", + "no-debug-go", + ] + + secrets_rule = rules[0] + debug_rule = rules[1] + + assert secrets_rule["paths"] == ["**/*.go"] + assert debug_rule["paths"] == ["**/*.go"] + assert debug_rule["severity"] == "blocking" + assert debug_rule["check"] == ( + r"becwright run forbid --pattern 'fmt\.Println\s*\(|panic\s*\('" + ) + + +def test_starter_rules_rust(): + rules = cli._starter_rules(["rust"]) + + assert [rule["id"] for rule in rules] == [ + "no-hardcoded-secrets", + "no-debug-rust", + ] + + secrets_rule = rules[0] + debug_rule = rules[1] + + assert secrets_rule["paths"] == ["**/*.rs"] + assert debug_rule["paths"] == ["**/*.rs"] + assert debug_rule["severity"] == "blocking" + assert debug_rule["check"] == ( + r"becwright run forbid --pattern 'dbg!\s*\(|println!\s*\('" + ) def test_starter_rules_empty(): assert cli._starter_rules([]) == [] @@ -51,6 +97,23 @@ def test_starter_rules_empty(): # --- rendering --- +def test_render_yaml_parses_go_and_rust_forbid_rules(tmp_path): + rules_path = tmp_path / "rules.yaml" + + rules_path.write_text( + cli._render_rules_yaml(cli._starter_rules(["go", "rust"])), + encoding="utf-8", + ) + + rules = load_rules(rules_path) + rule_ids = {rule.id for rule in rules} + + assert rule_ids == { + "no-hardcoded-secrets", + "no-debug-go", + "no-debug-rust", + } + def test_render_yaml_parses_and_keeps_forbid(tmp_path): p = tmp_path / "rules.yaml" p.write_text(cli._render_rules_yaml(cli._starter_rules(["js"])), encoding="utf-8")