From 98545b24957e403dcdf91e2f2b9e3eb7421e4c82 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sat, 8 Nov 2025 03:06:02 +0900 Subject: [PATCH] Implement HC014: Missing Entrypoint detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview Detects when no valid entrypoint (Main.main or main) exists in analyzed code. ## Implementation Details - **Rule**: `rule_missing_entrypoint.hako` following single-responsibility box principles - **Detection**: Checks if any entrypoint from entrypoints[] exists in methods[] - **Pattern Matching**: Matches "Main.main" or "main" with any arity (e.g., Main.main/0, Main.main/1) - **Integration**: Added to cli.hako with debug output support ## Test Cases - **ok.hako**: Main box with main() method → no warning - **ng.hako**: Main box with run() method (not main) → HC014 + HC011 warnings - HC011: Main.run/0 unreachable (no entrypoint calling it) - HC014: Missing entrypoint (correct cascading diagnostics) ## Test Results ``` [TEST/OK] HC011_dead_methods [TEST/OK] HC012_dead_static_box [TEST/OK] HC013_duplicate_method [TEST/OK] HC014_missing_entrypoint ← NEW [TEST/OK] HC016_unused_alias [TEST/SUMMARY] all green ``` ## Architecture - Box-first design: RuleMissingEntrypointBox with single responsibility - Helper method: _has_entrypoint_method() for clean separation of concerns - Diagnostic format: "[HC014] missing entrypoint (Main.main or main)" - Severity: "warning" (non-blocking, informational) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tools/hako_check/cli.hako | 8 +++ .../rules/rule_missing_entrypoint.hako | 64 +++++++++++++++++++ .../HC014_missing_entrypoint/expected.json | 4 ++ .../tests/HC014_missing_entrypoint/ng.hako | 7 ++ .../tests/HC014_missing_entrypoint/ok.hako | 14 ++++ 5 files changed, 97 insertions(+) create mode 100644 tools/hako_check/rules/rule_missing_entrypoint.hako create mode 100644 tools/hako_check/tests/HC014_missing_entrypoint/expected.json create mode 100644 tools/hako_check/tests/HC014_missing_entrypoint/ng.hako create mode 100644 tools/hako_check/tests/HC014_missing_entrypoint/ok.hako diff --git a/tools/hako_check/cli.hako b/tools/hako_check/cli.hako index beb04ea9..1d08fcd1 100644 --- a/tools/hako_check/cli.hako +++ b/tools/hako_check/cli.hako @@ -11,6 +11,7 @@ using tools.hako_check.rules.rule_unused_alias as RuleUnusedAliasBox using tools.hako_check.rules.rule_non_ascii_quotes as RuleNonAsciiQuotesBox using tools.hako_check.rules.rule_dead_static_box as RuleDeadStaticBoxBox using tools.hako_check.rules.rule_duplicate_method as RuleDuplicateMethodBox +using tools.hako_check.rules.rule_missing_entrypoint as RuleMissingEntrypointBox using tools.hako_check.render.graphviz as GraphvizRenderBox using tools.hako_parser.parser_core as HakoParserCoreBox @@ -101,6 +102,13 @@ static box HakoAnalyzerBox { local added = after_n - before_n print("[hako_check/HC013] file=" + p + " added=" + me._itoa(added) + " total_out=" + me._itoa(after_n)) } + before_n = out.size() + RuleMissingEntrypointBox.apply_ir(ir, p, out) + if debug == 1 { + local after_n = out.size() + local added = after_n - before_n + print("[hako_check/HC014] file=" + p + " added=" + me._itoa(added) + " total_out=" + me._itoa(after_n)) + } // suppression: HC012(dead box) > HC011(unreachable method) local filtered = me._suppress_overlap(out) // flush (text only) diff --git a/tools/hako_check/rules/rule_missing_entrypoint.hako b/tools/hako_check/rules/rule_missing_entrypoint.hako new file mode 100644 index 00000000..3426b1ce --- /dev/null +++ b/tools/hako_check/rules/rule_missing_entrypoint.hako @@ -0,0 +1,64 @@ +// tools/hako_check/rules/rule_missing_entrypoint.hako — HC014: Missing Entrypoint +// Detect when no entrypoint (Main.main or main) exists in the analyzed code. + +using selfhost.shared.common.string_helpers as Str + +static box RuleMissingEntrypointBox { + method apply_ir(ir, path, out) { + local entrypoints = ir.get("entrypoints") + if entrypoints == null { return 0 } + + local methods = ir.get("methods") + if methods == null || methods.size() == 0 { + // No methods at all - definitely missing entrypoint + out.push("[HC014] missing entrypoint (Main.main or main)") + return out.size() + } + + // Check if any entrypoint exists + local found = 0 + local ei = 0 + while ei < entrypoints.size() { + local ep = entrypoints.get(ei) + if me._has_entrypoint_method(methods, ep) == 1 { + found = 1 + break + } + ei = ei + 1 + } + + // If no entrypoint found, report error + if found == 0 { + out.push("[HC014] missing entrypoint (Main.main or main)") + } + + return out.size() + } + + _has_entrypoint_method(methods, ep_name) { + // Check if method starting with ep_name exists + // e.g., "Main.main" matches "Main.main/0", "Main.main/1", etc. + // "main" matches "main/0", etc. + local mi = 0 + while mi < methods.size() { + local m = methods.get(mi) + if m == null { mi = mi + 1; continue } + + // Check exact match with arity suffix + // ep_name can be "Main.main" or "main" + local len = ep_name.length() + if m.length() >= len + 2 { + local prefix = m.substring(0, len) + if prefix == ep_name { + // Check if followed by "/" (arity separator) + local next_char = m.substring(len, len+1) + if next_char == "/" { return 1 } + } + } + mi = mi + 1 + } + return 0 + } +} + +static box RuleMissingEntrypointMain { method main(args) { return 0 } } diff --git a/tools/hako_check/tests/HC014_missing_entrypoint/expected.json b/tools/hako_check/tests/HC014_missing_entrypoint/expected.json new file mode 100644 index 00000000..d75181a2 --- /dev/null +++ b/tools/hako_check/tests/HC014_missing_entrypoint/expected.json @@ -0,0 +1,4 @@ +{"diagnostics":[ + {"file":"ng.hako","line":1,"rule":"HC011","message":"[HC011] unreachable method (dead code): PLACEHOLDER :: Main.run/0","quickFix":"Remove or reference the dead method from an entrypoint","severity":"error"}, + {"file":"ng.hako","line":1,"rule":"HC014","message":"[HC014] missing entrypoint (Main.main or main)","quickFix":"","severity":"warning"} +]} diff --git a/tools/hako_check/tests/HC014_missing_entrypoint/ng.hako b/tools/hako_check/tests/HC014_missing_entrypoint/ng.hako new file mode 100644 index 00000000..23c464c8 --- /dev/null +++ b/tools/hako_check/tests/HC014_missing_entrypoint/ng.hako @@ -0,0 +1,7 @@ +// ng.hako — missing Main.main entrypoint + +static box Main { + method run() { + return 0 + } +} diff --git a/tools/hako_check/tests/HC014_missing_entrypoint/ok.hako b/tools/hako_check/tests/HC014_missing_entrypoint/ok.hako new file mode 100644 index 00000000..bdff0ea9 --- /dev/null +++ b/tools/hako_check/tests/HC014_missing_entrypoint/ok.hako @@ -0,0 +1,14 @@ +// ok.hako — has Main.main entrypoint + +static box Helper { + method calculate(x) { + return x * 2 + } +} + +static box Main { + method main() { + Helper.calculate(42) + return 0 + } +}