new lint: format_collect
This commit is contained in:
parent
568ccf3fc8
commit
c3881569af
@ -4865,6 +4865,7 @@ Released 2018-09-13
|
|||||||
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
|
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
|
||||||
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
|
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
|
||||||
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
|
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
|
||||||
|
[`format_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_collect
|
||||||
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
|
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
|
||||||
[`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string
|
[`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string
|
||||||
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
|
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
|
||||||
|
@ -338,6 +338,7 @@
|
|||||||
crate::methods::FILTER_NEXT_INFO,
|
crate::methods::FILTER_NEXT_INFO,
|
||||||
crate::methods::FLAT_MAP_IDENTITY_INFO,
|
crate::methods::FLAT_MAP_IDENTITY_INFO,
|
||||||
crate::methods::FLAT_MAP_OPTION_INFO,
|
crate::methods::FLAT_MAP_OPTION_INFO,
|
||||||
|
crate::methods::FORMAT_COLLECT_INFO,
|
||||||
crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO,
|
crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO,
|
||||||
crate::methods::GET_FIRST_INFO,
|
crate::methods::GET_FIRST_INFO,
|
||||||
crate::methods::GET_LAST_WITH_LEN_INFO,
|
crate::methods::GET_LAST_WITH_LEN_INFO,
|
||||||
|
33
clippy_lints/src/methods/format_collect.rs
Normal file
33
clippy_lints/src/methods/format_collect.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use super::FORMAT_COLLECT;
|
||||||
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
|
use clippy_utils::macros::is_format_macro;
|
||||||
|
use clippy_utils::macros::root_macro_call_first_node;
|
||||||
|
use clippy_utils::ty::is_type_lang_item;
|
||||||
|
use rustc_hir::Expr;
|
||||||
|
use rustc_hir::ExprKind;
|
||||||
|
use rustc_hir::LangItem;
|
||||||
|
use rustc_lint::LateContext;
|
||||||
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
fn tail_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||||
|
match expr.kind {
|
||||||
|
ExprKind::Block(block, _) if !expr.span.from_expansion() => block.expr,
|
||||||
|
_ => Some(expr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
|
||||||
|
if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
|
||||||
|
&& let ExprKind::Closure(closure) = map_arg.kind
|
||||||
|
&& let body = cx.tcx.hir().body(closure.body)
|
||||||
|
&& let Some(value) = tail_expr(body.value)
|
||||||
|
&& let Some(mac) = root_macro_call_first_node(cx, value)
|
||||||
|
&& is_format_macro(cx, mac.def_id)
|
||||||
|
{
|
||||||
|
span_lint_and_then(cx, FORMAT_COLLECT, expr.span, "use of `format!` to build up a string from an iterator", |diag| {
|
||||||
|
diag.span_help(map_span, "call `fold` instead")
|
||||||
|
.span_help(value.span.source_callsite(), "... and use the `write!` macro here")
|
||||||
|
.note("this can be written more efficiently by appending to a `String` directly");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@
|
|||||||
mod filter_next;
|
mod filter_next;
|
||||||
mod flat_map_identity;
|
mod flat_map_identity;
|
||||||
mod flat_map_option;
|
mod flat_map_option;
|
||||||
|
mod format_collect;
|
||||||
mod from_iter_instead_of_collect;
|
mod from_iter_instead_of_collect;
|
||||||
mod get_first;
|
mod get_first;
|
||||||
mod get_last_with_len;
|
mod get_last_with_len;
|
||||||
@ -3378,6 +3379,36 @@
|
|||||||
"calling `Stdin::read_line`, then trying to parse it without first trimming"
|
"calling `Stdin::read_line`, then trying to parse it without first trimming"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare_clippy_lint! {
|
||||||
|
/// ### What it does
|
||||||
|
/// Checks for usage of `.map(|_| format!(..)).collect::<String>()`.
|
||||||
|
///
|
||||||
|
/// ### Why is this bad?
|
||||||
|
/// This allocates a new string for every element in the iterator.
|
||||||
|
/// This can be done more efficiently by creating the `String` once and appending to it using `Iterator::fold`.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
/// ```rust
|
||||||
|
/// fn hex_encode(bytes: &[u8]) -> String {
|
||||||
|
/// bytes.iter().map(|b| format!("{b:02X}")).collect()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// Use instead:
|
||||||
|
/// ```rust
|
||||||
|
/// use std::fmt::Write;
|
||||||
|
/// fn hex_encode(bytes: &[u8]) -> String {
|
||||||
|
/// bytes.iter().fold(String::new(), |mut output, b| {
|
||||||
|
/// let _ = write!(output, "{b:02X}");
|
||||||
|
/// output
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[clippy::version = "1.72.0"]
|
||||||
|
pub FORMAT_COLLECT,
|
||||||
|
perf,
|
||||||
|
"`format!`ing every element in a collection, then collecting the strings into a new `String`"
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Methods {
|
pub struct Methods {
|
||||||
avoid_breaking_exported_api: bool,
|
avoid_breaking_exported_api: bool,
|
||||||
msrv: Msrv,
|
msrv: Msrv,
|
||||||
@ -3512,6 +3543,7 @@ pub fn new(
|
|||||||
UNNECESSARY_LITERAL_UNWRAP,
|
UNNECESSARY_LITERAL_UNWRAP,
|
||||||
DRAIN_COLLECT,
|
DRAIN_COLLECT,
|
||||||
MANUAL_TRY_FOLD,
|
MANUAL_TRY_FOLD,
|
||||||
|
FORMAT_COLLECT,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// Extracts a method call name, args, and `Span` of the method name.
|
/// Extracts a method call name, args, and `Span` of the method name.
|
||||||
@ -3733,8 +3765,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
|||||||
Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => {
|
Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => {
|
||||||
iter_cloned_collect::check(cx, name, expr, recv2);
|
iter_cloned_collect::check(cx, name, expr, recv2);
|
||||||
},
|
},
|
||||||
Some(("map", m_recv, [m_arg], _, _)) => {
|
Some(("map", m_recv, [m_arg], m_ident_span, _)) => {
|
||||||
map_collect_result_unit::check(cx, expr, m_recv, m_arg);
|
map_collect_result_unit::check(cx, expr, m_recv, m_arg);
|
||||||
|
format_collect::check(cx, expr, m_arg, m_ident_span);
|
||||||
},
|
},
|
||||||
Some(("take", take_self_arg, [take_arg], _, _)) => {
|
Some(("take", take_self_arg, [take_arg], _, _)) => {
|
||||||
if self.msrv.meets(msrvs::STR_REPEAT) {
|
if self.msrv.meets(msrvs::STR_REPEAT) {
|
||||||
|
@ -132,8 +132,7 @@ fn into_std_cmd(self) -> Command {
|
|||||||
let clippy_args: String = self
|
let clippy_args: String = self
|
||||||
.clippy_args
|
.clippy_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| format!("{arg}__CLIPPY_HACKERY__"))
|
.fold(String::new(), |s, arg| s + arg + "__CLIPPY_HACKERY__");
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages.
|
// Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages.
|
||||||
let terminal_width = termize::dimensions().map_or(0, |(w, _)| w);
|
let terminal_width = termize::dimensions().map_or(0, |(w, _)| w);
|
||||||
|
26
tests/ui/format_collect.rs
Normal file
26
tests/ui/format_collect.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#![allow(unused, dead_code)]
|
||||||
|
#![warn(clippy::format_collect)]
|
||||||
|
|
||||||
|
fn hex_encode(bytes: &[u8]) -> String {
|
||||||
|
bytes.iter().map(|b| format!("{b:02X}")).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! fmt {
|
||||||
|
($x:ident) => {
|
||||||
|
format!("{x:02X}", x = $x)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_macro(bytes: &[u8]) -> String {
|
||||||
|
bytes.iter().map(|x| fmt!(x)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_block() -> String {
|
||||||
|
(1..10)
|
||||||
|
.map(|s| {
|
||||||
|
let y = 1;
|
||||||
|
format!("{s} {y}")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
fn main() {}
|
44
tests/ui/format_collect.stderr
Normal file
44
tests/ui/format_collect.stderr
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
error: use of `format!` to build up a string from an iterator
|
||||||
|
--> $DIR/format_collect.rs:5:5
|
||||||
|
|
|
||||||
|
LL | bytes.iter().map(|b| format!("{b:02X}")).collect()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: call `fold` instead
|
||||||
|
--> $DIR/format_collect.rs:5:18
|
||||||
|
|
|
||||||
|
LL | bytes.iter().map(|b| format!("{b:02X}")).collect()
|
||||||
|
| ^^^
|
||||||
|
help: ... and use the `write!` macro here
|
||||||
|
--> $DIR/format_collect.rs:5:26
|
||||||
|
|
|
||||||
|
LL | bytes.iter().map(|b| format!("{b:02X}")).collect()
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
= note: this can be written more efficiently by appending to a `String` directly
|
||||||
|
= note: `-D clippy::format-collect` implied by `-D warnings`
|
||||||
|
|
||||||
|
error: use of `format!` to build up a string from an iterator
|
||||||
|
--> $DIR/format_collect.rs:19:5
|
||||||
|
|
|
||||||
|
LL | / (1..10)
|
||||||
|
LL | | .map(|s| {
|
||||||
|
LL | | let y = 1;
|
||||||
|
LL | | format!("{s} {y}")
|
||||||
|
LL | | })
|
||||||
|
LL | | .collect()
|
||||||
|
| |__________________^
|
||||||
|
|
|
||||||
|
help: call `fold` instead
|
||||||
|
--> $DIR/format_collect.rs:20:10
|
||||||
|
|
|
||||||
|
LL | .map(|s| {
|
||||||
|
| ^^^
|
||||||
|
help: ... and use the `write!` macro here
|
||||||
|
--> $DIR/format_collect.rs:22:13
|
||||||
|
|
|
||||||
|
LL | format!("{s} {y}")
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
= note: this can be written more efficiently by appending to a `String` directly
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
Loading…
Reference in New Issue
Block a user