Content-Length: 700468 | pFad | http://redirect.github.com/astral-sh/ruff/pull/16257

4D Add `per-file-target-version` option by ntBre · Pull Request #16257 · astral-sh/ruff · GitHub
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

Add per-file-target-version option #16257

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open

Conversation

ntBre
Copy link
Contributor

@ntBre ntBre commented Feb 19, 2025

Summary

This PR is another step in preparing to detect syntax errors in the parser. It introduces the new per-file-target-version top-level configuration option, which holds a mapping of compiled glob patterns to Python versions. I intend to use the LinterSettings::resolve_target_version method here to pass to the parser:

// Parse once.
let parsed =
ruff_python_parser::parse_unchecked_source(transformed.source_code(), source_type);

Test Plan

I added two new CLI tests to show that the per-file-target-version is respected in both the formatter and the linter.

Copy link
Contributor

github-actions bot commented Feb 19, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@MichaReiser
Copy link
Member

We should respect the per-file-target-version when resolving the target version for a file being linted or formatted. Ideally, we'd add a CLI test that demonstrates that the per-file-target-version override is respected both for linting and formatting.

@MichaReiser MichaReiser added the configuration Related to settings and configuration label Feb 19, 2025
@ntBre
Copy link
Contributor Author

ntBre commented Feb 19, 2025

That makes sense to me. How does the approach of making the current target_version fields private and requiring callers to use the resolve_target_version method sound to you? I only added LinterSettings::resolve_target_version so far, but I'm assuming a formatter analog would be easy too.

I wasn't sure how many changes to put in this PR, but I think it definitely makes sense to wire it up somehow for useful tests.

@ntBre ntBre marked this pull request as draft February 19, 2025 16:33
@ntBre
Copy link
Contributor Author

ntBre commented Feb 19, 2025

I'll convert to a draft because I think it's incomplete based on your suggestion.

@MichaReiser
Copy link
Member

Hmm. I haven't really thought about it. We probably want to avoid calling resolve_target_version everywhere where we compare the target versions. That would become expensive very quickly. Ideally, we would compute the target version for the current file once and then store it somewhere (on Checker?)

@ntBre
Copy link
Contributor Author

ntBre commented Feb 19, 2025

Reopening for review. I added

  • Checker::target_version as a field and const method to return the resolved PythonVersion 1 to be used in lint rules
  • Target version resolution to the formatter
  • Two new CLI tests showing that both the linter and formatter respect the new option

Footnotes

  1. I tried also making the LinterSettings::target_version private, as I mentioned above, but it disrupts a ton of tests that create them directly with code like LinterSettings { ..LinterSettings::default() }. This could be solved with a bunch of with_* methods to build a LinterSettings without pub fields, but after adding two of those and seeing the number of remaining errors, I just made them pub again. I think I caught all of the (current) uses in lint rules while they were private, though.

@ntBre ntBre marked this pull request as ready for review February 19, 2025 19:33
@ntBre ntBre requested a review from AlexWaygood as a code owner February 19, 2025 19:33
Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

This is great! I suggest renaming the field on the setting structs to e.g. default_target_version or global_target_version so that its name raises questions: Hmm, why global? maybe this is not the field I want. Let me check the documentation

We should also test this feature in the editor. I think it currently doesn't work for the formatter.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

I'll leave this one mostly to Micha, just a quick nit I spotted

@@ -129,7 +133,7 @@ fn get_prefix<'a>(settings: &'a LinterSettings, path: &Path) -> Option<&'a PathB
prefix
}

fn is_allowed_module(settings: &LinterSettings, module: &str) -> bool {
fn is_allowed_module(settings: &LinterSettings, minor_version: u8, module: &str) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to pass around a PythonVersion in the ruff_linter crate where possible (only converting it to a u8 representing the minor version when crossing the crate boundary and calling a function from the ruff_python_stdlib crate) as it's more strongly typed

Suggested change
fn is_allowed_module(settings: &LinterSettings, minor_version: u8, module: &str) -> bool {
fn is_allowed_module(settings: &LinterSettings, version: PythonVersion, module: &str) -> bool {

@ntBre ntBre force-pushed the brent/per-file-target-version branch from f8ab420 to 778c559 Compare February 20, 2025 22:33
@ntBre
Copy link
Contributor Author

ntBre commented Feb 20, 2025

Wow I didn't know eliding the lifetime in a const was a recent feature. I thought clippy used to suggest that 🤷 Maybe I need to start building with 1.80 locally instead of 1.82...

@ntBre
Copy link
Contributor Author

ntBre commented Feb 20, 2025

Thanks @MichaReiser for the thorough review. I think it's in a much better place now. The only open question I see now is how to add tests for the editor integration (if that's possible).

I also feel like there might be a more elegant solution to the generic PerFile<T> stuff, but I haven't found it yet. Probably we could remove some of the wrapper newtypes and just use the generic versions directly at least, if we want to.

Comment on lines 223 to 232
//redirect.github.com/ The non-path-resolved Python version specified by the `target-version` input option.
//redirect.github.com/
//redirect.github.com/ See [`LinterSettings::resolve_target_version`] for a way to obtain the Python version for a
//redirect.github.com/ given file, while respecting the overrides in `per_file_target_version`.
pub unresolved_target_version: PythonVersion,
//redirect.github.com/ Path-specific overrides to `unresolved_target_version`.
//redirect.github.com/
//redirect.github.com/ See [`LinterSettings::resolve_target_version`] for a way to check a given [`Path`]
//redirect.github.com/ against these patterns, while falling back to `unresolved_target_version` if none of them
//redirect.github.com/ match.
Copy link
Member

Choose a reason for hiding this comment

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

I'd probably mention Checker.target_version first because that's what most devs should use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, of course

}
}

impl Display for CompiledPerFileIgnoreList {
impl<T: CacheKey + std::fmt::Debug + PerFileKind> CompiledPerFileList<T> {
pub(crate) fn iter_matches<'a, 'p>(&'a self, path: &'p Path) -> impl Iterator<Item = &'p T>
Copy link
Member

Choose a reason for hiding this comment

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

I'd probably add a third argument: debug_label: &'static str over introducing my own trait. Seems simpler


impl<T> Display for CompiledPerFileList<T>
where
T: Display + CacheKey,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
T: Display + CacheKey,
T: Display,

pub struct CompiledPerFileIgnoreList {
// Ordered as (absolute path matcher, basename matcher, rules)
ignores: Vec<CompiledPerFileIgnore>,
pub struct CompiledPerFileList<T: CacheKey> {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub struct CompiledPerFileList<T: CacheKey> {
pub struct CompiledPerFileList<T> {

I prefer to avoid having type constraints in struct and instead, only add them on the impls where needed.

I think the downside of this is that you'd have to implement CacheKey manually (because we aren't using Rust edition 2024 yet)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah that makes sense, I did find it annoying to have to put : CacheKey everywhere. I took a brief look at adding the trait bounds in the derive macro, but I probably need a little more proc macro practice before tackling that!

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if that's something that you even can do in the proc macro? I'd suggest to simply implement CacheKey manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what Clone does, so I thought it was possible. But I looked for the source code, and it said Clone was a compiler builtin, so maybe you're right. I already switched to the manual impl locally, I was just curious.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think serde might be able to do it too, again just for my curiositiy, I don't think we need to do this here. CacheKey is easy to implement.

@MichaReiser
Copy link
Member

The only open question I see now is how to add tests for the editor integration (if that's possible).

There are a few end to end tests in the VS code extension but it isn't something we have good infrastructure today. But @dhruvmanila knows better than I.

@dhruvmanila
Copy link
Member

There are a few end to end tests in the VS code extension but it isn't something we have good infrastructure today. But @dhruvmanila knows better than I.

I've talked with Brent in regards to this on Discord. Looking at the change that fixed the issue which you were pointing out earlier there are two options:

  1. Add unit tests in https://github.com/astral-sh/ruff/blob/df9fbdce3af5af826115780f2d8c5cc4d91be650/crates/ruff_server/src/format.rs for format function specifically for per-file-target-version
  2. Add a E2E test in https://github.com/astral-sh/ruff-vscode/blob/main/src/test/e2e.test.ts with a fixture that sets this new option but this would only work after the Ruff release with this option is went out. I wouldn't recommend using this for a new option in Ruff.

I'd suggest (1) but I'm longing to create a mock server xD

@ntBre
Copy link
Contributor Author

ntBre commented Feb 21, 2025

Yes, thanks @dhruvmanila! I've added unit tests for format and format_range for scripts that parallel the other linter and formatter tests I added, so I think this was a really nice idea.

It looks like notebooks also go through this code path, but I'm happy to add notebook-specific tests if either of you want. It shouldn't be too much trouble if I reuse the create_test_notebook infrastructure from notebook.rs.

Similarly, it looks like linting goes straight through check_path, where the target version resolution happens and is tested in the linter. But again, I'd be happy to add linter tests in the server crate if you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
configuration Related to settings and configuration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://redirect.github.com/astral-sh/ruff/pull/16257

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy