use rustc_lexer::{FrontmatterAllowed, LiteralKind, TokenKind, tokenize};
use rustc_lint::{LateContext, LintContext};
use rustc_span::{BytePos, Pos, Span, Symbol};
use super::UNICODE_ELLIPSIS_IN_PANIC_MESSAGES;
use crate::literal_scan::{emit_flagged_chars, string_literal_quote_lengths};
pub(super) fn scan_macro_call_source(
lint_context: &LateContext<'_>,
flagged_chars: &[char],
call_span: Span,
macro_name: Symbol,
) {
let Ok(snippet) = lint_context.sess().source_map().span_to_snippet(call_span) else {
return;
};
let context = format!("`{macro_name}!` message");
let skip_arguments = arguments_before_message(macro_name);
let mut byte_offset: u32 = 0;
let mut depth: u32 = 0;
let mut top_level_comma_count: u32 = 0;
for token in tokenize(&snippet, FrontmatterAllowed::No) {
let token_length = token.len;
match token.kind {
TokenKind::OpenParen | TokenKind::OpenBracket | TokenKind::OpenBrace => {
depth = depth.saturating_add(1);
}
TokenKind::CloseParen | TokenKind::CloseBracket | TokenKind::CloseBrace => {
depth = depth.saturating_sub(1);
}
TokenKind::Comma if depth == 1 => {
top_level_comma_count = top_level_comma_count.saturating_add(1);
}
TokenKind::Literal { kind, .. }
if depth == 1
&& top_level_comma_count >= skip_arguments
&& is_display_string_literal(kind) =>
{
let token_start = byte_offset as usize;
let token_end = token_start + token_length as usize;
let literal_snippet = &snippet[token_start..token_end];
let token_lo = call_span.lo() + BytePos::from_u32(byte_offset);
let token_hi = token_lo + BytePos::from_u32(token_length);
let token_span =
Span::new(token_lo, token_hi, call_span.ctxt(), call_span.parent());
scan_literal(
lint_context,
flagged_chars,
token_span,
literal_snippet,
&context,
);
}
_ => {}
}
byte_offset = byte_offset
.checked_add(token_length)
.expect("snippet offset overflowed u32");
}
}
pub(super) fn scan_literal(
lint_context: &LateContext<'_>,
flagged_chars: &[char],
literal_span: Span,
literal_snippet: &str,
context: &str,
) {
let Some((prefix_length, suffix_length)) = string_literal_quote_lengths(literal_snippet) else {
return;
};
let body = &literal_snippet[prefix_length..literal_snippet.len() - suffix_length];
emit_flagged_chars(
lint_context,
UNICODE_ELLIPSIS_IN_PANIC_MESSAGES,
body,
flagged_chars,
context,
|byte_offset, character_length| {
let span_start =
literal_span.lo() + BytePos::from_u32((prefix_length + byte_offset) as u32);
let span_end = span_start + BytePos::from_u32(character_length);
Span::new(
span_start,
span_end,
literal_span.ctxt(),
literal_span.parent(),
)
},
);
}
fn is_display_string_literal(kind: LiteralKind) -> bool {
matches!(kind, LiteralKind::Str { .. } | LiteralKind::RawStr { .. })
}
fn arguments_before_message(macro_name: Symbol) -> u32 {
match macro_name.as_str() {
"assert" | "debug_assert" => 1,
"assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => 2,
_ => 0,
}
}