extern crate rustc_hir;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
dylint_linting::declare_late_lint! {
#[doc = include_str!("../../docs/de02_api_layer/de0202_dtos_not_referenced_outside_api/README.md")]
pub DE0202_DTOS_NOT_REFERENCED_OUTSIDE_API,
Deny,
"DTO types should not be imported outside of api layer (DE0202)"
}
impl<'tcx> LateLintPass<'tcx> for De0202DtosNotReferencedOutsideApi {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
let ItemKind::Use(path, _) = &item.kind else {
return;
};
let sm = cx.sess().source_map();
let span = cx.tcx.def_span(item.owner_id.def_id);
let in_forbidden = crate::lint_utils::is_in_contract_path(sm, span)
|| crate::lint_utils::is_in_domain_path(sm, span)
|| crate::lint_utils::is_in_infra_path(sm, span);
if !in_forbidden {
return;
}
let path_str = path_to_string(path);
if !path_str.contains("api::rest::dto") && !path_str.contains("api::rest") {
return;
}
let segments: Vec<&str> = path_str.split("::").collect();
if let Some(last) = segments.last() {
let is_dto = last.ends_with("Dto")
|| last.ends_with("Request")
|| last.ends_with("Response")
|| last.ends_with("Query");
if is_dto {
let module_type = if crate::lint_utils::is_in_contract_path(sm, span) {
"contract"
} else if crate::lint_utils::is_in_domain_path(sm, span) {
"domain"
} else {
"infra"
};
cx.span_lint(DE0202_DTOS_NOT_REFERENCED_OUTSIDE_API, item.span, |diag| {
diag.primary_message(format!(
"{} module imports DTO type `{}` from api layer (DE0202)",
module_type, last
));
diag.help("DTOs are API layer details; use domain types instead");
});
}
}
}
}
fn path_to_string(path: &rustc_hir::UsePath<'_>) -> String {
path.segments
.iter()
.map(|seg| seg.ident.name.as_str())
.collect::<Vec<_>>()
.join("::")
}