mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Preserve valid LSP settings when configuration resolution fails (#28653)
This commit is contained in:
1 parent
aeb51db2b8
commit
72b1fd3cef
3 files changed
+162
-37
No files matched your search
@@ -777,47 +777,48 @@ impl Session {
|
||||
ProjectMetadata::discover_with_uv(workspace_directory, &system, self.use_uv)
|
||||
};
|
||||
|
||||
let project = metadata
|
||||
.context("Failed to discover project configuration")
|
||||
.and_then(|mut metadata| {
|
||||
if let Some(fallback_options) = workspace.settings.fallback_options() {
|
||||
metadata.set_fallback_options(fallback_options.clone());
|
||||
let (mut metadata, discovery_result) =
|
||||
match metadata.context("Failed to discover project configuration") {
|
||||
Ok(metadata) => (metadata, Ok(())),
|
||||
Err(err) => {
|
||||
let Ok(metadata) = ProjectMetadata::from_options(
|
||||
Options::default(),
|
||||
workspace_directory.to_path_buf(),
|
||||
None,
|
||||
&UseDefaultStrategy,
|
||||
);
|
||||
(metadata.with_use_uv(self.use_uv), Err(err))
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(fallback_options) = workspace.settings.fallback_options() {
|
||||
metadata.set_fallback_options(fallback_options.clone());
|
||||
}
|
||||
|
||||
if let Some(override_options) = workspace.settings.override_options() {
|
||||
metadata.set_override_options(override_options.clone());
|
||||
}
|
||||
|
||||
// Load user configuration even if project discovery failed.
|
||||
let project = discovery_result
|
||||
.and(
|
||||
metadata
|
||||
.apply_configuration_files(&system)
|
||||
.context("Failed to apply configuration files")?;
|
||||
.context("Failed to apply configuration files"),
|
||||
)
|
||||
.and_then(|()| ProjectDatabase::fallible(metadata.clone(), system.clone()));
|
||||
|
||||
if let Some(override_options) = workspace.settings.override_options() {
|
||||
metadata.set_override_options(override_options.clone());
|
||||
}
|
||||
|
||||
ProjectDatabase::fallible(metadata, system.clone())
|
||||
});
|
||||
|
||||
let mut db = match project {
|
||||
Ok(db) => db,
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"Failed to create project for workspace `{uri}`: {err:#}. \
|
||||
Falling back to default settings"
|
||||
);
|
||||
|
||||
client.show_error_message(format!(
|
||||
"Failed to load project for workspace {uri}. {}",
|
||||
self.client_name.log_guidance(),
|
||||
));
|
||||
|
||||
let Ok(metadata) = ProjectMetadata::from_options(
|
||||
Options::default(),
|
||||
workspace_directory.to_path_buf(),
|
||||
None,
|
||||
&UseDefaultStrategy,
|
||||
)
|
||||
.map(|metadata| metadata.with_use_uv(self.use_uv));
|
||||
ProjectDatabase::use_defaults(metadata, system)
|
||||
}
|
||||
};
|
||||
let mut db = project.unwrap_or_else(|err| {
|
||||
tracing::error!(
|
||||
"Failed to load project for workspace `{uri}`: {err:#}. \
|
||||
Continuing with valid settings"
|
||||
);
|
||||
client.show_error_message(format!(
|
||||
"Failed to load project for workspace {uri}. {}",
|
||||
self.client_name.log_guidance(),
|
||||
));
|
||||
ProjectDatabase::use_defaults(metadata, system)
|
||||
});
|
||||
|
||||
// Carry forward diagnostic state if any exists
|
||||
let previous = self.projects.remove(&root);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use anyhow::Result;
|
||||
use insta::assert_json_snapshot;
|
||||
use ruff_db::system::SystemPath;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use serde_json::{Map, json};
|
||||
use ty_server::{ClientOptions, WorkspaceOptions};
|
||||
|
||||
use crate::TestServerBuilder;
|
||||
use crate::pull_diagnostics::filter_result_id;
|
||||
use crate::workspace_folders::condensed_document_diagnostic_snapshot;
|
||||
|
||||
#[test]
|
||||
fn configuration_file() -> Result<()> {
|
||||
@@ -73,6 +75,16 @@ def foo() -> str:
|
||||
Some(ClientOptions {
|
||||
workspace: WorkspaceOptions {
|
||||
configuration_file: Some(settings_path.to_string()),
|
||||
configuration: Some(
|
||||
Map::from_iter([
|
||||
("rules".to_string(), json!({"unresolved-reference": "warn"})),
|
||||
(
|
||||
"environment".to_string(),
|
||||
json!({"python": "./missing-environment"}),
|
||||
),
|
||||
])
|
||||
.into(),
|
||||
),
|
||||
..WorkspaceOptions::default()
|
||||
},
|
||||
..ClientOptions::default()
|
||||
@@ -104,6 +116,118 @@ unresolved-reference="warn"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_python_environment_preserves_valid_configuration() -> Result<()> {
|
||||
let main = SystemPath::new("src/main.py");
|
||||
let content = dedent(
|
||||
"
|
||||
import sys
|
||||
sys.platform
|
||||
",
|
||||
);
|
||||
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(SystemPath::new("src"), None)?
|
||||
.with_file(
|
||||
"src/ty.toml",
|
||||
dedent(
|
||||
r#"
|
||||
[environment]
|
||||
python = "./missing-environment"
|
||||
python-platform = "win32"
|
||||
"#,
|
||||
),
|
||||
)?
|
||||
.with_file(main, &content)?
|
||||
.build()
|
||||
.wait_until_workspaces_are_initialized();
|
||||
|
||||
let message = server.await_notification::<lsp_types::ShowMessageNotification>();
|
||||
assert_eq!(message.kind, lsp_types::MessageType::Error);
|
||||
|
||||
server.open_text_document(main, &content, 1);
|
||||
let platform = server.hover_request(main, lsp_types::Position::new(2, 5));
|
||||
assert_json_snapshot!(platform, @r#"
|
||||
{
|
||||
"contents": {
|
||||
"kind": "plaintext",
|
||||
"value": "Literal[\"win32\"]"
|
||||
},
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"character": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"character": 12
|
||||
}
|
||||
}
|
||||
}
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_include_pattern_preserves_rules_and_diagnostics() -> Result<()> {
|
||||
let main = SystemPath::new("src/main.py");
|
||||
let content = "undefined";
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(SystemPath::new("src"), None)?
|
||||
.with_file(
|
||||
"src/ty.toml",
|
||||
dedent(
|
||||
r#"
|
||||
[src]
|
||||
include = ["["]
|
||||
|
||||
[rules]
|
||||
unresolved-reference = "warn"
|
||||
unknown-rule = "warn"
|
||||
"#,
|
||||
),
|
||||
)?
|
||||
.with_file(main, content)?
|
||||
.build()
|
||||
.wait_until_workspaces_are_initialized();
|
||||
|
||||
let message = server.await_notification::<lsp_types::ShowMessageNotification>();
|
||||
assert_eq!(message.kind, lsp_types::MessageType::Error);
|
||||
|
||||
let settings_diagnostics =
|
||||
server.await_notification::<lsp_types::PublishDiagnosticsNotification>();
|
||||
assert_json_snapshot!(settings_diagnostics, @r#"
|
||||
{
|
||||
"uri": "file://<temp_dir>/src/ty.toml",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 6,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"character": 12
|
||||
}
|
||||
},
|
||||
"severity": 2,
|
||||
"code": "unknown-rule",
|
||||
"source": "ty",
|
||||
"message": "Unknown rule `unknown-rule`. Did you mean `unknown-argument`?"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#);
|
||||
|
||||
server.open_text_document(main, content, 1);
|
||||
let diagnostics = server.document_diagnostic_request(main, None);
|
||||
insta::assert_snapshot!(condensed_document_diagnostic_snapshot(diagnostics), @"0:0..0:9[WARNING]: Name `undefined` used when not defined");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configuration_overrides() -> Result<()> {
|
||||
let _filter = filter_result_id();
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ expression: diagnostics
|
||||
"character": 12
|
||||
}
|
||||
},
|
||||
"severity": 1,
|
||||
"severity": 2,
|
||||
"code": "unresolved-reference",
|
||||
"codeDescription": {
|
||||
"href": "https://ty.dev/rules#unresolved-reference"
|
||||
|
||||
Reference in new issue
Block a user