From 632965d0fa65626a53837ee378473a7009fddaba Mon Sep 17 00:00:00 2001 From: Jane Lewis Date: Fri, 26 Apr 2024 16:46:07 -0700 Subject: [PATCH] `ruff server`: Support a custom TOML configuration file (#11140) ## Summary Closes #10985. The server now supports a custom TOML configuration file as a client setting. The setting must be an absolute path to a file. If the file is called `pyproject.toml`, the server will attempt to parse it as a pyproject file - otherwise, it will attempt to parse it as a `ruff.toml` file, even if the file has a name besides `ruff.toml`. If an option is set in both the custom TOML configuration file and in the client settings directly, the latter will be used. ## Test Plan 1. Create a `ruff.toml` file outside of the workspace you are testing. Set an option that is different from the one in the configuration for your test workspace. 2. Set the path to the configuration in NeoVim: ```lua require('lspconfig').ruff.setup { init_options = { settings = { configuration = "absolute/path/to/your/configuration" } } } ``` 3. Confirm that the option in the configuration file is used, regardless of what the option is set to in the workspace configuration. 4. Add the same option, with a different value, to the NeoVim configuration directly. For example: ```lua require('lspconfig').ruff.setup { init_options = { settings = { configuration = "absolute/path/to/your/configuration", lint = { select = [] } } } } ``` 5. Confirm that the option set in client settings is used, regardless of the value in either the custom configuration file or in the workspace configuration. --- crates/ruff_server/src/session/settings.rs | 22 ++++++++++++++---- .../src/session/workspace/ruff_settings.rs | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/crates/ruff_server/src/session/settings.rs b/crates/ruff_server/src/session/settings.rs index 610c6d5bea..0786d29be2 100644 --- a/crates/ruff_server/src/session/settings.rs +++ b/crates/ruff_server/src/session/settings.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, str::FromStr}; +use std::{ffi::OsString, ops::Deref, path::PathBuf, str::FromStr}; use lsp_types::Url; use ruff_linter::{line_width::LineLength, RuleSelector}; @@ -31,6 +31,7 @@ pub(crate) struct ResolvedClientSettings { #[derive(Clone, Debug)] #[cfg_attr(test, derive(PartialEq, Eq))] pub(crate) struct ResolvedEditorSettings { + pub(super) configuration: Option, pub(super) lint_preview: Option, pub(super) format_preview: Option, pub(super) select: Option>, @@ -44,11 +45,10 @@ pub(crate) struct ResolvedEditorSettings { /// Determines how multiple conflicting configurations should be resolved - in this /// case, the configuration from the client settings and configuration from local /// `.toml` files (aka 'workspace' configuration). -#[derive(Clone, Copy, Debug, Deserialize, Default)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub(crate) enum ConfigurationPreference { - /// Configuration set in the editor takes priority over workspace configuration set in `.toml` files. + /// Configuration set in the editor takes priority over configuration set in `.toml` files. #[default] EditorFirst, /// Configuration set in `.toml` files takes priority over configuration set in the editor. @@ -62,6 +62,7 @@ pub(crate) enum ConfigurationPreference { #[cfg_attr(test, derive(PartialEq, Eq))] #[serde(rename_all = "camelCase")] pub(crate) struct ClientSettings { + configuration: Option, fix_all: Option, organize_imports: Option, lint: Option, @@ -229,6 +230,12 @@ impl ResolvedClientSettings { true, ), editor_settings: ResolvedEditorSettings { + configuration: Self::resolve_optional(all_settings, |settings| { + settings + .configuration + .as_ref() + .map(|config_path| OsString::from(config_path.clone()).into()) + }), lint_preview: Self::resolve_optional(all_settings, |settings| { settings.lint.as_ref()?.preview }), @@ -357,6 +364,7 @@ mod tests { assert_debug_snapshot!(options, @r###" HasWorkspaces { global_settings: ClientSettings { + configuration: None, fix_all: Some( false, ), @@ -411,6 +419,7 @@ mod tests { workspace_settings: [ WorkspaceSettings { settings: ClientSettings { + configuration: None, fix_all: Some( true, ), @@ -469,6 +478,7 @@ mod tests { }, WorkspaceSettings { settings: ClientSettings { + configuration: None, fix_all: Some( true, ), @@ -555,6 +565,7 @@ mod tests { disable_rule_comment_enable: false, fix_violation_enable: false, editor_settings: ResolvedEditorSettings { + configuration: None, lint_preview: Some(true), format_preview: None, select: Some(vec![ @@ -584,6 +595,7 @@ mod tests { disable_rule_comment_enable: true, fix_violation_enable: false, editor_settings: ResolvedEditorSettings { + configuration: None, lint_preview: Some(false), format_preview: None, select: Some(vec![ @@ -608,6 +620,7 @@ mod tests { GlobalOnly { settings: Some( ClientSettings { + configuration: None, fix_all: Some( false, ), @@ -671,6 +684,7 @@ mod tests { disable_rule_comment_enable: false, fix_violation_enable: true, editor_settings: ResolvedEditorSettings { + configuration: None, lint_preview: None, format_preview: None, select: None, diff --git a/crates/ruff_server/src/session/workspace/ruff_settings.rs b/crates/ruff_server/src/session/workspace/ruff_settings.rs index c8da8f3de4..120713e069 100644 --- a/crates/ruff_server/src/session/workspace/ruff_settings.rs +++ b/crates/ruff_server/src/session/workspace/ruff_settings.rs @@ -110,6 +110,7 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { filesystem_configuration: ruff_workspace::configuration::Configuration, ) -> ruff_workspace::configuration::Configuration { let ResolvedEditorSettings { + configuration, format_preview, lint_preview, select, @@ -150,6 +151,19 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { ..Default::default() }; + // Merge in the editor-specified configuration file, if it exists + let editor_configuration = if let Some(config_file_path) = configuration { + match open_configuration_file(&config_file_path, project_root) { + Ok(config_from_file) => editor_configuration.combine(config_from_file), + Err(err) => { + tracing::error!("Unable to find editor-specified configuration file {err}"); + editor_configuration + } + } + } else { + editor_configuration + }; + match configuration_preference { ConfigurationPreference::EditorFirst => { editor_configuration.combine(filesystem_configuration) @@ -161,3 +175,12 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { } } } + +fn open_configuration_file( + config_path: &Path, + project_root: &Path, +) -> crate::Result { + let options = ruff_workspace::pyproject::load_options(config_path)?; + + Configuration::from_options(options, Some(config_path), project_root) +}