Skip to content

Commit 8d31a5e

Browse files
committed
teach clippy::author format args
1 parent 9283497 commit 8d31a5e

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

clippy_lints/src/utils/author.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! A group of attributes that can be attached to Rust code in order
22
//! to generate a clippy lint detecting said code automatically.
33
4+
use clippy_utils::macros::{collect_format_args, is_format_macro, root_macro_call_first_node};
45
use clippy_utils::{get_attr, higher};
56
use rustc_ast::ast::{LitFloatType, LitKind};
67
use rustc_ast::LitIntType;
@@ -333,6 +334,24 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
333334

334335
#[allow(clippy::too_many_lines)]
335336
fn expr(&self, expr: &Binding<&hir::Expr<'_>>) {
337+
if let Some(macro_call) = root_macro_call_first_node(self.cx, expr.value)
338+
&& is_format_macro(self.cx, macro_call.def_id)
339+
{
340+
let diag = self.cx.tcx.item_name(macro_call.def_id);
341+
bind!(self, macro_call);
342+
chain!(self, "let Some({macro_call}) = macros::root_macro_call_first_node(cx, {expr})");
343+
chain!(self, "cx.tcx.is_diagnostic_item(sym::{diag}_macro, {macro_call}.def_id)");
344+
let exprs: Vec<_> = collect_format_args(expr.value).collect();
345+
let args = exprs.as_slice();
346+
bind!(self, args);
347+
chain!(self, "let {args} = macros::collect_format_args({expr}).collect()");
348+
self.slice(args, |e| {
349+
let expr = Binding { name: e.name.clone(), value: *e.value };
350+
self.expr(&expr);
351+
});
352+
return;
353+
}
354+
336355
if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) {
337356
bind!(self, condition, body);
338357
chain!(

clippy_utils/src/macros.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#![allow(clippy::similar_names)] // `expr` and `expn`
22

3-
use crate::visitors::{for_each_expr, Descend};
3+
use crate::visitors::{for_each_expr, Descend, Visitable};
44

55
use arrayvec::ArrayVec;
66
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
77
use rustc_data_structures::fx::FxHashMap;
8-
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
8+
use rustc_hir::{self as hir, Expr, ExprKind, HirId, LangItem, Node, QPath};
99
use rustc_lint::LateContext;
1010
use rustc_span::def_id::DefId;
1111
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
@@ -425,6 +425,39 @@ pub fn find_format_arg_expr<'hir, 'ast>(
425425
.ok_or(&target.expr)
426426
}
427427

428+
/// Given a start node, finds and extracts all format arguments from first occurance.
429+
///
430+
/// ```ignore
431+
/// // vvvvv any format-like macro
432+
/// println!("Hello, {}!", "ferris")
433+
/// // ^^^^^^^^ returns these expressions
434+
/// ```
435+
pub fn collect_format_args<'hir>(node: impl Visitable<'hir>) -> impl Iterator<Item = &'hir Expr<'hir>> {
436+
let args = for_each_expr(node, |expr| {
437+
if let ExprKind::Call(
438+
Expr {
439+
kind:
440+
ExprKind::Path(QPath::TypeRelative(
441+
rustc_hir::Ty {
442+
kind: rustc_hir::TyKind::Path(QPath::LangItem(LangItem::FormatArgument, ..)),
443+
..
444+
},
445+
..,
446+
)),
447+
..
448+
},
449+
args,
450+
) = expr.kind
451+
{
452+
ControlFlow::Break(args)
453+
} else {
454+
ControlFlow::Continue(Descend::Yes)
455+
}
456+
})
457+
.unwrap_or_default();
458+
args.iter()
459+
}
460+
428461
/// Span of the `:` and format specifiers
429462
///
430463
/// ```ignore

0 commit comments

Comments
 (0)