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