@@ -5,9 +5,10 @@ use clippy_utils::source::snippet;
55use clippy_utils:: ty:: is_type_diagnostic_item;
66use clippy_utils:: visitors:: for_each_local_use_after_expr;
77use clippy_utils:: { get_parent_expr, match_def_path} ;
8+ use rustc_ast:: LitKind ;
89use rustc_errors:: Applicability ;
910use rustc_hir:: def:: Res ;
10- use rustc_hir:: { Expr , ExprKind , QPath } ;
11+ use rustc_hir:: { BinOpKind , Expr , ExprKind , QPath } ;
1112use rustc_lint:: LateContext ;
1213use rustc_middle:: ty:: { self , Ty } ;
1314use rustc_span:: sym;
@@ -30,37 +31,67 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
3031 // now let's check if the first use of the string passed to `::read_line()` is
3132 // parsed into a type that will always fail if it has a trailing newline.
3233 for_each_local_use_after_expr ( cx, local_id, call. hir_id , |expr| {
33- if let Some ( parent) = get_parent_expr ( cx, expr)
34- && let ExprKind :: MethodCall ( segment, .., span) = parent. kind
35- && segment. ident . name == sym ! ( parse)
36- && let parse_result_ty = cx. typeck_results ( ) . expr_ty ( parent)
37- && is_type_diagnostic_item ( cx, parse_result_ty, sym:: Result )
38- && let ty:: Adt ( _, args ) = parse_result_ty. kind ( )
39- && let Some ( ok_ty) = args [ 0 ] . as_type ( )
40- && parse_fails_on_trailing_newline ( ok_ty)
41- {
42- let local_snippet = snippet ( cx, expr. span , "<expr>" ) ;
43- span_lint_and_then (
44- cx,
45- READ_LINE_WITHOUT_TRIM ,
46- span,
47- "calling `.parse()` without trimming the trailing newline character" ,
48- |diag| {
49- diag. span_note (
50- call. span ,
51- "call to `.read_line()` here, \
52- which leaves a trailing newline character in the buffer, \
53- which in turn will cause `.parse()` to fail",
54- ) ;
34+ 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+ ) ;
5556
56- diag. span_suggestion (
57- expr. span ,
58- "try" ,
59- format ! ( "{local_snippet}.trim_end()" ) ,
60- Applicability :: MachineApplicable ,
61- ) ;
62- } ,
63- ) ;
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
66+ && 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' )
70+ {
71+ span_lint_and_then (
72+ cx,
73+ READ_LINE_WITHOUT_TRIM ,
74+ parent. span ,
75+ "comparing a string literal without trimming the trailing newline character" ,
76+ |diag| {
77+ let local_snippet = snippet ( cx, expr. span , "<expr>" ) ;
78+
79+ diag. span_note (
80+ call. span ,
81+ "call to `.read_line()` here, \
82+ which leaves a trailing newline character in the buffer, \
83+ which in turn will cause the comparison to always fail",
84+ ) ;
85+
86+ diag. span_suggestion (
87+ expr. span ,
88+ "try" ,
89+ format ! ( "{local_snippet}.trim_end()" ) ,
90+ Applicability :: MachineApplicable ,
91+ ) ;
92+ } ,
93+ ) ;
94+ }
6495 }
6596
6697 // only consider the first use to prevent this scenario:
0 commit comments