use sdivi_config::PatternsConfig;
use sdivi_lang_python::PythonAdapter;
use sdivi_lang_typescript::TypeScriptAdapter;
use sdivi_parsing::adapter::LanguageAdapter;
use sdivi_parsing::feature_record::FeatureRecord;
use sdivi_patterns::build_catalog;
use sdivi_patterns::queries::category_for_node_kind;
use std::path::{Path, PathBuf};
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates/ parent must exist")
.parent()
.expect("workspace root must exist")
.to_path_buf()
}
fn read_and_parse_ts(rel_path: &str) -> FeatureRecord {
let abs = workspace_root().join(rel_path);
let source = std::fs::read_to_string(&abs)
.unwrap_or_else(|e| panic!("could not read TypeScript fixture {:?}: {e}", abs));
TypeScriptAdapter.parse_file(Path::new(rel_path), source)
}
fn read_and_parse_py(rel_path: &str) -> FeatureRecord {
let abs = workspace_root().join(rel_path);
let source = std::fs::read_to_string(&abs)
.unwrap_or_else(|e| panic!("could not read Python fixture {:?}: {e}", abs));
PythonAdapter.parse_file(Path::new(rel_path), source)
}
fn catalog_config() -> PatternsConfig {
PatternsConfig {
min_pattern_nodes: 1,
..PatternsConfig::default()
}
}
#[test]
fn simple_typescript_fixture_produces_data_access_bucket() {
let records = vec![
read_and_parse_ts("tests/fixtures/simple-typescript/app.ts"),
read_and_parse_ts("tests/fixtures/simple-typescript/utils.ts"),
];
let call_expr_count: usize = records
.iter()
.flat_map(|r| &r.pattern_hints)
.filter(|h| h.node_kind == "call_expression")
.count();
assert!(
call_expr_count >= 1,
"TypeScript fixture must produce at least one call_expression hint; got 0. \
Check that the fixture files contain function calls."
);
let catalog = build_catalog(&records, &catalog_config());
assert!(
catalog.entries.contains_key("data_access"),
"PatternCatalog must include a `data_access` bucket for TypeScript fixtures \
containing call_expression nodes; present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let da = &catalog.entries["data_access"];
let total: u32 = da.values().map(|s| s.count).sum();
assert!(
total >= 1,
"`data_access` bucket must contain at least one pattern instance, got {total}"
);
}
#[test]
fn simple_python_fixture_produces_data_access_bucket() {
let records = vec![
read_and_parse_py("tests/fixtures/simple-python/main.py"),
read_and_parse_py("tests/fixtures/simple-python/utils.py"),
];
let call_count: usize = records
.iter()
.flat_map(|r| &r.pattern_hints)
.filter(|h| h.node_kind == "call")
.count();
assert!(
call_count >= 1,
"Python fixture must produce at least one `call` hint after M29 adds \
\"call\" to PATTERN_KINDS; got 0. Verify the Python adapter includes \"call\"."
);
let catalog = build_catalog(&records, &catalog_config());
assert!(
catalog.entries.contains_key("data_access"),
"PatternCatalog must include a `data_access` bucket for Python fixtures \
containing call nodes; present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let da = &catalog.entries["data_access"];
let total: u32 = da.values().map(|s| s.count).sum();
assert!(
total >= 1,
"`data_access` bucket must contain at least one pattern instance, got {total}"
);
}
#[test]
fn call_expression_maps_to_data_access_for_go() {
assert_eq!(
category_for_node_kind("call_expression", "go"),
Some("data_access"),
"call_expression must map to data_access for Go (same as TypeScript/JavaScript)"
);
}