// tools/hako_check/cli.hako — HakoAnalyzerBox (MVP) using tools.hako_check.analysis_consumer as HakoAnalysisBuilderBox using tools.hako_check.rules.rule_include_forbidden as RuleIncludeForbiddenBox using tools.hako_check.rules.rule_using_quoted as RuleUsingQuotedBox using tools.hako_check.rules.rule_static_top_assign as RuleStaticTopAssignBox using tools.hako_check.rules.rule_global_assign as RuleGlobalAssignBox using tools.hako_check.rules.rule_dead_methods as RuleDeadMethodsBox using tools.hako_check.rules.rule_jsonfrag_usage as RuleJsonfragUsageBox static box HakoAnalyzerBox { run(args) { if args == null || args.size() < 1 { print("[lint/error] missing paths"); return 2 } // options: --format {text|dot|json} local fmt = "text" local start = 0 if args.size() >= 2 && args.get(0) == "--format" { fmt = args.get(1) start = 2 } if args.size() <= start { print("[lint/error] missing paths"); return 2 } local fail = 0 local irs = new ArrayBox() // for i in start..(args.size()-1) local i = start while i < args.size() { local p = args.get(i) local f = new FileBox(); if f.open(p) == 0 { print("[lint/error] cannot open: " + p); fail = fail + 1; continue } local text = f.read(); f.close() // pre-sanitize (ASCII quotes, normalize newlines) — minimal & reversible text = me._sanitize(text) // analysis local ir = HakoAnalysisBuilderBox.build_from_source(text, p) irs.push(ir) // rules that work on raw source local out = new ArrayBox() RuleIncludeForbiddenBox.apply(text, p, out) RuleUsingQuotedBox.apply(text, p, out) RuleStaticTopAssignBox.apply(text, p, out) RuleGlobalAssignBox.apply(text, p, out) RuleJsonfragUsageBox.apply(text, p, out) // rules that need IR (enable dead code detection) RuleDeadMethodsBox.apply_ir(ir, p, out) // flush // for j in 0..(n-1) local n = out.size(); if n > 0 && fmt == "text" { local j = 0; while j < n { print(out.get(j)); j = j + 1 } } fail = fail + n i = i + 1 } // optional DOT/JSON output (MVP: dot only) if fmt == "dot" { me._render_dot_multi(irs) } // return number of findings as RC return fail } _sanitize(text) { if text == null { return text } // Normalize CRLF -> LF and convert fancy quotes to ASCII local out = "" local n = text.length() for i in 0..(n-1) { local ch = text.substring(i, i+1) // drop CR if ch == "\r" { continue } // fancy double quotes → ASCII if ch == "“" || ch == "”" { out = out.concat("\""); continue } // fancy single quotes → ASCII if ch == "‘" || ch == "’" { out = out.concat("'"); continue } out = out.concat(ch) } return out } _render_dot_multi(irs) { // Minimal DOT: emit method nodes; edges omitted in MVP print("digraph Hako {") if irs == null { print("}"); return 0 } local i = 0 while i < irs.size() { local ir = irs.get(i) if ir != null { local ms = ir.get("methods") if ms != null { local j = 0 while j < ms.size() { local name = ms.get(j) print(" \"" + name + "\";") j = j + 1 } } } i = i + 1 } print("}") return 0 } } static box HakoAnalyzerCliMain { method main(args) { return HakoAnalyzerBox.run(args) } }