diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:03:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:03:36 +0000 |
commit | 17d40c6057c88f4c432b0d7bac88e1b84cb7e67f (patch) | |
tree | 3f66c4a5918660bb8a758ab6cda5ff8ee4f6cdcd /src/tools/clippy/clippy_lints/src/manual_string_new.rs | |
parent | Adding upstream version 1.64.0+dfsg1. (diff) | |
download | rustc-upstream/1.65.0+dfsg1.tar.xz rustc-upstream/1.65.0+dfsg1.zip |
Adding upstream version 1.65.0+dfsg1.upstream/1.65.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/manual_string_new.rs')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/manual_string_new.rs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/manual_string_new.rs b/src/tools/clippy/clippy_lints/src/manual_string_new.rs new file mode 100644 index 000000000..6acfb2ae3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_string_new.rs @@ -0,0 +1,135 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::LitKind; +use rustc_errors::Applicability::MachineApplicable; +use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, symbol, Span}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usage of `""` to create a `String`, such as `"".to_string()`, `"".to_owned()`, + /// `String::from("")` and others. + /// + /// ### Why is this bad? + /// + /// Different ways of creating an empty string makes your code less standardized, which can + /// be confusing. + /// + /// ### Example + /// ```rust + /// let a = "".to_string(); + /// let b: String = "".into(); + /// ``` + /// Use instead: + /// ```rust + /// let a = String::new(); + /// let b = String::new(); + /// ``` + #[clippy::version = "1.65.0"] + pub MANUAL_STRING_NEW, + pedantic, + "empty String is being created manually" +} +declare_lint_pass!(ManualStringNew => [MANUAL_STRING_NEW]); + +impl LateLintPass<'_> for ManualStringNew { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + let ty = cx.typeck_results().expr_ty(expr); + match ty.kind() { + ty::Adt(adt_def, _) if adt_def.is_struct() => { + if !cx.tcx.is_diagnostic_item(sym::String, adt_def.did()) { + return; + } + }, + _ => return, + } + + match expr.kind { + ExprKind::Call(func, args) => { + parse_call(cx, expr.span, func, args); + }, + ExprKind::MethodCall(path_segment, receiver, ..) => { + parse_method_call(cx, expr.span, path_segment, receiver); + }, + _ => (), + } + } +} + +/// Checks if an expression's kind corresponds to an empty &str. +fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool { + if let ExprKind::Lit(lit) = expr_kind && + let LitKind::Str(value, _) = lit.node && + value == symbol::kw::Empty + { + return true; + } + + false +} + +fn warn_then_suggest(cx: &LateContext<'_>, span: Span) { + span_lint_and_sugg( + cx, + MANUAL_STRING_NEW, + span, + "empty String is being created manually", + "consider using", + "String::new()".into(), + MachineApplicable, + ); +} + +/// Tries to parse an expression as a method call, emitting the warning if necessary. +fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, receiver: &Expr<'_>) { + let ident = path_segment.ident.as_str(); + let method_arg_kind = &receiver.kind; + if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) { + warn_then_suggest(cx, span); + } else if let ExprKind::Call(func, args) = method_arg_kind { + // If our first argument is a function call itself, it could be an `unwrap`-like function. + // E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc. + parse_call(cx, span, func, args); + } +} + +/// Tries to parse an expression as a function call, emitting the warning if necessary. +fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_>]) { + if args.len() != 1 { + return; + } + + let arg_kind = &args[0].kind; + if let ExprKind::Path(qpath) = &func.kind { + if let QPath::TypeRelative(_, _) = qpath { + // String::from(...) or String::try_from(...) + if let QPath::TypeRelative(ty, path_seg) = qpath && + [sym::from, sym::try_from].contains(&path_seg.ident.name) && + let TyKind::Path(qpath) = &ty.kind && + let QPath::Resolved(_, path) = qpath && + let [path_seg] = path.segments && + path_seg.ident.name == sym::String && + is_expr_kind_empty_str(arg_kind) + { + warn_then_suggest(cx, span); + } + } else if let QPath::Resolved(_, path) = qpath { + // From::from(...) or TryFrom::try_from(...) + if let [path_seg1, path_seg2] = path.segments && + is_expr_kind_empty_str(arg_kind) && ( + (path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) || + (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from) + ) + { + warn_then_suggest(cx, span); + } + } + } +} |