]>
Commit | Line | Data |
---|---|---|
f2b60f7d FG |
1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
2 | use rustc_ast::LitKind; | |
3 | use rustc_errors::Applicability::MachineApplicable; | |
4 | use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; | |
5 | use rustc_lint::{LateContext, LateLintPass}; | |
6 | use rustc_middle::ty; | |
7 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
8 | use rustc_span::{sym, symbol, Span}; | |
9 | ||
10 | declare_clippy_lint! { | |
11 | /// ### What it does | |
12 | /// | |
13 | /// Checks for usage of `""` to create a `String`, such as `"".to_string()`, `"".to_owned()`, | |
14 | /// `String::from("")` and others. | |
15 | /// | |
16 | /// ### Why is this bad? | |
17 | /// | |
18 | /// Different ways of creating an empty string makes your code less standardized, which can | |
19 | /// be confusing. | |
20 | /// | |
21 | /// ### Example | |
22 | /// ```rust | |
23 | /// let a = "".to_string(); | |
24 | /// let b: String = "".into(); | |
25 | /// ``` | |
26 | /// Use instead: | |
27 | /// ```rust | |
28 | /// let a = String::new(); | |
29 | /// let b = String::new(); | |
30 | /// ``` | |
31 | #[clippy::version = "1.65.0"] | |
32 | pub MANUAL_STRING_NEW, | |
33 | pedantic, | |
34 | "empty String is being created manually" | |
35 | } | |
36 | declare_lint_pass!(ManualStringNew => [MANUAL_STRING_NEW]); | |
37 | ||
38 | impl LateLintPass<'_> for ManualStringNew { | |
39 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { | |
40 | if expr.span.from_expansion() { | |
41 | return; | |
42 | } | |
43 | ||
44 | let ty = cx.typeck_results().expr_ty(expr); | |
45 | match ty.kind() { | |
46 | ty::Adt(adt_def, _) if adt_def.is_struct() => { | |
487cf647 | 47 | if cx.tcx.lang_items().string() != Some(adt_def.did()) { |
f2b60f7d FG |
48 | return; |
49 | } | |
50 | }, | |
51 | _ => return, | |
52 | } | |
53 | ||
54 | match expr.kind { | |
55 | ExprKind::Call(func, args) => { | |
56 | parse_call(cx, expr.span, func, args); | |
57 | }, | |
58 | ExprKind::MethodCall(path_segment, receiver, ..) => { | |
59 | parse_method_call(cx, expr.span, path_segment, receiver); | |
60 | }, | |
61 | _ => (), | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | /// Checks if an expression's kind corresponds to an empty &str. | |
67 | fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool { | |
68 | if let ExprKind::Lit(lit) = expr_kind && | |
69 | let LitKind::Str(value, _) = lit.node && | |
70 | value == symbol::kw::Empty | |
71 | { | |
72 | return true; | |
73 | } | |
74 | ||
75 | false | |
76 | } | |
77 | ||
78 | fn warn_then_suggest(cx: &LateContext<'_>, span: Span) { | |
79 | span_lint_and_sugg( | |
80 | cx, | |
81 | MANUAL_STRING_NEW, | |
82 | span, | |
83 | "empty String is being created manually", | |
84 | "consider using", | |
85 | "String::new()".into(), | |
86 | MachineApplicable, | |
87 | ); | |
88 | } | |
89 | ||
90 | /// Tries to parse an expression as a method call, emitting the warning if necessary. | |
91 | fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, receiver: &Expr<'_>) { | |
92 | let ident = path_segment.ident.as_str(); | |
93 | let method_arg_kind = &receiver.kind; | |
94 | if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) { | |
95 | warn_then_suggest(cx, span); | |
96 | } else if let ExprKind::Call(func, args) = method_arg_kind { | |
97 | // If our first argument is a function call itself, it could be an `unwrap`-like function. | |
98 | // E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc. | |
99 | parse_call(cx, span, func, args); | |
100 | } | |
101 | } | |
102 | ||
103 | /// Tries to parse an expression as a function call, emitting the warning if necessary. | |
104 | fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_>]) { | |
105 | if args.len() != 1 { | |
106 | return; | |
107 | } | |
108 | ||
109 | let arg_kind = &args[0].kind; | |
110 | if let ExprKind::Path(qpath) = &func.kind { | |
111 | if let QPath::TypeRelative(_, _) = qpath { | |
112 | // String::from(...) or String::try_from(...) | |
113 | if let QPath::TypeRelative(ty, path_seg) = qpath && | |
114 | [sym::from, sym::try_from].contains(&path_seg.ident.name) && | |
115 | let TyKind::Path(qpath) = &ty.kind && | |
116 | let QPath::Resolved(_, path) = qpath && | |
117 | let [path_seg] = path.segments && | |
118 | path_seg.ident.name == sym::String && | |
119 | is_expr_kind_empty_str(arg_kind) | |
120 | { | |
121 | warn_then_suggest(cx, span); | |
122 | } | |
123 | } else if let QPath::Resolved(_, path) = qpath { | |
124 | // From::from(...) or TryFrom::try_from(...) | |
125 | if let [path_seg1, path_seg2] = path.segments && | |
126 | is_expr_kind_empty_str(arg_kind) && ( | |
127 | (path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) || | |
128 | (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from) | |
129 | ) | |
130 | { | |
131 | warn_then_suggest(cx, span); | |
132 | } | |
133 | } | |
134 | } | |
135 | } |