mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-31 07:37:38 +00:00
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.
This commit is contained in:
parent
5da7299b32
commit
2cc487eb22
3 changed files with 222 additions and 29 deletions
|
@ -6,9 +6,12 @@
|
|||
}
|
||||
},
|
||||
"lint": {
|
||||
"ignore": ["RUF001"],
|
||||
"run": "onSave"
|
||||
},
|
||||
"fixAll": false,
|
||||
"logLevel": "warn"
|
||||
"logLevel": "warn",
|
||||
"lineLength": 80,
|
||||
"exclude": ["third_party"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
},
|
||||
"lint": {
|
||||
"enable": true,
|
||||
"preview": false,
|
||||
"run": "onType",
|
||||
"args": [
|
||||
"--preview"
|
||||
|
@ -85,6 +86,8 @@
|
|||
},
|
||||
"lint": {
|
||||
"enable": true,
|
||||
"preview": true,
|
||||
"select": ["F", "I"],
|
||||
"run": "onType",
|
||||
"args": [
|
||||
"--preview"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
use std::{ffi::OsString, ops::Deref, path::PathBuf, str::FromStr};
|
||||
|
||||
use lsp_types::Url;
|
||||
use ruff_linter::{line_width::LineLength, RuleSelector};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
|
||||
|
@ -21,6 +22,25 @@ pub(crate) struct ResolvedClientSettings {
|
|||
#[allow(dead_code)]
|
||||
disable_rule_comment_enable: bool,
|
||||
fix_violation_enable: bool,
|
||||
// TODO(jane): Remove once editor settings resolution is implemented
|
||||
#[allow(dead_code)]
|
||||
editor_settings: ResolvedEditorSettings,
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// if these were un-set.
|
||||
#[derive(Debug, Default)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[allow(dead_code)] // TODO(jane): Remove once editor settings resolution is implemented
|
||||
pub(crate) struct ResolvedEditorSettings {
|
||||
lint_preview: Option<bool>,
|
||||
format_preview: Option<bool>,
|
||||
select: Option<Vec<RuleSelector>>,
|
||||
extend_select: Option<Vec<RuleSelector>>,
|
||||
ignore: Option<Vec<RuleSelector>>,
|
||||
exclude: Option<Vec<PathBuf>>,
|
||||
line_length: Option<LineLength>,
|
||||
}
|
||||
|
||||
/// This is a direct representation of the settings schema sent by the client.
|
||||
|
@ -30,8 +50,11 @@ pub(crate) struct ResolvedClientSettings {
|
|||
pub(crate) struct ClientSettings {
|
||||
fix_all: Option<bool>,
|
||||
organize_imports: Option<bool>,
|
||||
lint: Option<Lint>,
|
||||
code_action: Option<CodeAction>,
|
||||
lint: Option<LintOptions>,
|
||||
format: Option<FormatOptions>,
|
||||
code_action: Option<CodeActionOptions>,
|
||||
exclude: Option<Vec<String>>,
|
||||
line_length: Option<LineLength>,
|
||||
}
|
||||
|
||||
/// This is a direct representation of the workspace settings schema,
|
||||
|
@ -49,22 +72,33 @@ struct WorkspaceSettings {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Lint {
|
||||
struct LintOptions {
|
||||
enable: Option<bool>,
|
||||
preview: Option<bool>,
|
||||
select: Option<Vec<String>>,
|
||||
extend_select: Option<Vec<String>>,
|
||||
ignore: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CodeAction {
|
||||
disable_rule_comment: Option<CodeActionSettings>,
|
||||
fix_violation: Option<CodeActionSettings>,
|
||||
struct FormatOptions {
|
||||
preview: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CodeActionOptions {
|
||||
disable_rule_comment: Option<CodeActionParameters>,
|
||||
fix_violation: Option<CodeActionParameters>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CodeActionSettings {
|
||||
struct CodeActionParameters {
|
||||
enable: Option<bool>,
|
||||
}
|
||||
|
||||
|
@ -179,22 +213,80 @@ impl ResolvedClientSettings {
|
|||
},
|
||||
true,
|
||||
),
|
||||
editor_settings: ResolvedEditorSettings {
|
||||
lint_preview: Self::resolve_optional(all_settings, |settings| {
|
||||
settings.lint.as_ref()?.preview
|
||||
}),
|
||||
format_preview: Self::resolve_optional(all_settings, |settings| {
|
||||
settings.format.as_ref()?.preview
|
||||
}),
|
||||
select: Self::resolve_optional(all_settings, |settings| {
|
||||
settings
|
||||
.lint
|
||||
.as_ref()?
|
||||
.select
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.map(|rule| RuleSelector::from_str(rule).ok())
|
||||
.collect()
|
||||
}),
|
||||
extend_select: Self::resolve_optional(all_settings, |settings| {
|
||||
settings
|
||||
.lint
|
||||
.as_ref()?
|
||||
.extend_select
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.map(|rule| RuleSelector::from_str(rule).ok())
|
||||
.collect()
|
||||
}),
|
||||
ignore: Self::resolve_optional(all_settings, |settings| {
|
||||
settings
|
||||
.lint
|
||||
.as_ref()?
|
||||
.ignore
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.map(|rule| RuleSelector::from_str(rule).ok())
|
||||
.collect()
|
||||
}),
|
||||
exclude: Self::resolve_optional(all_settings, |settings| {
|
||||
Some(
|
||||
settings
|
||||
.exclude
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.map(|path| PathBuf::from(OsString::from(path)))
|
||||
.collect(),
|
||||
)
|
||||
}),
|
||||
line_length: Self::resolve_optional(all_settings, |settings| settings.line_length),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to resolve a setting using a list of available client settings as sources.
|
||||
/// Client settings that come earlier in the list take priority. This function is for fields
|
||||
/// that do not have a default value and should be left unset.
|
||||
/// Use [`ResolvedClientSettings::resolve_or`] for settings that should have default values.
|
||||
fn resolve_optional<T>(
|
||||
all_settings: &[&ClientSettings],
|
||||
get: impl Fn(&ClientSettings) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
all_settings.iter().map(Deref::deref).find_map(get)
|
||||
}
|
||||
|
||||
/// Attempts to resolve a setting using a list of available client settings as sources.
|
||||
/// Client settings that come earlier in the list take priority. `default` will be returned
|
||||
/// if none of the settings specify the requested setting.
|
||||
/// Use [`ResolvedClientSettings::resolve_optional`] if the setting should be optional instead
|
||||
/// of having a default value.
|
||||
fn resolve_or<T>(
|
||||
all_settings: &[&ClientSettings],
|
||||
get: impl Fn(&ClientSettings) -> Option<T>,
|
||||
default: T,
|
||||
) -> T {
|
||||
all_settings
|
||||
.iter()
|
||||
.map(Deref::deref)
|
||||
.find_map(get)
|
||||
.unwrap_or(default)
|
||||
Self::resolve_optional(all_settings, get).unwrap_or(default)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,6 +317,7 @@ impl Default for InitializationOptions {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
use ruff_linter::registry::Linter;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use super::*;
|
||||
|
@ -254,23 +347,39 @@ mod tests {
|
|||
true,
|
||||
),
|
||||
lint: Some(
|
||||
Lint {
|
||||
LintOptions {
|
||||
enable: Some(
|
||||
true,
|
||||
),
|
||||
preview: Some(
|
||||
true,
|
||||
),
|
||||
select: Some(
|
||||
[
|
||||
"F",
|
||||
"I",
|
||||
],
|
||||
),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
},
|
||||
),
|
||||
format: Some(
|
||||
FormatOptions {
|
||||
preview: None,
|
||||
},
|
||||
),
|
||||
code_action: Some(
|
||||
CodeAction {
|
||||
CodeActionOptions {
|
||||
disable_rule_comment: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
false,
|
||||
),
|
||||
},
|
||||
),
|
||||
fix_violation: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
false,
|
||||
),
|
||||
|
@ -278,6 +387,8 @@ mod tests {
|
|||
),
|
||||
},
|
||||
),
|
||||
exclude: None,
|
||||
line_length: None,
|
||||
},
|
||||
workspace_settings: [
|
||||
WorkspaceSettings {
|
||||
|
@ -289,23 +400,32 @@ mod tests {
|
|||
true,
|
||||
),
|
||||
lint: Some(
|
||||
Lint {
|
||||
LintOptions {
|
||||
enable: Some(
|
||||
true,
|
||||
),
|
||||
preview: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
},
|
||||
),
|
||||
format: Some(
|
||||
FormatOptions {
|
||||
preview: None,
|
||||
},
|
||||
),
|
||||
code_action: Some(
|
||||
CodeAction {
|
||||
CodeActionOptions {
|
||||
disable_rule_comment: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
false,
|
||||
),
|
||||
},
|
||||
),
|
||||
fix_violation: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
false,
|
||||
),
|
||||
|
@ -313,6 +433,8 @@ mod tests {
|
|||
),
|
||||
},
|
||||
),
|
||||
exclude: None,
|
||||
line_length: None,
|
||||
},
|
||||
workspace: Url {
|
||||
scheme: "file",
|
||||
|
@ -335,23 +457,34 @@ mod tests {
|
|||
true,
|
||||
),
|
||||
lint: Some(
|
||||
Lint {
|
||||
LintOptions {
|
||||
enable: Some(
|
||||
true,
|
||||
),
|
||||
preview: Some(
|
||||
false,
|
||||
),
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
},
|
||||
),
|
||||
format: Some(
|
||||
FormatOptions {
|
||||
preview: None,
|
||||
},
|
||||
),
|
||||
code_action: Some(
|
||||
CodeAction {
|
||||
CodeActionOptions {
|
||||
disable_rule_comment: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
true,
|
||||
),
|
||||
},
|
||||
),
|
||||
fix_violation: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
false,
|
||||
),
|
||||
|
@ -359,6 +492,8 @@ mod tests {
|
|||
),
|
||||
},
|
||||
),
|
||||
exclude: None,
|
||||
line_length: None,
|
||||
},
|
||||
workspace: Url {
|
||||
scheme: "file",
|
||||
|
@ -399,6 +534,18 @@ mod tests {
|
|||
lint_enable: true,
|
||||
disable_rule_comment_enable: false,
|
||||
fix_violation_enable: false,
|
||||
editor_settings: ResolvedEditorSettings {
|
||||
lint_preview: Some(true),
|
||||
format_preview: None,
|
||||
select: Some(vec![
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
RuleSelector::Linter(Linter::Isort)
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
exclude: None,
|
||||
line_length: None
|
||||
}
|
||||
}
|
||||
);
|
||||
let url = Url::parse("file:///Users/test/projects/scipy").expect("url should parse");
|
||||
|
@ -415,6 +562,18 @@ mod tests {
|
|||
lint_enable: true,
|
||||
disable_rule_comment_enable: true,
|
||||
fix_violation_enable: false,
|
||||
editor_settings: ResolvedEditorSettings {
|
||||
lint_preview: Some(false),
|
||||
format_preview: None,
|
||||
select: Some(vec![
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
RuleSelector::Linter(Linter::Isort)
|
||||
]),
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
exclude: None,
|
||||
line_length: None
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -432,14 +591,23 @@ mod tests {
|
|||
),
|
||||
organize_imports: None,
|
||||
lint: Some(
|
||||
Lint {
|
||||
LintOptions {
|
||||
enable: None,
|
||||
preview: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: Some(
|
||||
[
|
||||
"RUF001",
|
||||
],
|
||||
),
|
||||
},
|
||||
),
|
||||
format: None,
|
||||
code_action: Some(
|
||||
CodeAction {
|
||||
CodeActionOptions {
|
||||
disable_rule_comment: Some(
|
||||
CodeActionSettings {
|
||||
CodeActionParameters {
|
||||
enable: Some(
|
||||
false,
|
||||
),
|
||||
|
@ -448,6 +616,16 @@ mod tests {
|
|||
fix_violation: None,
|
||||
},
|
||||
),
|
||||
exclude: Some(
|
||||
[
|
||||
"third_party",
|
||||
],
|
||||
),
|
||||
line_length: Some(
|
||||
LineLength(
|
||||
80,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
}
|
||||
|
@ -469,6 +647,15 @@ mod tests {
|
|||
lint_enable: true,
|
||||
disable_rule_comment_enable: false,
|
||||
fix_violation_enable: true,
|
||||
editor_settings: ResolvedEditorSettings {
|
||||
lint_preview: None,
|
||||
format_preview: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: Some(vec![RuleSelector::from_str("RUF001").unwrap()]),
|
||||
exclude: Some(vec![PathBuf::from_str("third_party").unwrap()]),
|
||||
line_length: Some(LineLength::try_from(80).unwrap())
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue