Skip to content

Commit ce2d616

Browse files
committed
Rework the suspicious formatting lints.
1 parent 236c8c7 commit ce2d616

15 files changed

+1095
-396
lines changed

clippy_lints/src/formatting.rs

Lines changed: 231 additions & 159 deletions
Large diffs are not rendered by default.

clippy_lints/src/implicit_hasher.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,13 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
162162
continue;
163163
}
164164
let generics_suggestion_span = generics.span.substitute_dummy({
165-
let range = (item.span.lo()..body.params[0].pat.span.lo()).map_range(cx, |src, range| {
166-
let (pre, post) = src.get(range.clone())?.split_once("fn")?;
167-
let pos = post.find('(')? + pre.len() + 2;
168-
Some(pos..pos)
169-
});
165+
let range = (item.span.lo()..body.params[0].pat.span.lo()).map_range_as_pos_len(
166+
cx,
167+
|src, range| {
168+
let (pre, post) = src.get(range.clone())?.split_once("fn")?;
169+
Some((post.find('(')? + pre.len() + 2, 0))
170+
},
171+
);
170172
if let Some(range) = range {
171173
range.with_ctxt(item.span.ctxt())
172174
} else {

clippy_lints/src/methods/manual_inspect.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,12 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
100100
match x {
101101
UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())),
102102
UseKind::Borrowed(s) => {
103-
#[expect(clippy::range_plus_one)]
104-
let range = s.map_range(cx, |src, range| {
103+
let range = s.map_range_as_pos_len(cx, |src, range| {
105104
let src = src.get(range.clone())?;
106105
let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']);
107-
trimmed.starts_with('&').then(|| {
108-
let pos = range.start + src.len() - trimmed.len();
109-
pos..pos + 1
110-
})
106+
trimmed
107+
.starts_with('&')
108+
.then_some((range.start + src.len() - trimmed.len(), 1))
111109
});
112110
if let Some(range) = range {
113111
addr_of_edits.push((range.with_ctxt(s.ctxt()), String::new()));

clippy_utils/src/source.rs

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use rustc_lint::{LateContext, LintContext};
1010
use rustc_session::Session;
1111
use rustc_span::source_map::{original_sp, SourceMap};
1212
use rustc_span::{
13-
hygiene, BytePos, FileNameDisplayPreference, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext,
14-
DUMMY_SP,
13+
hygiene, BytePos, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine, Span, SpanData,
14+
SyntaxContext, DUMMY_SP,
1515
};
1616
use std::borrow::Cow;
1717
use std::fmt;
@@ -75,6 +75,12 @@ pub trait SpanRangeExt: SpanRange {
7575
get_source_text(cx.sess().source_map(), self.into_range())
7676
}
7777

78+
/// Calls the given function with the indent of the referenced line and returns the value.
79+
/// Passes an empty string if the indent cannot be determined.
80+
fn with_line_indent<T>(self, cx: &impl LintContext, f: impl for<'a> FnOnce(&'a str) -> T) -> T {
81+
with_line_indent(cx.sess().source_map(), self.into_range(), f)
82+
}
83+
7884
/// Calls the given function with the source text referenced and returns the value. Returns
7985
/// `None` if the source text cannot be retrieved.
8086
fn with_source_text<T>(self, cx: &impl LintContext, f: impl for<'a> FnOnce(&'a str) -> T) -> Option<T> {
@@ -110,11 +116,29 @@ pub trait SpanRangeExt: SpanRange {
110116
map_range(cx.sess().source_map(), self.into_range(), f)
111117
}
112118

119+
/// Calls the given function with the both the text of the source file and the referenced range,
120+
/// and creates a new span from the result. Returns `None` if the source text cannot be
121+
/// retrieved, or no result is returned.
122+
///
123+
/// The new range must reside within the same source file.
124+
fn map_range_as_pos_len(
125+
self,
126+
cx: &impl LintContext,
127+
f: impl for<'a> FnOnce(&'a str, Range<usize>) -> Option<(usize, usize)>,
128+
) -> Option<Range<BytePos>> {
129+
map_range_as_pos_len(cx.sess().source_map(), self.into_range(), f)
130+
}
131+
113132
/// Extends the range to include all preceding whitespace characters.
114133
fn with_leading_whitespace(self, cx: &impl LintContext) -> Range<BytePos> {
115134
with_leading_whitespace(cx.sess().source_map(), self.into_range())
116135
}
117136

137+
/// Extends the range to include all trailing whitespace characters.
138+
fn with_trailing_whitespace(self, cx: &impl LintContext) -> Range<BytePos> {
139+
with_trailing_whitespace(cx.sess().source_map(), self.into_range())
140+
}
141+
118142
/// Trims the leading whitespace from the range.
119143
fn trim_start(self, cx: &impl LintContext) -> Range<BytePos> {
120144
trim_start(cx.sess().source_map(), self.into_range())
@@ -139,7 +163,7 @@ fn get_source_text(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange
139163
if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos {
140164
return None;
141165
}
142-
let range = start.pos.to_usize()..end.pos.to_usize();
166+
let range = RelativeBytePos(start.pos.0)..RelativeBytePos(end.pos.0);
143167
Some(SourceFileRange { sf: start.sf, range })
144168
}
145169

@@ -161,12 +185,31 @@ fn with_source_text_and_range<T>(
161185
if let Some(src) = get_source_text(sm, sp)
162186
&& let Some(text) = &src.sf.src
163187
{
164-
Some(f(text, src.range))
188+
Some(f(text, src.usize_range()))
165189
} else {
166190
None
167191
}
168192
}
169193

194+
fn with_line_indent<T>(sm: &SourceMap, sp: Range<BytePos>, f: impl for<'a> FnOnce(&'a str) -> T) -> T {
195+
let src = get_source_text(sm, sp);
196+
let indent = if let Some(src) = &src
197+
&& let Some(line) = src.sf.lookup_line(src.range.start)
198+
&& let Some(start) = src.sf.lines().get(line)
199+
&& let Some(text) = src.sf.src.as_ref()
200+
{
201+
let text = if let Some(end) = src.sf.lines().get(line + 1) {
202+
&text[start.to_usize()..end.to_usize()]
203+
} else {
204+
&text[start.to_usize()..]
205+
};
206+
&text[..text.len() - text.trim_start().len()]
207+
} else {
208+
""
209+
};
210+
f(indent)
211+
}
212+
170213
#[expect(clippy::cast_possible_truncation)]
171214
fn map_range(
172215
sm: &SourceMap,
@@ -175,7 +218,7 @@ fn map_range(
175218
) -> Option<Range<BytePos>> {
176219
if let Some(src) = get_source_text(sm, sp.clone())
177220
&& let Some(text) = &src.sf.src
178-
&& let Some(range) = f(text, src.range.clone())
221+
&& let Some(range) = f(text, src.usize_range())
179222
{
180223
debug_assert!(
181224
range.start <= text.len() && range.end <= text.len(),
@@ -184,21 +227,54 @@ fn map_range(
184227
text.len(),
185228
);
186229
debug_assert!(range.start <= range.end, "Range `{range:?}` has overlapping bounds");
187-
let dstart = (range.start as u32).wrapping_sub(src.range.start as u32);
188-
let dend = (range.end as u32).wrapping_sub(src.range.start as u32);
230+
let dstart = (range.start as u32).wrapping_sub(src.range.start.0);
231+
let dend = (range.end as u32).wrapping_sub(src.range.start.0);
189232
Some(BytePos(sp.start.0.wrapping_add(dstart))..BytePos(sp.start.0.wrapping_add(dend)))
190233
} else {
191234
None
192235
}
193236
}
194237

238+
#[expect(clippy::cast_possible_truncation)]
239+
fn map_range_as_pos_len(
240+
sm: &SourceMap,
241+
sp: Range<BytePos>,
242+
f: impl for<'a> FnOnce(&'a str, Range<usize>) -> Option<(usize, usize)>,
243+
) -> Option<Range<BytePos>> {
244+
if let Some(src) = get_source_text(sm, sp.clone())
245+
&& let Some(text) = &src.sf.src
246+
&& let Some((pos, len)) = f(text, src.usize_range())
247+
{
248+
debug_assert!(
249+
pos + len <= text.len(),
250+
"Range `{:?}` is outside the source file (file `{}`, length `{}`)",
251+
pos..pos + len,
252+
src.sf.name.display(FileNameDisplayPreference::Local),
253+
text.len(),
254+
);
255+
let delta = (pos as u32).wrapping_sub(src.range.start.0);
256+
let pos = sp.start.0.wrapping_add(delta);
257+
Some(BytePos(pos)..BytePos(pos + len as u32))
258+
} else {
259+
None
260+
}
261+
}
262+
195263
fn with_leading_whitespace(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> {
196264
map_range(sm, sp.clone(), |src, range| {
197265
Some(src.get(..range.start)?.trim_end().len()..range.end)
198266
})
199267
.unwrap_or(sp)
200268
}
201269

270+
fn with_trailing_whitespace(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> {
271+
map_range(sm, sp.clone(), |src, range| {
272+
let tail = src.get(range.end..)?;
273+
Some(range.start..range.end + (tail.len() - tail.trim_start().len()))
274+
})
275+
.unwrap_or(sp)
276+
}
277+
202278
fn trim_start(sm: &SourceMap, sp: Range<BytePos>) -> Range<BytePos> {
203279
map_range(sm, sp.clone(), |src, range| {
204280
let src = src.get(range.clone())?;
@@ -216,13 +292,18 @@ fn write_source_text_to(sm: &SourceMap, sp: Range<BytePos>, dst: &mut impl fmt::
216292

217293
pub struct SourceFileRange {
218294
pub sf: Lrc<SourceFile>,
219-
pub range: Range<usize>,
295+
pub range: Range<RelativeBytePos>,
220296
}
221297
impl SourceFileRange {
222298
/// Attempts to get the text from the source file. This can fail if the source text isn't
223299
/// loaded.
224300
pub fn as_str(&self) -> Option<&str> {
225-
self.sf.src.as_ref().and_then(|x| x.get(self.range.clone()))
301+
self.sf.src.as_ref().and_then(|x| x.get(self.usize_range()))
302+
}
303+
304+
/// Gets the range of the source text as a `usize` range.
305+
pub fn usize_range(&self) -> Range<usize> {
306+
self.range.start.0 as usize..self.range.end.0 as usize
226307
}
227308
}
228309

tests/ui/formatting.rs

Lines changed: 0 additions & 85 deletions
This file was deleted.

tests/ui/formatting.stderr

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)