]>
Commit | Line | Data |
---|---|---|
ea8adc8c XL |
1 | //! Contains utility functions to generate suggestions. |
2 | #![deny(missing_docs_in_private_items)] | |
3 | // currently ignores lifetimes and generics | |
4 | #![allow(use_self)] | |
5 | ||
6 | use rustc::hir; | |
7 | use rustc::lint::{EarlyContext, LateContext, LintContext}; | |
8 | use rustc_errors; | |
9 | use std::borrow::Cow; | |
10 | use std::fmt::Display; | |
11 | use std; | |
12 | use syntax::codemap::{CharPos, Span}; | |
13 | use syntax::parse::token; | |
14 | use syntax::print::pprust::token_to_string; | |
15 | use syntax::util::parser::AssocOp; | |
16 | use syntax::ast; | |
17 | use utils::{higher, snippet, snippet_opt}; | |
2c00a5a8 | 18 | use syntax_pos::{BytePos, Pos}; |
ea8adc8c XL |
19 | |
20 | /// A helper type to build suggestion correctly handling parenthesis. | |
21 | pub enum Sugg<'a> { | |
22 | /// An expression that never needs parenthesis such as `1337` or `[0; 42]`. | |
23 | NonParen(Cow<'a, str>), | |
24 | /// An expression that does not fit in other variants. | |
25 | MaybeParen(Cow<'a, str>), | |
26 | /// A binary operator expression, including `as`-casts and explicit type | |
27 | /// coercion. | |
28 | BinOp(AssocOp, Cow<'a, str>), | |
29 | } | |
30 | ||
31 | /// Literal constant `1`, for convenience. | |
32 | pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1")); | |
33 | ||
34 | impl<'a> Display for Sugg<'a> { | |
35 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { | |
36 | match *self { | |
abe05a73 | 37 | Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f), |
ea8adc8c XL |
38 | } |
39 | } | |
40 | } | |
41 | ||
42 | #[allow(wrong_self_convention)] // ok, because of the function `as_ty` method | |
43 | impl<'a> Sugg<'a> { | |
44 | /// Prepare a suggestion from an expression. | |
45 | pub fn hir_opt(cx: &LateContext, expr: &hir::Expr) -> Option<Self> { | |
46 | snippet_opt(cx, expr.span).map(|snippet| { | |
47 | let snippet = Cow::Owned(snippet); | |
48 | match expr.node { | |
49 | hir::ExprAddrOf(..) | | |
50 | hir::ExprBox(..) | | |
51 | hir::ExprClosure(.., _) | | |
52 | hir::ExprIf(..) | | |
53 | hir::ExprUnary(..) | | |
54 | hir::ExprMatch(..) => Sugg::MaybeParen(snippet), | |
55 | hir::ExprAgain(..) | | |
56 | hir::ExprYield(..) | | |
57 | hir::ExprArray(..) | | |
58 | hir::ExprBlock(..) | | |
59 | hir::ExprBreak(..) | | |
60 | hir::ExprCall(..) | | |
61 | hir::ExprField(..) | | |
62 | hir::ExprIndex(..) | | |
63 | hir::ExprInlineAsm(..) | | |
64 | hir::ExprLit(..) | | |
65 | hir::ExprLoop(..) | | |
66 | hir::ExprMethodCall(..) | | |
67 | hir::ExprPath(..) | | |
68 | hir::ExprRepeat(..) | | |
69 | hir::ExprRet(..) | | |
70 | hir::ExprStruct(..) | | |
71 | hir::ExprTup(..) | | |
72 | hir::ExprTupField(..) | | |
73 | hir::ExprWhile(..) => Sugg::NonParen(snippet), | |
74 | hir::ExprAssign(..) => Sugg::BinOp(AssocOp::Assign, snippet), | |
75 | hir::ExprAssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet), | |
76 | hir::ExprBinary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet), | |
77 | hir::ExprCast(..) => Sugg::BinOp(AssocOp::As, snippet), | |
78 | hir::ExprType(..) => Sugg::BinOp(AssocOp::Colon, snippet), | |
79 | } | |
80 | }) | |
81 | } | |
82 | ||
83 | /// Convenience function around `hir_opt` for suggestions with a default | |
84 | /// text. | |
85 | pub fn hir(cx: &LateContext, expr: &hir::Expr, default: &'a str) -> Self { | |
86 | Self::hir_opt(cx, expr).unwrap_or_else(|| Sugg::NonParen(Cow::Borrowed(default))) | |
87 | } | |
88 | ||
89 | /// Prepare a suggestion from an expression. | |
90 | pub fn ast(cx: &EarlyContext, expr: &ast::Expr, default: &'a str) -> Self { | |
91 | use syntax::ast::RangeLimits; | |
92 | ||
93 | let snippet = snippet(cx, expr.span, default); | |
94 | ||
95 | match expr.node { | |
96 | ast::ExprKind::AddrOf(..) | | |
97 | ast::ExprKind::Box(..) | | |
98 | ast::ExprKind::Closure(..) | | |
99 | ast::ExprKind::If(..) | | |
100 | ast::ExprKind::IfLet(..) | | |
101 | ast::ExprKind::InPlace(..) | | |
102 | ast::ExprKind::Unary(..) | | |
103 | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet), | |
104 | ast::ExprKind::Block(..) | | |
105 | ast::ExprKind::Break(..) | | |
106 | ast::ExprKind::Call(..) | | |
107 | ast::ExprKind::Catch(..) | | |
108 | ast::ExprKind::Continue(..) | | |
109 | ast::ExprKind::Yield(..) | | |
110 | ast::ExprKind::Field(..) | | |
111 | ast::ExprKind::ForLoop(..) | | |
112 | ast::ExprKind::Index(..) | | |
113 | ast::ExprKind::InlineAsm(..) | | |
114 | ast::ExprKind::Lit(..) | | |
115 | ast::ExprKind::Loop(..) | | |
116 | ast::ExprKind::Mac(..) | | |
117 | ast::ExprKind::MethodCall(..) | | |
118 | ast::ExprKind::Paren(..) | | |
119 | ast::ExprKind::Path(..) | | |
120 | ast::ExprKind::Repeat(..) | | |
121 | ast::ExprKind::Ret(..) | | |
122 | ast::ExprKind::Struct(..) | | |
123 | ast::ExprKind::Try(..) | | |
124 | ast::ExprKind::Tup(..) | | |
125 | ast::ExprKind::TupField(..) | | |
126 | ast::ExprKind::Array(..) | | |
127 | ast::ExprKind::While(..) | | |
128 | ast::ExprKind::WhileLet(..) => Sugg::NonParen(snippet), | |
129 | ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet), | |
abe05a73 | 130 | ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet), |
ea8adc8c XL |
131 | ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet), |
132 | ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet), | |
133 | ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet), | |
134 | ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet), | |
135 | ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet), | |
136 | } | |
137 | } | |
138 | ||
139 | /// Convenience method to create the `<lhs> && <rhs>` suggestion. | |
abe05a73 XL |
140 | pub fn and(self, rhs: &Self) -> Sugg<'static> { |
141 | make_binop(ast::BinOpKind::And, &self, rhs) | |
ea8adc8c XL |
142 | } |
143 | ||
144 | /// Convenience method to create the `<lhs> as <rhs>` suggestion. | |
145 | pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> { | |
146 | make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into())) | |
147 | } | |
148 | ||
149 | /// Convenience method to create the `&<expr>` suggestion. | |
150 | pub fn addr(self) -> Sugg<'static> { | |
151 | make_unop("&", self) | |
152 | } | |
153 | ||
154 | /// Convenience method to create the `&mut <expr>` suggestion. | |
155 | pub fn mut_addr(self) -> Sugg<'static> { | |
156 | make_unop("&mut ", self) | |
157 | } | |
158 | ||
159 | /// Convenience method to create the `*<expr>` suggestion. | |
160 | pub fn deref(self) -> Sugg<'static> { | |
161 | make_unop("*", self) | |
162 | } | |
163 | ||
164 | /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>` | |
165 | /// suggestion. | |
abe05a73 | 166 | pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> { |
ea8adc8c | 167 | match limit { |
abe05a73 XL |
168 | ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end), |
169 | ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end), | |
ea8adc8c XL |
170 | } |
171 | } | |
172 | ||
173 | /// Add parenthesis to any expression that might need them. Suitable to the | |
174 | /// `self` argument of | |
175 | /// a method call (eg. to build `bar.foo()` or `(1 + 2).foo()`). | |
176 | pub fn maybe_par(self) -> Self { | |
177 | match self { | |
178 | Sugg::NonParen(..) => self, | |
179 | // (x) and (x).y() both don't need additional parens | |
abe05a73 XL |
180 | Sugg::MaybeParen(sugg) => if sugg.starts_with('(') && sugg.ends_with(')') { |
181 | Sugg::MaybeParen(sugg) | |
182 | } else { | |
183 | Sugg::NonParen(format!("({})", sugg).into()) | |
ea8adc8c XL |
184 | }, |
185 | Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()), | |
186 | } | |
187 | } | |
188 | } | |
189 | ||
190 | impl<'a, 'b> std::ops::Add<Sugg<'b>> for Sugg<'a> { | |
191 | type Output = Sugg<'static>; | |
192 | fn add(self, rhs: Sugg<'b>) -> Sugg<'static> { | |
193 | make_binop(ast::BinOpKind::Add, &self, &rhs) | |
194 | } | |
195 | } | |
196 | ||
197 | impl<'a, 'b> std::ops::Sub<Sugg<'b>> for Sugg<'a> { | |
198 | type Output = Sugg<'static>; | |
199 | fn sub(self, rhs: Sugg<'b>) -> Sugg<'static> { | |
200 | make_binop(ast::BinOpKind::Sub, &self, &rhs) | |
201 | } | |
202 | } | |
203 | ||
204 | impl<'a> std::ops::Not for Sugg<'a> { | |
205 | type Output = Sugg<'static>; | |
206 | fn not(self) -> Sugg<'static> { | |
207 | make_unop("!", self) | |
208 | } | |
209 | } | |
210 | ||
211 | /// Helper type to display either `foo` or `(foo)`. | |
212 | struct ParenHelper<T> { | |
213 | /// Whether parenthesis are needed. | |
214 | paren: bool, | |
215 | /// The main thing to display. | |
216 | wrapped: T, | |
217 | } | |
218 | ||
219 | impl<T> ParenHelper<T> { | |
220 | /// Build a `ParenHelper`. | |
221 | fn new(paren: bool, wrapped: T) -> Self { | |
222 | Self { | |
0531ce1d XL |
223 | paren, |
224 | wrapped, | |
ea8adc8c XL |
225 | } |
226 | } | |
227 | } | |
228 | ||
229 | impl<T: Display> Display for ParenHelper<T> { | |
230 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { | |
231 | if self.paren { | |
232 | write!(f, "({})", self.wrapped) | |
233 | } else { | |
234 | self.wrapped.fmt(f) | |
235 | } | |
236 | } | |
237 | } | |
238 | ||
239 | /// Build the string for `<op><expr>` adding parenthesis when necessary. | |
240 | /// | |
241 | /// For convenience, the operator is taken as a string because all unary | |
242 | /// operators have the same | |
243 | /// precedence. | |
244 | pub fn make_unop(op: &str, expr: Sugg) -> Sugg<'static> { | |
245 | Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into()) | |
246 | } | |
247 | ||
248 | /// Build the string for `<lhs> <op> <rhs>` adding parenthesis when necessary. | |
249 | /// | |
250 | /// Precedence of shift operator relative to other arithmetic operation is | |
251 | /// often confusing so | |
252 | /// parenthesis will always be added for a mix of these. | |
253 | pub fn make_assoc(op: AssocOp, lhs: &Sugg, rhs: &Sugg) -> Sugg<'static> { | |
254 | /// Whether the operator is a shift operator `<<` or `>>`. | |
255 | fn is_shift(op: &AssocOp) -> bool { | |
256 | matches!(*op, AssocOp::ShiftLeft | AssocOp::ShiftRight) | |
257 | } | |
258 | ||
259 | /// Whether the operator is a arithmetic operator (`+`, `-`, `*`, `/`, `%`). | |
260 | fn is_arith(op: &AssocOp) -> bool { | |
261 | matches!( | |
262 | *op, | |
263 | AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus | |
264 | ) | |
265 | } | |
266 | ||
267 | /// Whether the operator `op` needs parenthesis with the operator `other` | |
268 | /// in the direction | |
269 | /// `dir`. | |
270 | fn needs_paren(op: &AssocOp, other: &AssocOp, dir: Associativity) -> bool { | |
abe05a73 XL |
271 | other.precedence() < op.precedence() |
272 | || (other.precedence() == op.precedence() | |
273 | && ((op != other && associativity(op) != dir) | |
274 | || (op == other && associativity(op) != Associativity::Both))) | |
275 | || is_shift(op) && is_arith(other) || is_shift(other) && is_arith(op) | |
ea8adc8c XL |
276 | } |
277 | ||
278 | let lhs_paren = if let Sugg::BinOp(ref lop, _) = *lhs { | |
279 | needs_paren(&op, lop, Associativity::Left) | |
280 | } else { | |
281 | false | |
282 | }; | |
283 | ||
284 | let rhs_paren = if let Sugg::BinOp(ref rop, _) = *rhs { | |
285 | needs_paren(&op, rop, Associativity::Right) | |
286 | } else { | |
287 | false | |
288 | }; | |
289 | ||
290 | let lhs = ParenHelper::new(lhs_paren, lhs); | |
291 | let rhs = ParenHelper::new(rhs_paren, rhs); | |
292 | let sugg = match op { | |
abe05a73 XL |
293 | AssocOp::Add | |
294 | AssocOp::BitAnd | | |
295 | AssocOp::BitOr | | |
296 | AssocOp::BitXor | | |
297 | AssocOp::Divide | | |
298 | AssocOp::Equal | | |
299 | AssocOp::Greater | | |
300 | AssocOp::GreaterEqual | | |
301 | AssocOp::LAnd | | |
302 | AssocOp::LOr | | |
303 | AssocOp::Less | | |
304 | AssocOp::LessEqual | | |
305 | AssocOp::Modulus | | |
306 | AssocOp::Multiply | | |
307 | AssocOp::NotEqual | | |
308 | AssocOp::ShiftLeft | | |
309 | AssocOp::ShiftRight | | |
310 | AssocOp::Subtract => format!("{} {} {}", lhs, op.to_ast_binop().expect("Those are AST ops").to_string(), rhs), | |
ea8adc8c XL |
311 | AssocOp::Inplace => format!("in ({}) {}", lhs, rhs), |
312 | AssocOp::Assign => format!("{} = {}", lhs, rhs), | |
313 | AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_to_string(&token::BinOp(op)), rhs), | |
314 | AssocOp::As => format!("{} as {}", lhs, rhs), | |
315 | AssocOp::DotDot => format!("{}..{}", lhs, rhs), | |
abe05a73 | 316 | AssocOp::DotDotEq => format!("{}..={}", lhs, rhs), |
ea8adc8c XL |
317 | AssocOp::Colon => format!("{}: {}", lhs, rhs), |
318 | }; | |
319 | ||
320 | Sugg::BinOp(op, sugg.into()) | |
321 | } | |
322 | ||
323 | /// Convinience wrapper arround `make_assoc` and `AssocOp::from_ast_binop`. | |
324 | pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg, rhs: &Sugg) -> Sugg<'static> { | |
325 | make_assoc(AssocOp::from_ast_binop(op), lhs, rhs) | |
326 | } | |
327 | ||
328 | #[derive(PartialEq, Eq, Clone, Copy)] | |
329 | /// Operator associativity. | |
330 | enum Associativity { | |
331 | /// The operator is both left-associative and right-associative. | |
332 | Both, | |
333 | /// The operator is left-associative. | |
334 | Left, | |
335 | /// The operator is not associative. | |
336 | None, | |
337 | /// The operator is right-associative. | |
338 | Right, | |
339 | } | |
340 | ||
341 | /// Return the associativity/fixity of an operator. The difference with | |
342 | /// `AssocOp::fixity` is that | |
343 | /// an operator can be both left and right associative (such as `+`: | |
344 | /// `a + b + c == (a + b) + c == a + (b + c)`. | |
345 | /// | |
346 | /// Chained `as` and explicit `:` type coercion never need inner parenthesis so | |
347 | /// they are considered | |
348 | /// associative. | |
349 | fn associativity(op: &AssocOp) -> Associativity { | |
350 | use syntax::util::parser::AssocOp::*; | |
351 | ||
352 | match *op { | |
353 | Inplace | Assign | AssignOp(_) => Associativity::Right, | |
354 | Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both, | |
abe05a73 XL |
355 | Divide | |
356 | Equal | | |
357 | Greater | | |
358 | GreaterEqual | | |
359 | Less | | |
360 | LessEqual | | |
361 | Modulus | | |
362 | NotEqual | | |
363 | ShiftLeft | | |
364 | ShiftRight | | |
ea8adc8c | 365 | Subtract => Associativity::Left, |
abe05a73 | 366 | DotDot | DotDotEq => Associativity::None, |
ea8adc8c XL |
367 | } |
368 | } | |
369 | ||
370 | /// Convert a `hir::BinOp` to the corresponding assigning binary operator. | |
371 | fn hirbinop2assignop(op: hir::BinOp) -> AssocOp { | |
372 | use rustc::hir::BinOp_::*; | |
373 | use syntax::parse::token::BinOpToken::*; | |
374 | ||
375 | AssocOp::AssignOp(match op.node { | |
376 | BiAdd => Plus, | |
377 | BiBitAnd => And, | |
378 | BiBitOr => Or, | |
379 | BiBitXor => Caret, | |
380 | BiDiv => Slash, | |
381 | BiMul => Star, | |
382 | BiRem => Percent, | |
383 | BiShl => Shl, | |
384 | BiShr => Shr, | |
385 | BiSub => Minus, | |
386 | BiAnd | BiEq | BiGe | BiGt | BiLe | BiLt | BiNe | BiOr => panic!("This operator does not exist"), | |
387 | }) | |
388 | } | |
389 | ||
390 | /// Convert an `ast::BinOp` to the corresponding assigning binary operator. | |
391 | fn astbinop2assignop(op: ast::BinOp) -> AssocOp { | |
392 | use syntax::ast::BinOpKind::*; | |
393 | use syntax::parse::token::BinOpToken; | |
394 | ||
395 | AssocOp::AssignOp(match op.node { | |
396 | Add => BinOpToken::Plus, | |
397 | BitAnd => BinOpToken::And, | |
398 | BitOr => BinOpToken::Or, | |
399 | BitXor => BinOpToken::Caret, | |
400 | Div => BinOpToken::Slash, | |
401 | Mul => BinOpToken::Star, | |
402 | Rem => BinOpToken::Percent, | |
403 | Shl => BinOpToken::Shl, | |
404 | Shr => BinOpToken::Shr, | |
405 | Sub => BinOpToken::Minus, | |
406 | And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"), | |
407 | }) | |
408 | } | |
409 | ||
410 | /// Return the indentation before `span` if there are nothing but `[ \t]` | |
411 | /// before it on its line. | |
412 | fn indentation<'a, T: LintContext<'a>>(cx: &T, span: Span) -> Option<String> { | |
413 | let lo = cx.sess().codemap().lookup_char_pos(span.lo()); | |
abe05a73 XL |
414 | if let Some(line) = lo.file |
415 | .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */) | |
ea8adc8c XL |
416 | { |
417 | if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') { | |
418 | // we can mix char and byte positions here because we only consider `[ \t]` | |
419 | if lo.col == CharPos(pos) { | |
420 | Some(line[..pos].into()) | |
421 | } else { | |
422 | None | |
423 | } | |
424 | } else { | |
425 | None | |
426 | } | |
427 | } else { | |
428 | None | |
429 | } | |
430 | } | |
431 | ||
432 | /// Convenience extension trait for `DiagnosticBuilder`. | |
433 | pub trait DiagnosticBuilderExt<'a, T: LintContext<'a>> { | |
434 | /// Suggests to add an attribute to an item. | |
435 | /// | |
436 | /// Correctly handles indentation of the attribute and item. | |
437 | /// | |
438 | /// # Example | |
439 | /// | |
440 | /// ```rust,ignore | |
441 | /// db.suggest_item_with_attr(cx, item, "#[derive(Default)]"); | |
442 | /// ``` | |
443 | fn suggest_item_with_attr<D: Display + ?Sized>(&mut self, cx: &T, item: Span, msg: &str, attr: &D); | |
444 | ||
445 | /// Suggest to add an item before another. | |
446 | /// | |
447 | /// The item should not be indented (expect for inner indentation). | |
448 | /// | |
449 | /// # Example | |
450 | /// | |
451 | /// ```rust,ignore | |
452 | /// db.suggest_prepend_item(cx, item, | |
453 | /// "fn foo() { | |
454 | /// bar(); | |
455 | /// }"); | |
456 | /// ``` | |
457 | fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str); | |
2c00a5a8 XL |
458 | |
459 | /// Suggest to completely remove an item. | |
460 | /// | |
461 | /// This will remove an item and all following whitespace until the next non-whitespace | |
462 | /// character. This should work correctly if item is on the same indentation level as the | |
463 | /// following item. | |
464 | /// | |
465 | /// # Example | |
466 | /// | |
467 | /// ```rust,ignore | |
468 | /// db.suggest_remove_item(cx, item, "remove this") | |
469 | /// ``` | |
470 | fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str); | |
ea8adc8c XL |
471 | } |
472 | ||
473 | impl<'a, 'b, 'c, T: LintContext<'c>> DiagnosticBuilderExt<'c, T> for rustc_errors::DiagnosticBuilder<'b> { | |
474 | fn suggest_item_with_attr<D: Display + ?Sized>(&mut self, cx: &T, item: Span, msg: &str, attr: &D) { | |
475 | if let Some(indent) = indentation(cx, item) { | |
476 | let span = item.with_hi(item.lo()); | |
477 | ||
478 | self.span_suggestion(span, msg, format!("{}\n{}", attr, indent)); | |
479 | } | |
480 | } | |
481 | ||
482 | fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str) { | |
483 | if let Some(indent) = indentation(cx, item) { | |
484 | let span = item.with_hi(item.lo()); | |
485 | ||
486 | let mut first = true; | |
487 | let new_item = new_item | |
488 | .lines() | |
abe05a73 XL |
489 | .map(|l| { |
490 | if first { | |
491 | first = false; | |
492 | format!("{}\n", l) | |
493 | } else { | |
494 | format!("{}{}\n", indent, l) | |
495 | } | |
ea8adc8c XL |
496 | }) |
497 | .collect::<String>(); | |
498 | ||
499 | self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent)); | |
500 | } | |
501 | } | |
2c00a5a8 XL |
502 | |
503 | fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str) { | |
504 | let mut remove_span = item; | |
505 | let hi = cx.sess().codemap().next_point(remove_span).hi(); | |
506 | let fmpos = cx.sess().codemap().lookup_byte_offset(hi); | |
507 | ||
508 | if let Some(ref src) = fmpos.fm.src { | |
509 | let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n'); | |
510 | ||
511 | if let Some(non_whitespace_offset) = non_whitespace_offset { | |
512 | remove_span = remove_span.with_hi(remove_span.hi() + BytePos(non_whitespace_offset as u32)) | |
513 | } | |
514 | } | |
515 | ||
516 | self.span_suggestion(remove_span, msg, String::new()); | |
517 | } | |
ea8adc8c | 518 | } |