use std::path::Path;
use crate::lint::finding::Finding;
use crate::lint::source_tree::SourceTree;
pub fn check(tree: &SourceTree, schema_dir: &Path) -> Vec<Finding> {
let mut out = Vec::new();
for (key, loc) in &tree.object_locations {
let rel = loc
.file
.strip_prefix(schema_dir)
.unwrap_or(loc.file.as_path());
if rel.parent().is_none_or(|p| p.as_os_str().is_empty()) {
out.push(
Finding::error(
"feature_grouped_needs_feature_dir",
format!(
"{} `{}` at `{}` is not under a feature subdirectory",
key.kind_name(),
key,
rel.display(),
),
)
.at(loc.clone()),
);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::catalog::Catalog;
use crate::ir::table::Table;
use crate::lint::source_tree::ObjectKey;
use crate::parse::SourceLocation;
use std::collections::HashMap;
use std::path::PathBuf;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(s: &str, n: &str) -> QualifiedName {
QualifiedName::new(id(s), id(n))
}
fn loc(p: &str) -> SourceLocation {
SourceLocation::new(PathBuf::from(p), 1, 1)
}
#[test]
fn passes_when_file_lives_under_feature_dir() {
let mut c = Catalog::empty();
c.tables.push(Table {
qname: qn("app", "users"),
columns: vec![],
constraints: vec![],
partition_by: None,
partition_of: None,
comment: None,
owner: None,
grants: vec![],
rls_enabled: false,
rls_forced: false,
policies: vec![],
storage: crate::ir::reloptions::TableStorageOptions::default(),
access_method: None,
tablespace: None,
});
let mut locs = HashMap::new();
locs.insert(
ObjectKey::Table(qn("app", "users")),
loc("schema/auth/users.sql"),
);
let tree = SourceTree::new(c, locs);
assert!(check(&tree, Path::new("schema")).is_empty());
}
#[test]
fn flags_file_directly_under_schema_dir() {
let mut c = Catalog::empty();
c.tables.push(Table {
qname: qn("app", "users"),
columns: vec![],
constraints: vec![],
partition_by: None,
partition_of: None,
comment: None,
owner: None,
grants: vec![],
rls_enabled: false,
rls_forced: false,
policies: vec![],
storage: crate::ir::reloptions::TableStorageOptions::default(),
access_method: None,
tablespace: None,
});
let mut locs = HashMap::new();
locs.insert(
ObjectKey::Table(qn("app", "users")),
loc("schema/users.sql"),
);
let tree = SourceTree::new(c, locs);
let f = check(&tree, Path::new("schema"));
assert_eq!(f.len(), 1);
assert_eq!(f[0].rule, "feature_grouped_needs_feature_dir");
}
}