[ty_test] Fix hover column calculation to use line position
The previous implementation calculated the down arrow position within the comment text, but then incorrectly used that as an offset into the target line. This only worked if the comment started at column 0. Now we properly calculate the column position by: 1. Finding the arrow offset within the comment text 2. Getting the comment's column position in its line 3. Adding these together to get the arrow's column in the line 4. Using that column to index into the target line This fixes hover assertions when comments are indented or don't start at the beginning of the line. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -129,7 +129,7 @@ impl<'a> Iterator for AssertionWithRangeIterator<'a> {
|
||||
loop {
|
||||
let inner_next = self.inner.next()?;
|
||||
let comment = &self.file_assertions.source[inner_next];
|
||||
if let Some(assertion) = UnparsedAssertion::from_comment(comment) {
|
||||
if let Some(assertion) = UnparsedAssertion::from_comment(comment, inner_next) {
|
||||
return Some(AssertionWithRange(assertion, inner_next));
|
||||
}
|
||||
}
|
||||
@@ -250,13 +250,14 @@ pub(crate) enum UnparsedAssertion<'a> {
|
||||
///
|
||||
/// The first string is the expected type (body after `hover:`).
|
||||
/// The second string is the full comment text (including the down arrow).
|
||||
Hover(&'a str, &'a str),
|
||||
/// The TextRange is the position of the comment in the source file.
|
||||
Hover(&'a str, &'a str, TextRange),
|
||||
}
|
||||
|
||||
impl<'a> UnparsedAssertion<'a> {
|
||||
/// Returns `Some(_)` if the comment starts with `# error:`, `# revealed:`, or `# hover:`,
|
||||
/// indicating that it is an assertion comment.
|
||||
fn from_comment(comment: &'a str) -> Option<Self> {
|
||||
fn from_comment(comment: &'a str, range: TextRange) -> Option<Self> {
|
||||
let trimmed = comment.trim().strip_prefix('#')?.trim();
|
||||
let (keyword, body) = trimmed.split_once(':')?;
|
||||
let keyword = keyword.trim();
|
||||
@@ -265,7 +266,7 @@ impl<'a> UnparsedAssertion<'a> {
|
||||
match keyword {
|
||||
"revealed" => Some(Self::Revealed(body)),
|
||||
"error" => Some(Self::Error(body)),
|
||||
"hover" | "↓ hover" => Some(Self::Hover(body, comment)),
|
||||
"hover" | "↓ hover" => Some(Self::Hover(body, comment, range)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -283,8 +284,8 @@ impl<'a> UnparsedAssertion<'a> {
|
||||
Self::Error(error) => ErrorAssertion::from_str(error)
|
||||
.map(ParsedAssertion::Error)
|
||||
.map_err(PragmaParseError::ErrorAssertionParseError),
|
||||
Self::Hover(expected_type, full_comment) => {
|
||||
HoverAssertion::from_str(expected_type, full_comment)
|
||||
Self::Hover(expected_type, full_comment, range) => {
|
||||
HoverAssertion::from_str(expected_type, full_comment, *range)
|
||||
.map(ParsedAssertion::Hover)
|
||||
.map_err(PragmaParseError::HoverAssertionParseError)
|
||||
}
|
||||
@@ -297,7 +298,7 @@ impl std::fmt::Display for UnparsedAssertion<'_> {
|
||||
match self {
|
||||
Self::Revealed(expected_type) => write!(f, "revealed: {expected_type}"),
|
||||
Self::Error(assertion) => write!(f, "error: {assertion}"),
|
||||
Self::Hover(expected_type, _) => write!(f, "hover: {expected_type}"),
|
||||
Self::Hover(expected_type, _, _) => write!(f, "hover: {expected_type}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,9 +364,12 @@ impl std::fmt::Display for ErrorAssertion<'_> {
|
||||
/// A parsed and validated `# hover:` assertion comment.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HoverAssertion<'a> {
|
||||
/// The zero-based column where the down arrow appears in the assertion comment.
|
||||
/// This indicates the position in the next line where we should hover.
|
||||
pub(crate) column: usize,
|
||||
/// The zero-based column offset within the comment where the down arrow appears.
|
||||
/// This indicates the character position in the target line where we should hover.
|
||||
pub(crate) arrow_offset_in_comment: usize,
|
||||
|
||||
/// The range of the comment in the source file.
|
||||
pub(crate) comment_range: TextRange,
|
||||
|
||||
/// The expected type at the hover position.
|
||||
pub(crate) expected_type: &'a str,
|
||||
@@ -375,18 +379,20 @@ impl<'a> HoverAssertion<'a> {
|
||||
fn from_str(
|
||||
expected_type: &'a str,
|
||||
full_comment: &'a str,
|
||||
comment_range: TextRange,
|
||||
) -> Result<Self, HoverAssertionParseError> {
|
||||
if expected_type.is_empty() {
|
||||
return Err(HoverAssertionParseError::EmptyType);
|
||||
}
|
||||
|
||||
// Find the down arrow position in the full comment to determine the column
|
||||
let column = full_comment
|
||||
// Find the down arrow position within the comment text
|
||||
let arrow_offset_in_comment = full_comment
|
||||
.find('↓')
|
||||
.ok_or(HoverAssertionParseError::MissingDownArrow)?;
|
||||
|
||||
Ok(Self {
|
||||
column,
|
||||
arrow_offset_in_comment,
|
||||
comment_range,
|
||||
expected_type,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,21 +96,28 @@ pub(crate) fn generate_hover_outputs(
|
||||
|
||||
// Look for hover assertions in this line's assertions
|
||||
for assertion in line_assertions.iter() {
|
||||
let UnparsedAssertion::Hover(_, _) = assertion else {
|
||||
let UnparsedAssertion::Hover(_, _, _) = assertion else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Parse the assertion to get the column
|
||||
// Parse the assertion to get the hover information
|
||||
let Ok(ParsedAssertion::Hover(hover)) = assertion.parse() else {
|
||||
// Invalid hover assertion - will be caught as error by matcher
|
||||
continue;
|
||||
};
|
||||
|
||||
// Calculate the column within the comment's line
|
||||
// First, get the line and column of the comment's start
|
||||
let comment_line_col = lines.line_column(hover.comment_range.start(), &source);
|
||||
|
||||
// The hover column is the comment's column plus the arrow offset within the comment
|
||||
let hover_column_in_line = comment_line_col.column.to_zero_indexed() + hover.arrow_offset_in_comment;
|
||||
|
||||
// Get the start offset of the target line
|
||||
let target_line_start = lines.line_start(target_line, &source);
|
||||
|
||||
// Calculate the hover position from the column in the parsed assertion
|
||||
let hover_offset = target_line_start + TextSize::try_from(hover.column).unwrap();
|
||||
// Calculate the hover position: start of target line + column in line
|
||||
let hover_offset = target_line_start + TextSize::try_from(hover_column_in_line).unwrap();
|
||||
|
||||
// Get the inferred type at that position
|
||||
let Some(inferred_type) = infer_type_at_position(db, file, hover_offset) else {
|
||||
|
||||
Reference in New Issue
Block a user