use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use bynk_syntax::error::CompileError;
use crate::discovery::ParsedFile;
use crate::paths::unit_path_matches;
use crate::roots::UnitKind;
pub fn check_directory_name_consistency(
parsed: &[ParsedFile],
) -> Result<(), Vec<(PathBuf, CompileError)>> {
let mut errors: Vec<(PathBuf, CompileError)> = Vec::new();
let mut by_name: HashMap<String, Vec<usize>> = HashMap::new();
for (i, pf) in parsed.iter().enumerate() {
if matches!(pf.kind, UnitKind::Test | UnitKind::Integration) {
continue;
}
by_name.entry(pf.unit.name().joined()).or_default().push(i);
}
for indices in by_name.values() {
if indices.len() < 2 {
continue;
}
let first_dir = parsed[indices[0]]
.source_path
.parent()
.unwrap_or(Path::new(""))
.to_path_buf();
for &idx in indices.iter().skip(1) {
let dir = parsed[idx]
.source_path
.parent()
.unwrap_or(Path::new(""))
.to_path_buf();
if dir != first_dir {
errors.push((
parsed[idx].identity_path.clone(),
CompileError::new(
"bynk.project.inconsistent_commons_name",
parsed[idx].unit.span(),
format!(
"files declaring `{}` are spread across different directories: `{}` vs `{}`",
parsed[idx].unit.name().joined(),
first_dir.display(),
dir.display(),
),
)
.with_note(format!(
"the first file declaring `{}` is `{}`",
parsed[idx].unit.name().joined(),
parsed[indices[0]].identity_path.to_string_lossy().replace('\\', "/"),
))
.with_note(
"all files of a multi-file commons or context must live in the same directory",
),
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn check_directory_kind_consistency(_parsed: &[ParsedFile]) -> Result<(), Vec<CompileError>> {
Ok(())
}
pub fn check_path_name_alignment(
parsed: &[ParsedFile],
) -> Result<(), Vec<(PathBuf, CompileError)>> {
let mut errors: Vec<(PathBuf, CompileError)> = Vec::new();
for pf in parsed {
if matches!(pf.kind, UnitKind::Test | UnitKind::Integration) {
continue;
}
let name = pf.unit.name().joined();
let name_parts: Vec<&str> = name.split('.').collect();
let rel = &pf.source_path;
if !unit_path_matches(rel, &name) {
errors.push((
pf.identity_path.clone(),
CompileError::new(
"bynk.project.inconsistent_commons_name",
pf.unit.span(),
format!(
"file `{}` declares `{name}`, but its path doesn't match — expected either `{}.bynk` (single-file) or `{}/...bynk` (multi-file)",
rel.display(),
name_parts.join("/"),
name_parts.join("/"),
),
)
.with_note(
"the source-tree layout determines a unit's identity: each commons or context's qualified name must match its path",
),
));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn check_group_kind_consistency(
parsed: &[ParsedFile],
groups: &BTreeMap<String, Vec<usize>>,
) -> Result<(), Vec<(PathBuf, CompileError)>> {
let mut errors: Vec<(PathBuf, CompileError)> = Vec::new();
for (name, indices) in groups {
if indices.len() < 2 {
continue;
}
let first_kind = parsed[indices[0]].kind;
for &idx in indices.iter().skip(1) {
if parsed[idx].kind != first_kind {
errors.push((
parsed[idx].identity_path.clone(),
CompileError::new(
"bynk.project.kind_conflict",
parsed[idx].unit.span(),
format!(
"name `{name}` is declared as both a {} and a {}",
first_kind.display(),
parsed[idx].kind.display(),
),
)
.with_note(format!(
"first declared as a {} in `{}`",
first_kind.display(),
parsed[indices[0]]
.identity_path
.to_string_lossy()
.replace('\\', "/"),
)),
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}