diff --git a/crates/ty_test/src/hover.rs b/crates/ty_test/src/hover.rs index ef6adf6a5e..0fc0706c2b 100644 --- a/crates/ty_test/src/hover.rs +++ b/crates/ty_test/src/hover.rs @@ -9,7 +9,6 @@ use ruff_db::parsed::parsed_module; use ruff_db::source::{line_index, source_text}; use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal}; use ruff_python_ast::AnyNodeRef; -use ruff_python_trivia::CommentRanges; use ruff_text_size::{Ranged, TextSize}; use ty_python_semantic::{HasType, SemanticModel}; @@ -69,56 +68,44 @@ fn infer_type_at_position(db: &Db, file: File, offset: TextSize) -> Option Vec { +/// Uses the parsed assertions from the assertion module, which correctly handles +/// multiple stacked assertion comments and determines the target line number. +pub(crate) fn generate_hover_outputs( + db: &Db, + file: File, + assertions: &crate::assertion::InlineFileAssertions, +) -> Vec { let source = source_text(db, file); let lines = line_index(db, file); - let parsed = parsed_module(db, file).load(db); - let comment_ranges = CommentRanges::from(parsed.tokens()); let mut hover_outputs = Vec::new(); - for comment_range in &comment_ranges { - let comment_text = &source[comment_range]; + // Iterate through all assertion groups, which are already associated with their target line + for line_assertions in assertions { + let target_line = line_assertions.line_number; - // Check if this is a hover assertion (contains "# ↓ hover:" or "# hover:") - if !comment_text.trim().starts_with('#') { - continue; - } + // Look for hover assertions in this line's assertions + for assertion in line_assertions.iter() { + if let crate::assertion::UnparsedAssertion::Hover(hover_text) = assertion { + // Find the down arrow position in the comment text to determine the column + if let Some(arrow_position) = hover_text.find('↓') { + // Get the start offset of the target line + let target_line_start = lines.line_start(target_line, &source); - let trimmed = comment_text.trim().strip_prefix('#').unwrap().trim(); - if !trimmed.starts_with("↓ hover:") && !trimmed.starts_with("hover:") { - continue; - } + // Calculate the hover position: start of target line + arrow column (0-indexed) + let hover_offset = + target_line_start + TextSize::try_from(arrow_position).unwrap(); - // Find the down arrow position in the comment - let arrow_offset = comment_text.find('↓'); - if arrow_offset.is_none() { - // No down arrow means we can't determine the column - continue; - } - let arrow_column = arrow_offset.unwrap(); - - // Get the line number of the comment - let comment_line = lines.line_index(comment_range.start()); - - // The hover target is the next non-comment, non-empty line - let target_line = comment_line.saturating_add(1); - - // Get the start offset of the target line - let target_line_start = lines.line_start(target_line, &source); - - // Calculate the hover position: start of target line + arrow column - let hover_offset = target_line_start + TextSize::try_from(arrow_column).unwrap(); - - // Get the inferred type at that position - if let Some(inferred_type) = infer_type_at_position(db, file, hover_offset) { - hover_outputs.push(matcher::CheckOutput::Hover { - offset: hover_offset, - inferred_type, - }); + // Get the inferred type at that position + if let Some(inferred_type) = infer_type_at_position(db, file, hover_offset) { + hover_outputs.push(matcher::CheckOutput::Hover { + offset: hover_offset, + inferred_type, + }); + } + } + // If no down arrow, skip this hover assertion (will be caught as error by matcher) + } } } diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 94ca20eaaa..5d50d06636 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -377,8 +377,11 @@ fn run_test( .map(|diag| matcher::CheckOutput::Diagnostic(diag.clone())) .collect(); + // Parse assertions to get hover assertions with correct line numbers + let assertions = assertion::InlineFileAssertions::from_file(db, test_file.file); + // Generate and add hover outputs - check_outputs.extend(hover::generate_hover_outputs(db, test_file.file)); + check_outputs.extend(hover::generate_hover_outputs(db, test_file.file, &assertions)); let failure = match matcher::match_file(db, test_file.file, &check_outputs) { Ok(()) => None,