code-moniker-core 0.7.1

Core symbol-graph types and per-language extractors for code-moniker.
Documentation
use std::path::Path;

use code_moniker_core::core::code_graph::{CodeGraph, DefRecord, Position, RefRecord};
use code_moniker_core::core::moniker::{Moniker, MonikerBuilder};
use code_moniker_core::core::uri::{UriConfig, to_uri};
use code_moniker_core::lang::{self, LangExtractor};
use serde_json::{Map, Value, json};

fn anchor() -> Moniker {
	MonikerBuilder::new().project(b"app").build()
}

fn snap<L: LangExtractor>(path: &Path) -> String {
	let src = std::fs::read_to_string(path).expect("read fixture");
	let uri = path
		.file_name()
		.expect("fixture has file name")
		.to_string_lossy()
		.into_owned();
	let a = anchor();
	let g = L::extract(&uri, &src, &a, false, &L::Presets::default());
	serde_json::to_string_pretty(&dump(&g)).expect("pretty json")
}

fn dump(g: &CodeGraph) -> Value {
	let cfg = UriConfig::default();
	let defs: Vec<&DefRecord> = g.defs().collect();
	let render = |m: &Moniker| -> String { to_uri(m, &cfg) };

	let defs_json: Vec<Value> = defs
		.iter()
		.skip(1)
		.map(|d| {
			let parent = d
				.parent
				.and_then(|i| defs.get(i).copied())
				.map(|p| &p.moniker);
			def_entry(d, parent, &render)
		})
		.collect();

	let refs_json: Vec<Value> = g
		.refs()
		.map(|r| {
			let from = defs.get(r.source).map(|d| &d.moniker);
			ref_entry(r, from, &render)
		})
		.collect();

	json!({
		"root":  render(g.root()),
		"defs":  defs_json,
		"refs":  refs_json,
	})
}

fn def_entry<F: Fn(&Moniker) -> String>(
	d: &DefRecord,
	parent: Option<&Moniker>,
	render: &F,
) -> Value {
	let mut m = Map::with_capacity(8);
	m.insert("kind".into(), s(&d.kind));
	m.insert("moniker".into(), Value::String(render(&d.moniker)));
	if let Some(p) = parent {
		m.insert("parent".into(), Value::String(render(p)));
	}
	m.insert("position".into(), position_value(d.position));
	m.insert("visibility".into(), s(&d.visibility));
	m.insert("signature".into(), s(&d.signature));
	m.insert("binding".into(), s(&d.binding));
	m.insert("origin".into(), s(&d.origin));
	Value::Object(m)
}

fn ref_entry<F: Fn(&Moniker) -> String>(
	r: &RefRecord,
	from: Option<&Moniker>,
	render: &F,
) -> Value {
	let mut m = Map::with_capacity(8);
	m.insert("kind".into(), s(&r.kind));
	m.insert(
		"from".into(),
		Value::String(from.map(render).unwrap_or_else(|| "<unknown>".into())),
	);
	m.insert("to".into(), Value::String(render(&r.target)));
	m.insert("position".into(), position_value(r.position));
	m.insert("confidence".into(), s(&r.confidence));
	m.insert("binding".into(), s(&r.binding));
	m.insert("alias".into(), s(&r.alias));
	m.insert("receiver_hint".into(), s(&r.receiver_hint));
	Value::Object(m)
}

fn position_value(p: Option<Position>) -> Value {
	match p {
		Some((a, b)) => json!([a, b]),
		None => Value::Null,
	}
}

fn s(bytes: &[u8]) -> Value {
	Value::String(String::from_utf8_lossy(bytes).into_owned())
}

macro_rules! snapshot_lang {
	($name:ident, $glob:literal, $lang:ty) => {
		#[test]
		fn $name() {
			insta::glob!($glob, |p| {
				let body = snap::<$lang>(p);
				insta::assert_snapshot!(body);
			});
		}
	};
}

snapshot_lang!(
	snapshot_ts,
	"fixtures/extractors/ts/*.{ts,tsx,jsx}",
	lang::ts::Lang
);
snapshot_lang!(snapshot_rs, "fixtures/extractors/rs/*.rs", lang::rs::Lang);
snapshot_lang!(
	snapshot_python,
	"fixtures/extractors/python/*.py",
	lang::python::Lang
);
snapshot_lang!(snapshot_go, "fixtures/extractors/go/*.go", lang::go::Lang);
snapshot_lang!(snapshot_c, "fixtures/extractors/c/*.c", lang::c::Lang);
snapshot_lang!(
	snapshot_c_headers,
	"fixtures/extractors/c/*.h",
	lang::c::Lang
);
snapshot_lang!(
	snapshot_java,
	"fixtures/extractors/java/*.java",
	lang::java::Lang
);
snapshot_lang!(snapshot_cs, "fixtures/extractors/cs/*.cs", lang::cs::Lang);
snapshot_lang!(
	snapshot_sql,
	"fixtures/extractors/sql/*.sql",
	lang::sql::Lang
);