use blue_lang_check::{Code, Severity, RULES};
use blue_lang_runtime::pipeline::{check_entry, run, syntax_diagnostic, Checking, RunError};
use blue_lang_runtime::uses::{Entry, Loader};
struct Imports(&'static [(&'static str, &'static str)]);
impl Loader for Imports {
fn load(&self, name: &str) -> Result<Vec<(String, String)>, String> {
self.0
.iter()
.find(|(n, _)| *n == name)
.map(|(n, src)| vec![(format!("{n}.b"), (*src).to_string())])
.ok_or_else(|| format!("no bidama named \"{name}\""))
}
fn needs(
&self,
_package: &str,
_entry_dir: Option<&std::path::Path>,
) -> Option<std::collections::BTreeSet<String>> {
Some(std::collections::BTreeSet::new())
}
fn version(&self, _package: &str, _entry_dir: Option<&std::path::Path>) -> Option<String> {
Some("0.1.1".to_string())
}
}
fn codes_with(src: &str, imports: &'static [(&'static str, &'static str)]) -> Vec<Code> {
if let Some(d) = syntax_diagnostic(src) {
return vec![d.code];
}
let checked = check_entry(
Entry {
path: Some(std::path::Path::new("witness.b")),
text: src,
},
&Imports(imports),
None,
Checking::WithTests,
)
.unwrap_or_else(|e| panic!("{src:?}: {e}"));
checked
.outcome
.diagnostics
.iter()
.chain(&checked.outcome.census)
.chain(&blue_lang_runtime::pipeline::form_diagnostics(src))
.map(|d| d.code)
.collect()
}
fn codes_of(src: &str) -> Vec<Code> {
codes_with(src, &[])
}
#[test]
fn every_rule_fires_on_its_witness_and_only_it() {
let mut failures = Vec::new();
for r in RULES {
let got = codes_with(r.witness, r.imports);
if got != vec![r.code] {
failures.push(format!("{}: expected [{}], got {got:?}", r.code, r.code));
}
}
assert!(failures.is_empty(), "{}", failures.join("\n"));
}
#[test]
fn a_clean_program_produces_no_diagnostic() {
let src = "# waive B0002: the callback shape is fixed\ndef on(event, ctx)\n event\nend\n\n\
def add(a: Int, b: Int) -> Int\n a + b\nend\n\n\
test \"adds\"\n assert add(1, 2) == 3\nend\n";
assert_eq!(codes_of(src), Vec::<Code>::new());
}
#[test]
fn a_typo_in_a_dead_function_stops_the_run() {
let r = run("def f(xs)\n lenght(xs)\nend\n\n1\n");
match r {
Err(RunError::Types(errors)) => {
assert_eq!(errors.len(), 1, "{errors:?}");
assert_eq!(
errors[0],
"<anonymous>:2:3: error[B0001]: unbound name `lenght`\n \
help: did you mean `length` (builtin)?"
);
}
other => panic!("a typo in a dead function must stop the run: {other:?}"),
}
}
#[test]
fn a_warning_does_not_stop_the_run() {
assert!(run("def f(x, unused)\n x\nend\n\nf(1, 2)\n").is_ok());
assert_eq!(Code::B0002.severity(), Severity::Warning);
}
#[test]
fn every_diagnostic_is_reported_in_one_pass() {
let src = "def f()\n aa()\nend\n\ndef g()\n bb()\nend\n\ndef h(x: Int) -> Str\n x\nend\n";
let got = codes_of(src);
assert_eq!(got, vec![Code::B0001, Code::B0001, Code::B0003], "{got:?}");
}
#[test]
fn a_name_a_builtin_macro_defines_is_bound() {
let src = "defflow(slug, trim, downcase)\n\nslug(\" A \")\n\n\
defsm(door, :initial, :closed, :transitions, [[:closed, :open, :opened]])\n\ndoor(:current)\n";
assert_eq!(codes_of(src), Vec::<Code>::new());
}
#[test]
fn a_waiver_on_an_unwaivable_rule_or_on_nothing_is_refused() {
const P: &[(&str, &str)] = &[
("kagi_a", "def kagi()\n 1\nend\n"),
("kagi_b", "def kagi()\n 2\nend\n"),
];
let src = "use(\"kagi_a\", [:kagi])\nuse(\"kagi_b\", [:kagi])\n\n# waive B0009: pick either\ndef f()\n kagi()\nend\n";
let got = codes_with(src, P);
assert!(
got.contains(&Code::B0007) && got.contains(&Code::B0009),
"{got:?}"
);
let unused = "# waive B0002: nothing here is unused\ndef f(x)\n x\nend\n\nf(1)\n";
assert_eq!(codes_of(unused), vec![Code::B0008]);
assert!(run(unused).is_err(), "an unused waiver stops the run");
}