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

Merged
merged 33 commits into from
Feb 24, 2025
Merged

Add per-file-target-version option #16257

merged 33 commits into from
Feb 24, 2025

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.

@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.

Copy link
Member

@dhruvmanila dhruvmanila left a comment

Choose a reason for hiding this comment

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

Thank you for adding those test cases in the language server, they look good.

@ntBre ntBre merged commit e7a6c19 into main Feb 24, 2025
21 checks passed
@ntBre ntBre deleted the brent/per-file-target-version branch February 24, 2025 13:47
dcreager added a commit that referenced this pull request Feb 25, 2025
* main: (38 commits)
  [red-knot] Use arena-allocated association lists for narrowing constraints (#16306)
  [red-knot] Rewrite `Type::try_iterate()` to improve type inference and diagnostic messages (#16321)
  Add issue templates (#16213)
  Normalize inconsistent markdown headings in docstrings (#16364)
  [red-knot] Better diagnostics for method calls (#16362)
  [red-knot] Add argfile and windows glob path support (#16353)
  [red-knot] Handle pipe-errors gracefully (#16354)
  Rename `venv-path` to `python` (#16347)
  [red-knot] Fixup some formatting in `infer.rs` (#16348)
  [red-knot] Restrict visibility of more things in `class.rs` (#16346)
  [red-knot] Add diagnostic for class-object access to pure instance variables (#16036)
  Add `per-file-target-version` option (#16257)
  [PLW1507] Mark fix unsafe (#16343)
  [red-knot] Add a test to ensure that `KnownClass::try_from_file_and_name()` is kept up to date (#16326)
  Extract class and instance types (#16337)
  Re-order changelog entries for 0.9.7 (#16344)
  [red-knot] Add support for `@classmethod`s (#16305)
  Update Salsa (#16338)
  Update Salsa part 1 (#16340)
  Upgrade Rust toolchain to 1.85.0 (#16339)
  ...
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