use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::MetaItemInner;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, LintContext};
use rustc_span::{BytePos, Pos, RelativeBytePos, SourceFile, Span};
use super::insertion::{build_reason_insertion, escape_for_rust_string};
use super::scan::{Comment, find_trailing_comment};
use super::{LINT_REASON_FROM_COMMENT, LintReasonFromComment};
use crate::common::attr_has_reason;
impl LintReasonFromComment {
pub(super) fn check(
&self,
lint_context: &EarlyContext<'_>,
outer_span: Span,
invocation_span: Span,
args: &[MetaItemInner],
) -> bool {
if attr_has_reason(args).is_some() {
return false;
}
let source_map = lint_context.sess().source_map();
let source_file = source_map.lookup_source_file(outer_span.lo());
let Some(source_text) = source_file.src.as_deref() else {
return false;
};
let file_start = source_file.start_pos;
let Some(outer_hi) = outer_span.hi().0.checked_sub(file_start.0) else {
return false;
};
let outer_hi = outer_hi as usize;
if outer_hi > source_text.len() {
return false;
}
let Some(comment) = find_trailing_comment(source_text, outer_hi) else {
return false;
};
self.emit(lint_context, &source_file, invocation_span, &comment);
true
}
fn emit(
&self,
lint_context: &EarlyContext<'_>,
source_file: &SourceFile,
invocation_span: Span,
comment: &Comment,
) {
let snippet = match lint_context
.sess()
.source_map()
.span_to_snippet(invocation_span)
{
Ok(snippet) => snippet,
Err(_) => return,
};
let escaped = escape_for_rust_string(&comment.text);
let Some(insertion) = build_reason_insertion(&snippet, &escaped) else {
return;
};
let arg_lo = invocation_span.lo() + BytePos::from_usize(insertion.start);
let arg_hi = invocation_span.lo() + BytePos::from_usize(insertion.end);
let arg_span = invocation_span.with_lo(arg_lo).with_hi(arg_hi);
let diag_span = file_span(
source_file,
comment.diag_start,
comment.diag_end,
invocation_span,
);
let delete_span = file_span(
source_file,
comment.delete_start,
comment.delete_end,
invocation_span,
);
span_lint_and_then(
lint_context,
LINT_REASON_FROM_COMMENT,
diag_span,
"comment can be lifted into the attribute's `reason` field",
|diagnostic| {
diagnostic.multipart_suggestion(
"lift the comment into a `reason` field",
vec![
(arg_span, insertion.replacement),
(delete_span, String::new()),
],
Applicability::MaybeIncorrect,
);
},
);
}
}
fn file_span(source_file: &SourceFile, start: usize, end: usize, anchor: Span) -> Span {
let lo = source_file.absolute_position(RelativeBytePos::from_usize(start));
let hi = source_file.absolute_position(RelativeBytePos::from_usize(end));
Span::new(lo, hi, anchor.ctxt(), anchor.parent())
}