#[path = "inspection/documentation.rs"] mod documentation; use super::Directory; use dever::compiler::project::Project; use std::process::Command; #[path = "inspection/dispatch.rs"] mod dispatch; #[path = "inspection/flows.rs"] mod flows; #[path = "inspection/links.rs"] mod links; #[test] fn inspection_preserves_multiple_callers_sites_and_utf16_columns() { let directory = Directory::new(); let path = directory.0.join("main.dever"); let source = "label(name: Text) (text: Text) {\n text = name\n}\n\nfirst(name: Text) (text: Text) {\n text = \"😀\" + label(name) + label(\"另\")\n}\n\nsecond(name: Text) (text: Text) {\n text = label(name)\n}\n"; std::fs::write(&path, source).unwrap(); let project = Project::load(&path).unwrap(); let mut index = project.inspect().unwrap(); index.select("main.label", 1).unwrap(); let label = index .symbols .iter() .find(|symbol| symbol.name == "main.label") .unwrap(); let calls: Vec<_> = index .calls .iter() .filter(|call| call.callee == label.id) .collect(); assert_eq!(calls.len(), 3); assert_eq!( calls .iter() .map(|call| call.caller) .collect::>() .len(), 2 ); let line = source.lines().nth(5).unwrap(); let first = calls.iter().find(|call| call.site.start.line == 5).unwrap(); assert_eq!( first.site.start.character, line[..line.find("label").unwrap()].encode_utf16().count() ); assert_eq!( first.site.end.character - first.site.start.character, "label".len() ); assert_eq!(index.files.len(), 1); assert!(index.select("does.not.exist", 1).is_err()); } #[test] fn inspection_retains_clauses_and_distinguishes_callback_and_task_dispatch() { let directory = Directory::new(); let path = directory.0.join("main.dever"); std::fs::write(&path, "label(value: true) (text: Text) {\n text = \"yes\"\n}\nlabel(value: other) (text: Text) {\n text = \"no\"\n}\nmain() (texts: List, text: Text) {\n texts = each(label, [true, false])\n task = run(label(true))\n text = wait(task)\n}\n").unwrap(); let index = Project::load(&path).unwrap().inspect().unwrap(); let label = index .symbols .iter() .find(|symbol| symbol.name == "main.label") .unwrap(); assert_eq!(label.declarations.len(), 2); let calls: Vec<_> = index .calls .iter() .filter(|call| call.callee == label.id) .collect(); assert_eq!(calls.len(), 2); assert!(calls.iter().any(|call| call.kind == "callback")); assert!(calls.iter().any(|call| call.kind == "run")); for call in calls { assert_eq!(call.site.end.character - call.site.start.character, 5); } } #[test] fn inspection_cli_resolves_real_command_bindings_and_reports_failures() { let directory = Directory::new(); let root = directory.0.join("app"); dever::compiler::project::create(&root).unwrap(); let output = Command::new(env!("CARGO_BIN_EXE_dever")) .args([ "inspect", root.to_str().unwrap(), "--symbol", "hello.greeting.greet", "--json", ]) .output() .unwrap(); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); let index: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); assert_eq!(index["calls"].as_array().unwrap().len(), 1); assert_eq!(index["calls"][0]["kind"], "command"); let failed = Command::new(env!("CARGO_BIN_EXE_dever")) .args(["inspect", root.to_str().unwrap(), "--symbol", "missing"]) .output() .unwrap(); assert!(!failed.status.success()); assert!(failed.stdout.is_empty()); assert!(String::from_utf8_lossy(&failed.stderr).contains("unknown symbol")); } #[test] fn inspection_does_not_change_generated_rust_or_accept_invalid_source() { let directory = Directory::new(); let path = directory.0.join("main.dever"); std::fs::write(&path, "main() (value: Int) {\n value = 42\n}\n").unwrap(); let project = Project::load(&path).unwrap(); let emit = || { let program = project.check().unwrap(); dever::compiler::emit::generate( &program, &project.sources, &std::collections::BTreeSet::from([0]), ) }; let before = emit(); project.inspect().unwrap(); assert_eq!(emit(), before); std::fs::write(&path, "main() (value: Int) {\n value = unknown()\n}\n").unwrap(); assert!(Project::load(&path).unwrap().inspect().is_err()); } #[test] fn inspection_keeps_job_dispatch_and_static_handler_specializations() { let fixtures = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../test/rust/fixtures"); let jobs = Project::load(&fixtures.join("job")) .unwrap() .inspect() .unwrap(); let publish = jobs .symbols .iter() .find(|symbol| symbol.name == "demo.queue.job.publish") .unwrap(); assert!( jobs.calls .iter() .any(|call| call.callee == publish.id && call.kind == "enqueue") ); assert!( jobs.calls .iter() .any(|call| call.caller == publish.id && call.kind == "call") ); let handlers = Project::load(&fixtures.join("handler.dever")) .unwrap() .inspect() .unwrap(); let apply = handlers .symbols .iter() .filter(|symbol| symbol.name == "main.apply") .collect::>(); assert_eq!(apply.len(), 1); let targets: std::collections::BTreeSet<_> = handlers .calls .iter() .filter(|call| call.caller == apply[0].id) .map(|call| handlers.symbols[call.callee].name.as_str()) .collect(); assert!(targets.contains("main.increment")); assert!(targets.contains("main.decrement")); }