extern crate rustc_ast;
extern crate rustc_hir;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
dylint_linting::declare_late_lint! {
pub DE0802_USE_ODATA_EXT,
Deny,
"use OperationBuilderODataExt methods instead of .query_param() for OData parameters (DE0802)"
}
const ODATA_PARAMS: &[&str] = &["$filter", "$orderby", "$select", "$top", "$skip", "$count"];
fn get_recommended_method(param: &str) -> &'static str {
match param {
"$filter" => ".with_odata_filter::<FilterFieldEnum>()",
"$orderby" => ".with_odata_orderby::<FilterFieldEnum>()",
"$select" => ".with_odata_select()",
"$top" | "$skip" | "$count" => ".query_param_typed() with proper OData extractor",
_ => "the appropriate OperationBuilderODataExt method",
}
}
impl<'tcx> LateLintPass<'tcx> for De0802UseOdataExt {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::MethodCall(method_segment, receiver, args, _span) = &expr.kind {
let method_name = method_segment.ident.name.as_str();
if method_name != "query_param" && method_name != "query_param_typed" {
return;
}
if !is_operation_builder_chain(receiver) {
return;
}
if let Some(first_arg) = args.first() {
check_odata_param(cx, first_arg, method_name);
}
}
}
}
fn is_operation_builder_chain(expr: &Expr<'_>) -> bool {
match &expr.kind {
ExprKind::Call(func, _) => {
if let ExprKind::Path(qpath) = &func.kind {
return path_contains_operation_builder(qpath);
}
false
}
ExprKind::MethodCall(_, receiver, _, _) => is_operation_builder_chain(receiver),
ExprKind::Path(qpath) => path_contains_operation_builder(qpath),
_ => false,
}
}
fn path_contains_operation_builder(qpath: &rustc_hir::QPath<'_>) -> bool {
match qpath {
rustc_hir::QPath::Resolved(_, path) => path
.segments
.iter()
.any(|seg| seg.ident.name.as_str() == "OperationBuilder"),
rustc_hir::QPath::TypeRelative(ty, segment) => {
segment.ident.name.as_str() == "OperationBuilder" || type_contains_operation_builder(ty)
}
}
}
fn type_contains_operation_builder(ty: &rustc_hir::Ty<'_>) -> bool {
match &ty.kind {
rustc_hir::TyKind::Path(qpath) => path_contains_operation_builder(qpath),
_ => false,
}
}
fn check_odata_param<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>, method_name: &str) {
if let ExprKind::Lit(lit) = &arg.kind
&& let rustc_ast::ast::LitKind::Str(sym, _) = lit.node
{
let param_name = sym.as_str();
if ODATA_PARAMS.contains(¶m_name) {
let recommended = get_recommended_method(param_name);
cx.span_lint(DE0802_USE_ODATA_EXT, arg.span, |diag| {
diag.primary_message(format!(
"use OperationBuilderODataExt instead of .{}() for OData parameter `{}` (DE0802)",
method_name, param_name
));
diag.help(format!("use {} instead", recommended));
diag.note(
"type-safe OData methods provide compile-time validation and automatic OpenAPI schema generation",
);
});
}
}
}