extern crate rustc_ast;
use crate::lint_utils::{
is_in_contract_module_ast, is_in_cyberware_server_path, is_in_modkit_db_path,
use_tree_to_strings,
};
use rustc_ast::{Item, ItemKind, Ty, TyKind};
use rustc_lint::{EarlyLintPass, LintContext};
dylint_linting::declare_early_lint! {
#[doc = include_str!("../../docs/de07_security/de0706_no_direct_sqlx/README.md")]
pub DE0706_NO_DIRECT_SQLX,
Deny,
"direct sqlx usage is prohibited; use Sea-ORM or SecORM instead (DE0706)"
}
const SQLX_PATTERN: &str = "sqlx";
fn is_sqlx_path(path: &str) -> bool {
path == SQLX_PATTERN || path.starts_with("sqlx::")
}
fn find_sqlx_path(tree: &rustc_ast::UseTree) -> Option<String> {
use_tree_to_strings(tree)
.into_iter()
.find(|path| is_sqlx_path(path))
}
fn check_type_for_sqlx(cx: &rustc_lint::EarlyContext<'_>, ty: &Ty) {
match &ty.kind {
TyKind::Path(_, path) => {
let path_str = path
.segments
.iter()
.map(|seg| seg.ident.name.as_str())
.collect::<Vec<_>>()
.join("::");
if is_sqlx_path(&path_str) {
cx.span_lint(DE0706_NO_DIRECT_SQLX, ty.span, |diag| {
diag.primary_message(format!(
"direct sqlx type usage detected: `{path_str}` (DE0706)"
));
diag.help("use Sea-ORM EntityTrait or SecORM abstractions instead");
diag.note("sqlx bypasses security enforcement and architectural patterns");
});
return;
}
for segment in &path.segments {
if let Some(args) = &segment.args
&& let rustc_ast::GenericArgs::AngleBracketed(ref angle_args) = **args
{
for arg in &angle_args.args {
if let rustc_ast::AngleBracketedArg::Arg(rustc_ast::GenericArg::Type(
inner_ty,
)) = arg
{
check_type_for_sqlx(cx, inner_ty);
}
}
}
}
}
TyKind::Ref(_, mut_ty) => {
check_type_for_sqlx(cx, &mut_ty.ty);
}
TyKind::Slice(inner_ty) | TyKind::Array(inner_ty, _) => {
check_type_for_sqlx(cx, inner_ty);
}
TyKind::Ptr(mut_ty) => {
check_type_for_sqlx(cx, &mut_ty.ty);
}
TyKind::Tup(types) => {
for inner_ty in types {
check_type_for_sqlx(cx, inner_ty);
}
}
TyKind::TraitObject(bounds, _) | TyKind::ImplTrait(_, bounds) => {
for bound in bounds {
if let rustc_ast::GenericBound::Trait(trait_ref) = bound {
let path = &trait_ref.trait_ref.path;
let path_str = path
.segments
.iter()
.map(|seg| seg.ident.name.as_str())
.collect::<Vec<_>>()
.join("::");
if is_sqlx_path(&path_str) {
cx.span_lint(DE0706_NO_DIRECT_SQLX, ty.span, |diag| {
diag.primary_message(format!(
"direct sqlx trait usage detected: `{path_str}` (DE0706)"
));
diag.help("use Sea-ORM EntityTrait or SecORM abstractions instead");
diag.note(
"sqlx bypasses security enforcement and architectural patterns",
);
});
return;
}
}
}
}
_ => {}
}
}
fn check_use_for_sqlx(cx: &rustc_lint::EarlyContext<'_>, item: &Item) {
let ItemKind::Use(use_tree) = &item.kind else {
return;
};
if let Some(path_str) = find_sqlx_path(use_tree) {
cx.span_lint(DE0706_NO_DIRECT_SQLX, item.span, |diag| {
diag.primary_message(format!(
"direct sqlx import detected: `{}` (DE0706)",
path_str
));
diag.help("use Sea-ORM EntityTrait or SecORM abstractions instead");
diag.note("sqlx bypasses security enforcement and architectural patterns");
});
}
}
impl EarlyLintPass for De0706NoDirectSqlx {
fn check_item(&mut self, cx: &rustc_lint::EarlyContext<'_>, item: &Item) {
if is_in_modkit_db_path(cx.sess().source_map(), item.span) {
return;
}
if is_in_cyberware_server_path(cx.sess().source_map(), item.span) {
return;
}
if is_in_contract_module_ast(cx, item) {
return;
}
match &item.kind {
ItemKind::Use(_) => {
check_use_for_sqlx(cx, item);
}
ItemKind::ExternCrate(rename, ident) => {
let is_sqlx = match rename {
Some(sym) => sym.as_str() == SQLX_PATTERN,
None => ident.name.as_str() == SQLX_PATTERN,
};
if is_sqlx {
cx.span_lint(DE0706_NO_DIRECT_SQLX, item.span, |diag| {
diag.primary_message("extern crate sqlx is prohibited (DE0706)");
diag.help("use Sea-ORM EntityTrait or SecORM abstractions instead");
diag.note("sqlx bypasses security enforcement and architectural patterns");
});
}
}
ItemKind::Struct(_, _, variant_data) => {
for field in variant_data.fields() {
check_type_for_sqlx(cx, &field.ty);
}
}
ItemKind::Enum(_, _, enum_def) => {
for variant in &enum_def.variants {
for field in variant.data.fields() {
check_type_for_sqlx(cx, &field.ty);
}
}
}
ItemKind::Fn(fn_item) => {
for param in &fn_item.sig.decl.inputs {
check_type_for_sqlx(cx, ¶m.ty);
}
if let rustc_ast::FnRetTy::Ty(ret_ty) = &fn_item.sig.decl.output {
check_type_for_sqlx(cx, ret_ty);
}
}
ItemKind::TyAlias(ty_alias) => {
if let Some(ty) = &ty_alias.ty {
check_type_for_sqlx(cx, ty);
}
}
_ => {}
}
}
}