Skip to content

Commit b336277

Browse files
committed
Rework clippy_utils::source.
1 parent f1cfd6e commit b336277

13 files changed

+493
-275
lines changed

clippy_lints/src/cognitive_complexity.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_and_help;
3-
use clippy_utils::source::{IntoSpan, SpanExt};
3+
use clippy_utils::source::SpanExt;
44
use clippy_utils::ty::is_type_diagnostic_item;
55
use clippy_utils::visitors::for_each_expr_without_closures;
66
use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn};
@@ -104,14 +104,15 @@ impl CognitiveComplexity {
104104
FnKind::Closure => {
105105
let header_span = body_span.with_hi(decl.output.span().lo());
106106
#[expect(clippy::range_plus_one)]
107-
if let Some(range) = header_span.map_range(cx, |src, range| {
108-
let mut idxs = src.get(range.clone())?.match_indices('|');
109-
Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
110-
}) {
111-
range.with_ctxt(header_span.ctxt())
112-
} else {
107+
let Some(s) = header_span.map_span(cx, |file| {
108+
file.map_range(|_, src, range| {
109+
let mut idxs = src.get(range.clone())?.match_indices('|');
110+
Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
111+
})
112+
}) else {
113113
return;
114-
}
114+
};
115+
s
115116
},
116117
};
117118

clippy_lints/src/collapsible_if.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
3-
use clippy_utils::source::{IntoSpan as _, SpanExt, snippet, snippet_block, snippet_block_with_applicability};
3+
use clippy_utils::source::{SpanExt, snippet, snippet_block, snippet_block_with_applicability};
44
use rustc_ast::BinOpKind;
55
use rustc_errors::Applicability;
66
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
@@ -123,6 +123,7 @@ impl CollapsibleIf {
123123
}
124124
}
125125

126+
#[expect(clippy::range_plus_one)]
126127
fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) {
127128
if let Some(inner) = expr_block(then)
128129
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
@@ -131,21 +132,36 @@ impl CollapsibleIf {
131132
&& let ctxt = expr.span.ctxt()
132133
&& inner.span.ctxt() == ctxt
133134
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
135+
&& let Some(then_open_bracket) = then.span.map_span(cx, |file| {
136+
file.map_range(|_, src, range| {
137+
src.get(range.clone())?
138+
.starts_with('{')
139+
.then_some(range.start..range.start + 1)
140+
})?
141+
.with_leading_whitespace()
142+
})
143+
&& let Some(then_closing_bracket) = then.span.map_span(cx, |file| {
144+
file.map_range(|_, src, range| {
145+
src.get(range.clone())?
146+
.ends_with('}')
147+
.then_some(range.end - 1..range.end)
148+
})?
149+
.with_leading_whitespace()
150+
})
151+
&& let Some(inner_if) = inner.span.map_span(cx, |file| {
152+
file.map_range(|_, src, range| {
153+
src.get(range.clone())?
154+
.starts_with("if")
155+
.then_some(range.start..range.start + 2)
156+
})
157+
})
134158
{
135159
span_lint_and_then(
136160
cx,
137161
COLLAPSIBLE_IF,
138162
expr.span,
139163
"this `if` statement can be collapsed",
140164
|diag| {
141-
let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span();
142-
let then_closing_bracket = {
143-
let end = then.span.shrink_to_hi();
144-
end.with_lo(end.lo() - rustc_span::BytePos(1))
145-
.with_leading_whitespace(cx)
146-
.into_span()
147-
};
148-
let inner_if = inner.span.split_at(2).0;
149165
let mut sugg = vec![
150166
// Remove the outer then block `{`
151167
(then_open_bracket, String::new()),

clippy_lints/src/copies.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
3-
use clippy_utils::source::{IntoSpan, SpanExt, first_line_of_span, indent_of, reindent_multiline, snippet};
3+
use clippy_utils::source::{SpanExt, first_line_of_span, indent_of, reindent_multiline, snippet};
44
use clippy_utils::ty::{InteriorMut, needs_ordered_drop};
55
use clippy_utils::visitors::for_each_expr_without_closures;
66
use clippy_utils::{
@@ -249,21 +249,23 @@ fn lint_branches_sharing_code<'tcx>(
249249
let suggestion = reindent_multiline(&suggestion, true, cond_indent);
250250
(replace_span, suggestion.to_string())
251251
});
252-
let end_suggestion = res.end_span(last_block, sm).map(|span| {
252+
let end_suggestion = res.end_span(last_block, sm).and_then(|span| {
253253
let moved_snipped = reindent_multiline(&snippet(cx, span, "_"), true, None);
254254
let indent = indent_of(cx, expr.span.shrink_to_hi());
255255
let suggestion = "}\n".to_string() + &moved_snipped;
256256
let suggestion = reindent_multiline(&suggestion, true, indent);
257257

258-
let span = span.with_hi(last_block.span.hi());
259258
// Improve formatting if the inner block has indentation (i.e. normal Rust formatting)
260-
let span = span
261-
.map_range(cx, |src, range| {
262-
(range.start > 4 && src.get(range.start - 4..range.start)? == " ")
263-
.then_some(range.start - 4..range.end)
259+
let span = span.map_span(cx, |file| {
260+
file.with_hi(last_block.span.hi()).map_range(|_, src, range| {
261+
if src.get(..range.start)?.ends_with(" ") {
262+
Some(range.start - 4..range.end)
263+
} else {
264+
Some(range)
265+
}
264266
})
265-
.map_or(span, |range| range.with_ctxt(span.ctxt()));
266-
(span, suggestion.to_string())
267+
})?;
268+
Some((span, suggestion.to_string()))
267269
});
268270

269271
let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) {

clippy_lints/src/default_constructed_unit_structs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs {
7575
&& !base.is_suggestable_infer_ty()
7676
{
7777
let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())];
78-
if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) {
78+
if expr.span.check_source_text(cx, |s| s.starts_with('<')) {
7979
// Remove `<`, '>` has already been removed by the existing removal expression.
8080
removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new()));
8181
}

clippy_lints/src/implicit_hasher.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_session::declare_lint_pass;
1212
use rustc_span::Span;
1313

1414
use clippy_utils::diagnostics::span_lint_and_then;
15-
use clippy_utils::source::{IntoSpan, SpanExt, snippet};
15+
use clippy_utils::source::{SpanExt, snippet};
1616
use clippy_utils::sym;
1717
use clippy_utils::ty::is_type_diagnostic_item;
1818

@@ -119,14 +119,13 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
119119
}
120120

121121
let generics_suggestion_span = impl_.generics.span.substitute_dummy({
122-
let range = (item.span.lo()..target.span().lo()).map_range(cx, |src, range| {
123-
Some(src.get(range.clone())?.find("impl")? + 4..range.end)
124-
});
125-
if let Some(range) = range {
126-
range.with_ctxt(item.span.ctxt())
127-
} else {
122+
let Some(s) = item.span.map_span(cx, |file| {
123+
file.with_hi(target.span().hi())
124+
.map_range(|_, src, range| Some(src.get(range.clone())?.find("impl")? + 4..range.end))
125+
}) else {
128126
return;
129-
}
127+
};
128+
s
130129
});
131130

132131
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
@@ -165,16 +164,16 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
165164
continue;
166165
}
167166
let generics_suggestion_span = generics.span.substitute_dummy({
168-
let range = (item.span.lo()..body.params[0].pat.span.lo()).map_range(cx, |src, range| {
169-
let (pre, post) = src.get(range.clone())?.split_once("fn")?;
170-
let pos = post.find('(')? + pre.len() + 2;
171-
Some(pos..pos)
172-
});
173-
if let Some(range) = range {
174-
range.with_ctxt(item.span.ctxt())
175-
} else {
167+
let Some(s) = item.span.map_span(cx, |file| {
168+
file.with_hi(body.params[0].pat.span.lo()).map_range(|_, src, range| {
169+
let (pre, post) = src.get(range.clone())?.split_once("fn")?;
170+
let pos = post.find('(')? + pre.len() + 2;
171+
Some(pos..pos)
172+
})
173+
}) else {
176174
return;
177-
}
175+
};
176+
s
178177
});
179178

180179
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);

clippy_lints/src/matches/single_match.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
2525
/// match arms.
2626
fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
2727
if let Some(ff) = span.get_source_range(cx)
28-
&& let Some(text) = ff.as_str()
28+
&& let Some(text) = ff.src_text()
2929
{
3030
text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*")
3131
} else {

clippy_lints/src/methods/manual_inspect.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::msrvs::{self, Msrv};
3-
use clippy_utils::source::{IntoSpan, SpanExt};
3+
use clippy_utils::source::{SourceFileRange, SpanExt};
44
use clippy_utils::ty::get_field_by_name;
55
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures};
66
use clippy_utils::{ExprUseNode, expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id};
@@ -89,7 +89,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
8989
})
9090
.is_none();
9191

92-
if ret_count != 0 {
92+
if !can_lint || ret_count != 0 {
9393
// A return expression that didn't return the original value was found.
9494
return;
9595
}
@@ -98,19 +98,26 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
9898
let mut addr_of_edits = Vec::with_capacity(delayed.len());
9999
for x in delayed {
100100
match x {
101-
UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())),
101+
UseKind::Return(s) => {
102+
if let Some(s) = s.map_span(cx, SourceFileRange::with_leading_whitespace) {
103+
edits.push((s, String::new()));
104+
} else {
105+
return;
106+
}
107+
},
102108
UseKind::Borrowed(s) => {
103109
#[expect(clippy::range_plus_one)]
104-
let range = s.map_range(cx, |src, range| {
105-
let src = src.get(range.clone())?;
106-
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+
if let Some(s) = s.map_span(cx, |file| {
111+
file.map_range(|_, src, range| {
112+
let src = src.get(range.clone())?;
113+
let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']);
114+
trimmed.starts_with('&').then(|| {
115+
let pos = range.start + src.len() - trimmed.len();
116+
pos..pos + 1
117+
})
110118
})
111-
});
112-
if let Some(range) = range {
113-
addr_of_edits.push((range.with_ctxt(s.ctxt()), String::new()));
119+
}) {
120+
addr_of_edits.push((s, String::new()));
114121
} else {
115122
requires_copy = true;
116123
requires_deref = true;
@@ -157,10 +164,10 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
157164
}
158165
}
159166

160-
if can_lint
161-
&& (!requires_copy || cx.type_is_copy_modulo_regions(arg_ty))
167+
if (!requires_copy || cx.type_is_copy_modulo_regions(arg_ty))
162168
// This case could be handled, but a fair bit of care would need to be taken.
163169
&& (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.typing_env()))
170+
&& let Some(final_expr_span) = final_expr.span.map_span(cx, SourceFileRange::with_leading_whitespace)
164171
{
165172
if requires_deref {
166173
edits.push((param.span.shrink_to_lo(), "&".into()));
@@ -173,13 +180,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
173180
_ => return,
174181
};
175182
edits.push((name_span, edit.to_string()));
176-
edits.push((
177-
final_expr
178-
.span
179-
.with_leading_whitespace(cx)
180-
.with_ctxt(final_expr.span.ctxt()),
181-
String::new(),
182-
));
183+
edits.push((final_expr_span, String::new()));
183184
let app = if edits.iter().any(|(s, _)| s.from_expansion()) {
184185
Applicability::MaybeIncorrect
185186
} else {

clippy_lints/src/multiple_bound_locations.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ impl EarlyLintPass for MultipleBoundLocations {
5757
&& let Some(Some(bound_span)) = pred
5858
.bounded_ty
5959
.span
60-
.with_source_text(cx, |src| generic_params_with_bounds.get(src))
60+
.get_source_text(cx)
61+
.map(|src| generic_params_with_bounds.get(&*src))
6162
{
6263
emit_lint(cx, *bound_span, pred.bounded_ty.span);
6364
}

clippy_lints/src/needless_else.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::source::{IntoSpan, SpanExt};
2+
use clippy_utils::source::SpanExt;
33
use rustc_ast::ast::{Expr, ExprKind};
44
use rustc_errors::Applicability;
55
use rustc_lint::{EarlyContext, EarlyLintPass};
66
use rustc_session::declare_lint_pass;
7+
use rustc_span::SyntaxContext;
78

89
declare_clippy_lint! {
910
/// ### What it does
@@ -41,16 +42,17 @@ impl EarlyLintPass for NeedlessElse {
4142
&& !expr.span.from_expansion()
4243
&& !else_clause.span.from_expansion()
4344
&& block.stmts.is_empty()
44-
&& let range = (then_block.span.hi()..expr.span.hi()).trim_start(cx)
45-
&& range.clone().check_source_text(cx, |src| {
45+
&& let Some(mut range) = (then_block.span.hi()..expr.span.hi()).get_source_range(cx)
46+
&& range.trim_start().is_some()
47+
&& range.src_text().is_some_and(|src| {
4648
// Ignore else blocks that contain comments or #[cfg]s
4749
!src.contains(['/', '#'])
4850
})
4951
{
5052
span_lint_and_sugg(
5153
cx,
5254
NEEDLESS_ELSE,
53-
range.with_ctxt(expr.span.ctxt()),
55+
range.as_span(SyntaxContext::root()),
5456
"this `else` branch is empty",
5557
"you can remove it",
5658
String::new(),

clippy_lints/src/ranges.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, HirId};
1111
use rustc_lint::{LateContext, LateLintPass};
1212
use rustc_middle::ty;
1313
use rustc_session::impl_lint_pass;
14-
use rustc_span::Span;
1514
use rustc_span::source_map::Spanned;
15+
use rustc_span::{Span, SyntaxContext};
1616
use std::cmp::Ordering;
1717

1818
declare_clippy_lint! {
@@ -182,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
182182
if let ExprKind::Binary(ref op, l, r) = expr.kind
183183
&& self.msrv.meets(cx, msrvs::RANGE_CONTAINS)
184184
{
185-
check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
185+
check_possible_range_contains(cx, op.node, l, r, expr.span, expr.span.ctxt());
186186
}
187187

188188
check_exclusive_range_plus_one(cx, expr);
@@ -196,8 +196,8 @@ fn check_possible_range_contains(
196196
op: BinOpKind,
197197
left: &Expr<'_>,
198198
right: &Expr<'_>,
199-
expr: &Expr<'_>,
200199
span: Span,
200+
ctxt: SyntaxContext,
201201
) {
202202
if is_in_const_context(cx) {
203203
return;
@@ -283,13 +283,14 @@ fn check_possible_range_contains(
283283
// the same operator precedence
284284
if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind
285285
&& op == lhs_op.node
286-
&& let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent())
287-
&& new_span.check_source_text(cx, |src| {
286+
&& new_lhs.span.ctxt() == ctxt
287+
&& let Some(span) = new_lhs.span.map_span(cx, |file| {
288+
let src = file.with_hi(span.hi()).src_text()?;
288289
// Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
289-
src.matches('(').count() == src.matches(')').count()
290+
(src.matches('(').count() == src.matches(')').count()).then_some(file)
290291
})
291292
{
292-
check_possible_range_contains(cx, op, new_lhs, right, expr, new_span);
293+
check_possible_range_contains(cx, op, new_lhs, right, span, ctxt);
293294
}
294295
}
295296

@@ -363,7 +364,10 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
363364
|diag| {
364365
let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string());
365366
let end = Sugg::hir(cx, y, "y").maybe_paren();
366-
match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
367+
match span
368+
.get_source_text(cx)
369+
.map(|src| src.starts_with('(') && src.ends_with(')'))
370+
{
367371
Some(true) => {
368372
diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect);
369373
},

clippy_utils/src/consts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
712712
&& let expr_lo = expr_span.lo()
713713
&& expr_lo >= span.lo
714714
&& let Some(src) = (span.lo..expr_lo).get_source_range(&self.tcx)
715-
&& let Some(src) = src.as_str()
715+
&& let Some(src) = src.src_text()
716716
{
717717
use rustc_lexer::TokenKind::{BlockComment, LineComment, OpenBrace, Semi, Whitespace};
718718
if !tokenize(src)

0 commit comments

Comments
 (0)