mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:45:24 +00:00
6 commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
![]() |
be03cb04c1
|
Expand ruff.configuration to allow inline config (#16296)
## Summary [Internal design document](https://www.notion.so/astral-sh/In-editor-settings-19e48797e1ca807fa8c2c91b689d9070?pvs=4) This PR expands `ruff.configuration` to allow inline configuration directly in the editor. For example: ```json { "ruff.configuration": { "line-length": 100, "lint": { "unfixable": ["F401"], "flake8-tidy-imports": { "banned-api": { "typing.TypedDict": { "msg": "Use `typing_extensions.TypedDict` instead" } } } }, "format": { "quote-style": "single" } } } ``` This means that now `ruff.configuration` accepts either a path to configuration file or the raw config itself. It's _mostly_ similar to `--config` with one difference that's highlighted in the following section. So, it can be said that the format of `ruff.configuration` when provided the config map is same as the one on the [playground] [^1]. ## Limitations <details><summary><b>Casing (<code>kebab-case</code> v/s/ <code>camelCase</code>)</b></summary> <p> The config keys needs to be in `kebab-case` instead of `camelCase` which is being used for other settings in the editor. This could be a bit confusing. For example, the `line-length` option can be set directly via an editor setting or can be configured via `ruff.configuration`: ```json { "ruff.configuration": { "line-length": 100 }, "ruff.lineLength": 120 } ``` #### Possible solution We could use feature flag with [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute) to indicate that when used in `ruff_server`, we need the `Options` fields to be renamed as `camelCase` while for other crates it needs to be renamed as `kebab-case`. But, this might not work very easily because it will require wrapping the `Options` struct and create two structs in which we'll have to add `#[cfg_attr(...)]` because otherwise `serde` will complain: ``` error: duplicate serde attribute `rename_all` --> crates/ruff_workspace/src/options.rs:43:38 | 43 | #[cfg_attr(feature = "editor", serde(rename_all = "camelCase"))] | ^^^^^^^^^^ ``` </p> </details> <details><summary><b>Nesting (flat v/s nested keys)</b></summary> <p> This is the major difference between `--config` flag on the command-line v/s `ruff.configuration` and it makes it such that `ruff.configuration` has same value format as [playground] [^1]. The config keys needs to be split up into keys which can result in nested structure instead of flat structure: So, the following **won't work**: ```json { "ruff.configuration": { "format.quote-style": "single", "lint.flake8-tidy-imports.banned-api.\"typing.TypedDict\".msg": "Use `typing_extensions.TypedDict` instead" } } ``` But, instead it would need to be split up like the following: ```json { "ruff.configuration": { "format": { "quote-style": "single" }, "lint": { "flake8-tidy-imports": { "banned-api": { "typing.TypedDict": { "msg": "Use `typing_extensions.TypedDict` instead" } } } } } } ``` #### Possible solution (1) The way we could solve this and make it same as `--config` would be to add a manual logic of converting the JSON map into an equivalent TOML string which would be then parsed into `Options`. So, the following JSON map: ```json { "lint.flake8-tidy-imports": { "banned-api": {"\"typing.TypedDict\".msg": "Use typing_extensions.TypedDict instead"}}} ``` would need to be converted into the following TOML string: ```toml lint.flake8-tidy-imports = { banned-api = { "typing.TypedDict".msg = "Use typing_extensions.TypedDict instead" } } ``` by recursively convering `"key": value` into `key = value` which is to remove the quotes from key and replacing `:` with `=`. #### Possible solution (2) Another would be to just accept `Map<String, String>` strictly and convert it into `key = value` and then parse it as a TOML string. This would also match `--config` but quotes might become a nuisance because JSON only allows double quotes and so it'll require escaping any inner quotes or use single quotes. </p> </details> ## Test Plan ### VS Code **Requires https://github.com/astral-sh/ruff-vscode/pull/702** **`settings.json`**: ```json { "ruff.lint.extendSelect": ["TID"], "ruff.configuration": { "line-length": 50, "format": { "quote-style": "single" }, "lint": { "unfixable": ["F401"], "flake8-tidy-imports": { "banned-api": { "typing.TypedDict": { "msg": "Use `typing_extensions.TypedDict` instead" } } } } } } ``` Following video showcases me doing the following: 1. Check diagnostics that it includes `TID` 2. Run `Ruff: Fix all auto-fixable problems` to test `unfixable` 3. Run `Format: Document` to test `line-length` and `quote-style` https://github.com/user-attachments/assets/0a38176f-3fb0-4960-a213-73b2ea5b1180 ### Neovim **`init.lua`**: ```lua require('lspconfig').ruff.setup { init_options = { settings = { lint = { extendSelect = { 'TID' }, }, configuration = { ['line-length'] = 50, format = { ['quote-style'] = 'single', }, lint = { unfixable = { 'F401' }, ['flake8-tidy-imports'] = { ['banned-api'] = { ['typing.TypedDict'] = { msg = 'Use typing_extensions.TypedDict instead', }, }, }, }, }, }, }, } ``` Same steps as in the VS Code test: https://github.com/user-attachments/assets/cfe49a9b-9a89-43d7-94f2-7f565d6e3c9d ## Documentation Preview https://github.com/user-attachments/assets/e0062f58-6ec8-4e01-889d-fac76fd8b3c7 [playground]: https://play.ruff.rs [^1]: This has one advantage that the value can be copy-pasted directly into the playground |
||
![]() |
2e77b775b0
|
Consider --preview flag for server subcommand (#12208)
## Summary This PR removes the requirement of `--preview` flag to run the `ruff server` and instead considers it to be an indicator to turn on preview mode for the linter and the formatter. resolves: #12161 ## Test Plan Add test cases to assert the `preview` value is updated accordingly. In an editor context, I used the local `ruff` executable in Neovim with the `--preview` flag and verified that the preview-only violations are being highlighted. Running with: ```lua require('lspconfig').ruff.setup({ cmd = { '/Users/dhruv/work/astral/ruff/target/debug/ruff', 'server', '--preview', }, }) ``` The screenshot shows that `E502` is highlighted with the below config in `pyproject.toml`: <img width="877" alt="Screenshot 2024-07-17 at 16 43 09" src="https://github.com/user-attachments/assets/c7016ef3-55b1-4a14-bbd3-a07b1bcdd323"> |
||
![]() |
37ad994318
|
Use default settings if initialization options is empty or not provided (#11566)
## Summary This PR fixes the bug to avoid flattening the global-only settings for the new server. This was added in https://github.com/astral-sh/ruff/pull/11497, possibly to correctly de-serialize an empty value (`{}`). But, this lead to a bug where the configuration under the `settings` key was not being read for global-only variant. By using #[serde(default)], we ensure that the settings field in the `GlobalOnly` variant is optional and that an empty JSON object `{}` is correctly deserialized into `GlobalOnly` with a default `ClientSettings` instance. fixes: #11507 ## Test Plan Update the snapshot and existing test case. Also, verify the following settings in Neovim: 1. Nothing ```lua ruff = { cmd = { '/Users/dhruv/work/astral/ruff/target/debug/ruff', 'server', '--preview', }, } ``` 2. Empty dictionary ```lua ruff = { cmd = { '/Users/dhruv/work/astral/ruff/target/debug/ruff', 'server', '--preview', }, init_options = vim.empty_dict(), } ``` 3. Empty `settings` ```lua ruff = { cmd = { '/Users/dhruv/work/astral/ruff/target/debug/ruff', 'server', '--preview', }, init_options = { settings = vim.empty_dict(), }, } ``` 4. With some configuration: ```lua ruff = { cmd = { '/Users/dhruv/work/astral/ruff/target/debug/ruff', 'server', '--preview', }, init_options = { settings = { configuration = '/tmp/ruff-repro/pyproject.toml', }, }, } ``` |
||
![]() |
94abea4b08
|
ruff server : Fix multiple issues with Neovim and Helix (#11497)
## Summary Fixes https://github.com/astral-sh/ruff/issues/11236. This PR fixes several issues, most of which relate to non-VS Code editors (Helix and Neovim). 1. Global-only initialization options are now correctly deserialized from Neovim and Helix 2. Empty diagnostics are now published correctly for Neovim and Helix. 3. A workspace folder is created at the current working directory if the initialization parameters send an empty list of workspace folders. 4. The server now gracefully handles opening files outside of any known workspace, and will use global fallback settings taken from client editor settings and a user settings TOML, if it exists. ## Test Plan I've tested to confirm that each issue has been fixed. * Global-only initialization options are now correctly deserialized from Neovim and Helix + the server gracefully handles opening files outside of any known workspace |
||
![]() |
2cc487eb22
|
ruff server : Introduce settings for directly configuring the linter and formatter (#10984)
## Summary The following client settings have been introduced to the language server: * `lint.preview` * `format.preview` * `lint.select` * `lint.extendSelect` * `lint.ignore` * `exclude` * `lineLength` `exclude` and `lineLength` apply to both the linter and formatter. This does not actually use the settings yet, but makes them available for future use. ## Test Plan Snapshot tests have been updated. |
||
![]() |
a184dc68f5
|
Implement client setting initialization and resolution for ruff server (#10764)
## Summary When a language server initializes, it is passed a serialized JSON object, which is known as its "initialization options". Until now, `ruff server` has ignored those initialization options, meaning that user-provided settings haven't worked. This PR is the first step for supporting settings from the LSP client. It implements procedures to deserialize initialization options into a settings object, and then resolve those settings objects into concrete settings for each workspace. One of the goals for user settings implementation in `ruff server` is backwards compatibility with `ruff-lsp`'s settings. We won't support all settings that `ruff-lsp` had, but the ones that we do support should work the same and use the same schema as `ruff-lsp`. These are the existing settings from `ruff-lsp` that we will continue to support, and which are part of the settings schema in this PR: | Setting | Default Value | Description | |----------------------------------------|---------------|----------------------------------------------------------------------------------------| | `codeAction.disableRuleComment.enable` | `true` | Whether to display Quick Fix actions to disable rules via `noqa` suppression comments. | | `codeAction.fixViolation.enable` | `true` | Whether to display Quick Fix actions to autofix violations. | | `fixAll` | `true` | Whether to register Ruff as capable of handling `source.fixAll` actions. | | `lint.enable` | `true` | Whether to enable linting. Set to `false` to use Ruff exclusively as a formatter. | | `organizeImports` | `true` | Whether to register Ruff as capable of handling `source.organizeImports` actions. | To be clear: this PR does not implement 'support' for these settings, individually. Rather, it constructs a framework for these settings to be used by the server in the future. Notably, we are choosing *not* to support `lint.args` and `format.args` as settings for `ruff server`. This is because we're now interfacing with Ruff at a lower level than its CLI, and converting CLI arguments back into configuration is too involved. We will have support for linter and formatter specific settings in follow-up PRs. We will also 'hook up' user settings to work with the server in follow up PRs. ## Test Plan ### Snapshot Tests Tests have been created in `crates/ruff_server/src/session/settings/tests.rs` to ensure that deserialization and settings resolution works as expected. ### Manual Testing Since we aren't using the resolved settings anywhere yet, we'll have to add a few printing statements. We want to capture what the resolved settings look like when sent as part of a snapshot, so modify `Session::take_snapshot` to be the following: ```rust pub(crate) fn take_snapshot(&self, url: &Url) -> Option<DocumentSnapshot> { let resolved_settings = self.workspaces.client_settings(url, &self.global_settings); tracing::info!("Resolved settings for document {url}: {resolved_settings:?}"); Some(DocumentSnapshot { configuration: self.workspaces.configuration(url)?.clone(), resolved_client_capabilities: self.resolved_client_capabilities.clone(), client_settings: resolved_settings, document_ref: self.workspaces.snapshot(url)?, position_encoding: self.position_encoding, url: url.clone(), }) } ``` Once you've done that, build the server and start up your extension testing environment. 1. Set up a workspace in VS Code with two workspace folders, each one having some variant of Ruff file-based configuration (`pyproject.toml`, `ruff.toml`, etc.). We'll call these folders `folder_a` and `folder_b`. 2. In each folder, open up `.vscode/settings.json`. 3. In folder A, use these settings: ```json { "ruff.codeAction.disableRuleComment": { "enable": true } } ``` 4. In folder B, use these settings: ```json { "ruff.codeAction.disableRuleComment": { "enable": false } } ``` 5. Finally, open up your VS Code User Settings and un-check the `Ruff > Code Action: Disable Rule Comment` setting. 6. When opening files in `folder_a`, you should see logs that look like this: ``` Resolved settings for document <file>: ResolvedClientSettings { fix_all: true, organize_imports: true, lint_enable: true, disable_rule_comment_enable: true, fix_violation_enable: true } ``` 7. When opening files in `folder_b`, you should see logs that look like this: ``` Resolved settings for document <file>: ResolvedClientSettings { fix_all: true, organize_imports: true, lint_enable: true, disable_rule_comment_enable: false, fix_violation_enable: true } ``` 8. To test invalid configuration, change `.vscode/settings.json` in either folder to be this: ```json { "ruff.codeAction.disableRuleComment": { "enable": "invalid" }, } ``` 10. You should now see these error logs: ``` <time> [info] <duration> ERROR ruff_server::session::settings Failed to deserialize initialization options: data did not match any variant of untagged enum InitializationOptions. Falling back to default client settings... <time> [info] <duration> WARN ruff_server::server No workspace settings found for file:///Users/jane/testbed/pandas <duration> WARN ruff_server::server No workspace settings found for file:///Users/jane/foss/scipy ``` 11. Opening files in either folder should now print the following configuration: ``` Resolved settings for document <file>: ResolvedClientSettings { fix_all: true, organize_imports: true, lint_enable: true, disable_rule_comment_enable: true, fix_violation_enable: true } ``` |