Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pygrep_hooks] Fix blanket-noqa panic when last line has noqa with no newline (PGH004) #11108

Merged
merged 3 commits into from
Apr 25, 2024

Conversation

augustelalande
Copy link
Contributor

@augustelalande augustelalande commented Apr 23, 2024

Summary

Resolves #11102

The error stems from these lines

// Extend a mapping at the end of the file to also include the EOF token.
if let Some(last) = directives.last_mut() {
if last.range.end() == locator.contents().text_len() {
last.range = last.range.add_end(TextSize::from(1));
}
}

I don't really understand the purpose of incrementing the last index, but it makes the resulting range invalid for indexing into contents.

For now I just detect if the index is too high in blanket_noqa and adjust it if necessary.

Test Plan

Created fixture from issue example.

Copy link
Contributor

github-actions bot commented Apr 23, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@AlexWaygood AlexWaygood changed the title [pygreb_hooks] Fix blanket-noqa panic when last line has noqa with no newline (PGH004) [pygrep_hooks] Fix blanket-noqa panic when last line has noqa with no newline (PGH004) Apr 23, 2024
@@ -88,8 +88,13 @@ pub(crate) fn blanket_noqa(
) {
for directive_line in noqa_directives.lines() {
if let Directive::All(all) = &directive_line.directive {
let line = locator.slice(directive_line.range);
let offset = directive_line.range.start();
let line = if directive_line.end() > locator.contents().text_len() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug, if the .end() is greater than the contents... Have you looked into how that happens?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the PR summary, but what happens if we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya it generates a fixture failure with W292_1.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the code mentioned in the PR summary was added by @MichaReiser so maybe he can shed some light.

@MichaReiser
Copy link
Member

Uff, I wrote this code like a year ago. But what I did is uncomment that specific code in the noqa file and run all tests and the following test now fails

def fn() -> None:
pass # noqa: W292

The problem is that we have fixes reported at the very end of the file, and there must be a way to suppress them.

The way the noqa lines are found is by searching by a given offset:

fn find_line_index(&self, offset: TextSize) -> Option<usize> {
self.inner
.binary_search_by(|directive| {
if directive.range.end() < offset {
std::cmp::Ordering::Less
} else if directive.range.contains(offset) {
std::cmp::Ordering::Equal
} else {
std::cmp::Ordering::Greater
}
})
.ok()
}

If we have two lines, than the previous_line.end() == next_line.start(). That's why the above methods searches the line using directive.end() < offset to avoid that a noqa comment suppresses a violation on the next lines.

I must admit, my "solution" is a hack and it would be nice if we could support end-of-file noqa comments in a better way.

@augustelalande
Copy link
Contributor Author

augustelalande commented Apr 24, 2024

@MichaReiser @charliermarsh I implemented a fix which addresses the issue at its source. Let me know if you like it better.

Self { inner: directives }
Self {
inner: directives,
last_directive_includes_eof,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think I would rename this to includes_end to make it clear that the comparison should include the end range.

Comment on lines 727 to 746
let index = self.inner.binary_search_by(|directive| {
if directive.range.end() < offset {
std::cmp::Ordering::Less
} else if directive.range.contains(offset) {
std::cmp::Ordering::Equal
} else {
std::cmp::Ordering::Greater
}
});

match index {
Ok(index) => Some(index),
Err(index) => {
if self.last_directive_includes_eof && index == self.inner.len() - 1 {
Some(index)
} else {
std::cmp::Ordering::Greater
None
}
})
.ok()
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search then becomes

self inner.binary_search_by(|directive| {
	if directive.range.end() < offset {
		std::cmp::Ordering::Less
	} else if directive.range.start() >  offset {
		std::cmp::Ordering::Greater
	} 
	// At this point, end >= offset, start <= offset
	else if !directive.includes_end && directive.range.end() == offset  {
		std::cmp::Ordering::Less // I think or should it be greater?
	} else {
		std::cmp::Ordering::Equal
	}

@augustelalande
Copy link
Contributor Author

Done. I don't know what that wasm test failure is about.

@charliermarsh
Copy link
Member

I think it's spurious, I'll re-run it.

@charliermarsh charliermarsh merged commit 3364ef9 into astral-sh:main Apr 25, 2024
18 checks passed
@charliermarsh charliermarsh added the bug Something isn't working label Apr 25, 2024
@augustelalande augustelalande deleted the pgh004-bug branch April 25, 2024 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rule PGH004 cause panic
3 participants