ruff server: Support setting to prioritize project configuration over editor configuration (#11086)

## Summary

This is intended to address
https://github.com/astral-sh/ruff-vscode/issues/425, and is a follow-up
to https://github.com/astral-sh/ruff/pull/11062.

A new client setting is now supported by the server,
`prioritizeFileConfiguration`. This is a boolean setting (default:
`false`) that, if set to `true`, will instruct the configuration
resolver to prioritize file configuration (aka discovered TOML files)
over configuration passed in by the editor.

A corresponding extension PR has been opened, which makes this setting
available for VS Code:
https://github.com/astral-sh/ruff-vscode/pull/457.

## Test Plan

To test this with VS Code, you'll need to check out [the VS Code
PR](https://github.com/astral-sh/ruff-vscode/pull/457) that adds this
setting.

The test process is similar to
https://github.com/astral-sh/ruff/pull/11062, but in scenarios where the
editor configuration would take priority over file configuration, file
configuration should take priority.
This commit is contained in:
Jane Lewis 2024-04-26 01:17:28 -07:00 committed by GitHub
parent cd3e319538
commit 16a1f3cbcc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 7 deletions

View file

@ -28,7 +28,7 @@ pub(crate) struct ResolvedClientSettings {
/// Contains the resolved values of 'editor settings' - Ruff configuration for the linter/formatter that was passed in via /// Contains the resolved values of 'editor settings' - Ruff configuration for the linter/formatter that was passed in via
/// LSP client settings. These fields are optional because we don't want to override file-based linter/formatting settings /// LSP client settings. These fields are optional because we don't want to override file-based linter/formatting settings
/// if these were un-set. /// if these were un-set.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) struct ResolvedEditorSettings { pub(crate) struct ResolvedEditorSettings {
pub(super) lint_preview: Option<bool>, pub(super) lint_preview: Option<bool>,
@ -38,6 +38,23 @@ pub(crate) struct ResolvedEditorSettings {
pub(super) ignore: Option<Vec<RuleSelector>>, pub(super) ignore: Option<Vec<RuleSelector>>,
pub(super) exclude: Option<Vec<String>>, pub(super) exclude: Option<Vec<String>>,
pub(super) line_length: Option<LineLength>, pub(super) line_length: Option<LineLength>,
pub(super) configuration_preference: ConfigurationPreference,
}
/// 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))]
#[serde(rename_all = "camelCase")]
pub(crate) enum ConfigurationPreference {
/// Configuration set in the editor takes priority over workspace configuration set in `.toml` files.
#[default]
EditorFirst,
/// Configuration set in `.toml` files takes priority over configuration set in the editor.
FilesystemFirst,
/// `.toml` files are ignored completely, and only the editor configuration is used.
EditorOnly,
} }
/// This is a direct representation of the settings schema sent by the client. /// This is a direct representation of the settings schema sent by the client.
@ -52,6 +69,7 @@ pub(crate) struct ClientSettings {
code_action: Option<CodeActionOptions>, code_action: Option<CodeActionOptions>,
exclude: Option<Vec<String>>, exclude: Option<Vec<String>>,
line_length: Option<LineLength>, line_length: Option<LineLength>,
configuration_preference: Option<ConfigurationPreference>,
} }
/// This is a direct representation of the workspace settings schema, /// This is a direct representation of the workspace settings schema,
@ -251,6 +269,11 @@ impl ResolvedClientSettings {
Some(settings.exclude.as_ref()?.clone()) Some(settings.exclude.as_ref()?.clone())
}), }),
line_length: Self::resolve_optional(all_settings, |settings| settings.line_length), line_length: Self::resolve_optional(all_settings, |settings| settings.line_length),
configuration_preference: Self::resolve_or(
all_settings,
|settings| settings.configuration_preference,
ConfigurationPreference::EditorFirst,
),
}, },
} }
} }
@ -383,6 +406,7 @@ mod tests {
), ),
exclude: None, exclude: None,
line_length: None, line_length: None,
configuration_preference: None,
}, },
workspace_settings: [ workspace_settings: [
WorkspaceSettings { WorkspaceSettings {
@ -429,6 +453,7 @@ mod tests {
), ),
exclude: None, exclude: None,
line_length: None, line_length: None,
configuration_preference: None,
}, },
workspace: Url { workspace: Url {
scheme: "file", scheme: "file",
@ -488,6 +513,7 @@ mod tests {
), ),
exclude: None, exclude: None,
line_length: None, line_length: None,
configuration_preference: None,
}, },
workspace: Url { workspace: Url {
scheme: "file", scheme: "file",
@ -538,7 +564,8 @@ mod tests {
extend_select: None, extend_select: None,
ignore: None, ignore: None,
exclude: None, exclude: None,
line_length: None line_length: None,
configuration_preference: ConfigurationPreference::default(),
} }
} }
); );
@ -566,7 +593,8 @@ mod tests {
extend_select: None, extend_select: None,
ignore: None, ignore: None,
exclude: None, exclude: None,
line_length: None line_length: None,
configuration_preference: ConfigurationPreference::EditorFirst,
} }
} }
); );
@ -620,6 +648,7 @@ mod tests {
80, 80,
), ),
), ),
configuration_preference: None,
}, },
), ),
} }
@ -648,7 +677,8 @@ mod tests {
extend_select: None, extend_select: None,
ignore: Some(vec![RuleSelector::from_str("RUF001").unwrap()]), ignore: Some(vec![RuleSelector::from_str("RUF001").unwrap()]),
exclude: Some(vec!["third_party".into()]), exclude: Some(vec!["third_party".into()]),
line_length: Some(LineLength::try_from(80).unwrap()) line_length: Some(LineLength::try_from(80).unwrap()),
configuration_preference: ConfigurationPreference::EditorFirst,
} }
} }
); );

View file

@ -14,7 +14,7 @@ use std::{
}; };
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
use crate::session::settings::ResolvedEditorSettings; use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings};
#[derive(Default)] #[derive(Default)]
pub(crate) struct RuffSettings { pub(crate) struct RuffSettings {
@ -107,7 +107,7 @@ struct EditorConfigurationTransformer<'a>(&'a ResolvedEditorSettings, &'a Path);
impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> { impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> {
fn transform( fn transform(
&self, &self,
project_configuration: ruff_workspace::configuration::Configuration, filesystem_configuration: ruff_workspace::configuration::Configuration,
) -> ruff_workspace::configuration::Configuration { ) -> ruff_workspace::configuration::Configuration {
let ResolvedEditorSettings { let ResolvedEditorSettings {
format_preview, format_preview,
@ -117,6 +117,7 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> {
ignore, ignore,
exclude, exclude,
line_length, line_length,
configuration_preference,
} = self.0.clone(); } = self.0.clone();
let project_root = self.1; let project_root = self.1;
@ -149,6 +150,14 @@ impl<'a> ConfigurationTransformer for EditorConfigurationTransformer<'a> {
..Default::default() ..Default::default()
}; };
editor_configuration.combine(project_configuration) match configuration_preference {
ConfigurationPreference::EditorFirst => {
editor_configuration.combine(filesystem_configuration)
}
ConfigurationPreference::FilesystemFirst => {
filesystem_configuration.combine(editor_configuration)
}
ConfigurationPreference::EditorOnly => editor_configuration,
}
} }
} }