@@ -15,6 +15,16 @@ use rustc_span::sym;
15
15
16
16
use super :: READ_LINE_WITHOUT_TRIM ;
17
17
18
+ fn expr_is_string_literal_without_trailing_newline ( expr : & Expr < ' _ > ) -> bool {
19
+ if let ExprKind :: Lit ( lit) = expr. kind
20
+ && let LitKind :: Str ( sym, _) = lit. node
21
+ {
22
+ !sym. as_str ( ) . ends_with ( '\n' )
23
+ } else {
24
+ false
25
+ }
26
+ }
27
+
18
28
/// Will a `.parse::<ty>()` call fail if the input has a trailing newline?
19
29
fn parse_fails_on_trailing_newline ( ty : Ty < ' _ > ) -> bool {
20
30
// only allow a very limited set of types for now, for which we 100% know parsing will fail
@@ -28,69 +38,75 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
28
38
&& let Res :: Local ( local_id) = path. res
29
39
{
30
40
// We've checked that `call` is a call to `Stdin::read_line()` with the right receiver,
31
- // now let's check if the first use of the string passed to `::read_line()` is
32
- // parsed into a type that will always fail if it has a trailing newline.
41
+ // now let's check if the first use of the string passed to `::read_line()`
42
+ // is used for operations that will always fail (e.g. parsing "6\n" into a number)
33
43
for_each_local_use_after_expr ( cx, local_id, call. hir_id , |expr| {
34
44
if let Some ( parent) = get_parent_expr ( cx, expr) {
35
- if let ExprKind :: MethodCall ( segment, .. , span) = parent. kind
36
- && segment. ident . name == sym ! ( parse)
37
- && let parse_result_ty = cx. typeck_results ( ) . expr_ty ( parent)
38
- && is_type_diagnostic_item ( cx, parse_result_ty, sym:: Result )
39
- && let ty:: Adt ( _, substs) = parse_result_ty. kind ( )
40
- && let Some ( ok_ty) = substs[ 0 ] . as_type ( )
41
- && parse_fails_on_trailing_newline ( ok_ty)
42
- {
43
- let local_snippet : std :: borrow :: Cow < ' _ , str > = snippet ( cx , expr . span , "<expr>" ) ;
44
- span_lint_and_then (
45
- cx ,
46
- READ_LINE_WITHOUT_TRIM ,
47
- span ,
48
- "calling `.parse()` without trimming the trailing newline character ",
49
- |diag| {
50
- diag . span_note (
51
- call . span ,
52
- "call to `.read_line()` here, \
53
- which leaves a trailing newline character in the buffer, \
54
- which in turn will cause `.parse()` to fail" ,
55
- ) ;
56
-
57
- diag . span_suggestion (
58
- expr . span ,
59
- "try ",
60
- format ! ( "{local_snippet}.trim_end()" ) ,
61
- Applicability :: MachineApplicable ,
62
- ) ;
63
- } ,
64
- ) ;
65
- } else if let ExprKind :: Binary ( binop, _ , right) = parent. kind
45
+ let data = if let ExprKind :: MethodCall ( segment, recv , args , span) = parent. kind {
46
+ if segment. ident . name == sym ! ( parse)
47
+ && let parse_result_ty = cx. typeck_results ( ) . expr_ty ( parent)
48
+ && is_type_diagnostic_item ( cx, parse_result_ty, sym:: Result )
49
+ && let ty:: Adt ( _, substs) = parse_result_ty. kind ( )
50
+ && let Some ( ok_ty) = substs[ 0 ] . as_type ( )
51
+ && parse_fails_on_trailing_newline ( ok_ty)
52
+ {
53
+ // Called `s.parse::<T>()` where `T` is a type we know for certain will fail
54
+ // if the input has a trailing newline
55
+ Some ( (
56
+ span ,
57
+ "calling `.parse()` on a string without trimming the trailing newline character" ,
58
+ "checking ",
59
+ ) )
60
+ } else if segment . ident . name == sym ! ( ends_with )
61
+ && recv . span == expr . span
62
+ && let [ arg ] = args
63
+ && expr_is_string_literal_without_trailing_newline ( arg )
64
+ {
65
+ // Called `s.ends_with(<some string literal>)` where the argument is a string literal that does
66
+ // not end with a newline, thus always evaluating to false
67
+ Some ( (
68
+ parent . span ,
69
+ "checking the end of a string without trimming the trailing newline character ",
70
+ "parsing" ,
71
+ ) )
72
+ } else {
73
+ None
74
+ }
75
+ } else if let ExprKind :: Binary ( binop, left , right) = parent. kind
66
76
&& let BinOpKind :: Eq = binop. node
67
- && let ExprKind :: Lit ( lit) = right. kind
68
- && let LitKind :: Str ( sym, _) = lit. node
69
- && !sym. as_str ( ) . ends_with ( '\n' )
77
+ && ( expr_is_string_literal_without_trailing_newline ( left)
78
+ || expr_is_string_literal_without_trailing_newline ( right) )
70
79
{
71
- span_lint_and_then (
72
- cx,
73
- READ_LINE_WITHOUT_TRIM ,
80
+ // `s == <some string literal>` where the string literal does not end with a newline
81
+ Some ( (
74
82
parent. span ,
75
83
"comparing a string literal without trimming the trailing newline character" ,
76
- |diag| {
77
- let local_snippet = snippet ( cx, expr. span , "<expr>" ) ;
84
+ "comparison" ,
85
+ ) )
86
+ } else {
87
+ None
88
+ } ;
89
+
90
+ if let Some ( ( primary_span, lint_message, operation) ) = data {
91
+ span_lint_and_then ( cx, READ_LINE_WITHOUT_TRIM , primary_span, lint_message, |diag| {
92
+ let local_snippet = snippet ( cx, expr. span , "<expr>" ) ;
78
93
79
- diag. span_note (
80
- call. span ,
94
+ diag. span_note (
95
+ call. span ,
96
+ format ! (
81
97
"call to `.read_line()` here, \
82
98
which leaves a trailing newline character in the buffer, \
83
- which in turn will cause the comparison to always fail",
84
- ) ;
99
+ which in turn will cause the {operation} to always fail"
100
+ ) ,
101
+ ) ;
85
102
86
- diag. span_suggestion (
87
- expr. span ,
88
- "try" ,
89
- format ! ( "{local_snippet}.trim_end()" ) ,
90
- Applicability :: MachineApplicable ,
91
- ) ;
92
- } ,
93
- ) ;
103
+ diag. span_suggestion (
104
+ expr. span ,
105
+ "try" ,
106
+ format ! ( "{local_snippet}.trim_end()" ) ,
107
+ Applicability :: MachineApplicable ,
108
+ ) ;
109
+ } ) ;
94
110
}
95
111
}
96
112
0 commit comments