-
Notifications
You must be signed in to change notification settings - Fork 211
feat: add workspace diagnostic mode support (Issue #397) #1415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
67b56b7
0ae7e0d
35b80e0
946ad22
c2d3667
761937a
64dd663
a71a9c4
88cd870
32c4cc2
2f74a46
01bf25b
99c3ec1
d4ee92b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # File with intentional type error for testing workspace diagnostic mode | ||
|
|
||
| def add_numbers(x: int, y: int) -> int: | ||
| return x + y | ||
|
|
||
| # This should cause a type error: passing string to int parameter | ||
| result = add_numbers("hello", "world") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # File that will be opened in tests (should always show diagnostics) | ||
|
|
||
| def greet(name: str) -> str: | ||
| return f"Hello, {name}" | ||
|
|
||
| # No errors in this file | ||
| message = greet("World") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Pyrefly config for workspace diagnostic mode tests |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| /* | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
|
|
||
| use lsp_server::RequestId; | ||
| use lsp_server::Response; | ||
|
|
||
| use crate::test::lsp::lsp_interaction::object_model::InitializeSettings; | ||
| use crate::test::lsp::lsp_interaction::object_model::LspInteraction; | ||
| use crate::test::lsp::lsp_interaction::util::get_test_files_root; | ||
|
|
||
| /// Test that workspace mode uses get_all_errors (shows all analyzed files) | ||
| /// This verifies the filtering logic respects workspace diagnostic mode | ||
| #[test] | ||
| fn test_workspace_mode_uses_get_all_errors() { | ||
AryanBagade marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let test_files_root = get_test_files_root(); | ||
| let mut interaction = LspInteraction::new(); | ||
| interaction.set_root(test_files_root.path().to_path_buf()); | ||
| interaction.initialize(InitializeSettings { | ||
| configuration: Some(None), | ||
| ..Default::default() | ||
| }); | ||
|
|
||
| // Send configuration change to enable workspace diagnostic mode | ||
| interaction.server.did_change_configuration(); | ||
| interaction.client.expect_configuration_request(2, None); | ||
| interaction.server.send_configuration_response( | ||
| 2, | ||
| serde_json::json!([ | ||
| { | ||
| "pyrefly": { | ||
| "displayTypeErrors": "force-on" | ||
| }, | ||
| "analysis": { | ||
| "diagnosticMode": "workspace" | ||
| } | ||
| }, | ||
| { | ||
| "pyrefly": { | ||
| "displayTypeErrors": "force-on" | ||
| }, | ||
| "analysis": { | ||
| "diagnosticMode": "workspace" | ||
| } | ||
| } | ||
| ]), | ||
| ); | ||
|
|
||
| // Open a file - in workspace mode, this should work normally | ||
| // The real test is that the code path uses get_all_errors() instead of get_errors(&handles) | ||
AryanBagade marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| interaction | ||
| .server | ||
| .did_open("workspace_diagnostic_mode/opened_file.py"); | ||
|
|
||
| // Request diagnostics - should work in workspace mode | ||
| interaction | ||
| .server | ||
| .diagnostic("workspace_diagnostic_mode/opened_file.py"); | ||
|
|
||
| // File has no errors | ||
|
||
| interaction.client.expect_response(Response { | ||
| id: RequestId::from(2), | ||
| result: Some(serde_json::json!({ | ||
| "items": [], | ||
| "kind": "full" | ||
| })), | ||
| error: None, | ||
| }); | ||
|
|
||
| interaction.shutdown(); | ||
| } | ||
|
|
||
| /// Test that openFilesOnly mode only shows errors for files that are opened | ||
| #[test] | ||
| fn test_open_files_only_mode_filters_correctly() { | ||
| let test_files_root = get_test_files_root(); | ||
| let mut interaction = LspInteraction::new(); | ||
| interaction.set_root(test_files_root.path().to_path_buf()); | ||
| interaction.initialize(InitializeSettings { | ||
| configuration: Some(None), | ||
| ..Default::default() | ||
| }); | ||
|
|
||
| // Send configuration with openFilesOnly mode (explicit) | ||
| interaction.server.did_change_configuration(); | ||
| interaction.client.expect_configuration_request(2, None); | ||
| interaction.server.send_configuration_response( | ||
| 2, | ||
| serde_json::json!([ | ||
| { | ||
| "pyrefly": { | ||
| "displayTypeErrors": "force-on" | ||
| }, | ||
| "analysis": { | ||
| "diagnosticMode": "openFilesOnly" | ||
| } | ||
| }, | ||
| { | ||
| "pyrefly": { | ||
| "displayTypeErrors": "force-on" | ||
| }, | ||
| "analysis": { | ||
| "diagnosticMode": "openFilesOnly" | ||
| } | ||
| } | ||
| ]), | ||
| ); | ||
|
|
||
| // Open a file without errors | ||
| interaction | ||
| .server | ||
| .did_open("workspace_diagnostic_mode/opened_file.py"); | ||
|
|
||
| // Request diagnostics for an UNOPENED file with errors | ||
| // In openFilesOnly mode, we should NOT get diagnostics for unopened files | ||
| interaction | ||
| .server | ||
| .diagnostic("workspace_diagnostic_mode/file_with_error.py"); | ||
|
|
||
| // Expect NO errors because the file is not opened and we're in openFilesOnly mode | ||
| interaction.client.expect_response(Response { | ||
| id: RequestId::from(2), | ||
| result: Some(serde_json::json!({ | ||
| "items": [], | ||
| "kind": "full" | ||
| })), | ||
| error: None, | ||
| }); | ||
|
|
||
| interaction.shutdown(); | ||
| } | ||
|
|
||
| /// Test default behavior (should be openFilesOnly for backward compatibility) | ||
| #[test] | ||
| fn test_default_mode_is_open_files_only() { | ||
| let test_files_root = get_test_files_root(); | ||
| let mut interaction = LspInteraction::new(); | ||
| interaction.set_root(test_files_root.path().to_path_buf()); | ||
| interaction.initialize(InitializeSettings { | ||
| configuration: Some(None), | ||
| ..Default::default() | ||
| }); | ||
|
|
||
| // Don't set diagnosticMode - should default to openFilesOnly | ||
| interaction.server.did_change_configuration(); | ||
| interaction.client.expect_configuration_request(2, None); | ||
| interaction.server.send_configuration_response( | ||
| 2, | ||
| serde_json::json!([ | ||
| {"pyrefly": {"displayTypeErrors": "force-on"}}, | ||
| {"pyrefly": {"displayTypeErrors": "force-on"}} | ||
| ]), | ||
| ); | ||
|
|
||
| // Open a file without errors | ||
| interaction | ||
| .server | ||
| .did_open("workspace_diagnostic_mode/opened_file.py"); | ||
|
|
||
| // Request diagnostics for an UNOPENED file with errors | ||
| // Default mode should be openFilesOnly, so no diagnostics for unopened files | ||
| interaction | ||
| .server | ||
| .diagnostic("workspace_diagnostic_mode/file_with_error.py"); | ||
|
|
||
| // Expect NO errors because default mode is openFilesOnly | ||
| interaction.client.expect_response(Response { | ||
| id: RequestId::from(2), | ||
| result: Some(serde_json::json!({ | ||
| "items": [], | ||
| "kind": "full" | ||
| })), | ||
| error: None, | ||
| }); | ||
|
|
||
| interaction.shutdown(); | ||
| } | ||
|
|
||
| /// Test that workspace mode does not show errors for files outside the workspace folder | ||
| #[test] | ||
| fn test_workspace_mode_excludes_files_outside_workspace() { | ||
| let test_files_root = get_test_files_root(); | ||
| let mut interaction = LspInteraction::new(); | ||
| interaction.set_root(test_files_root.path().to_path_buf()); | ||
| interaction.initialize(InitializeSettings { | ||
| configuration: Some(None), | ||
| ..Default::default() | ||
| }); | ||
|
|
||
| // Send configuration with workspace mode | ||
| interaction.server.did_change_configuration(); | ||
| interaction.client.expect_configuration_request(2, None); | ||
| interaction.server.send_configuration_response( | ||
| 2, | ||
| serde_json::json!([ | ||
| { | ||
| "pyrefly": { | ||
| "displayTypeErrors": "force-on" | ||
| }, | ||
| "analysis": { | ||
| "diagnosticMode": "workspace" | ||
| } | ||
| }, | ||
| { | ||
| "pyrefly": { | ||
| "displayTypeErrors": "force-on" | ||
| }, | ||
| "analysis": { | ||
| "diagnosticMode": "workspace" | ||
| } | ||
| } | ||
| ]), | ||
| ); | ||
|
|
||
| // Open a file in the workspace | ||
| interaction | ||
| .server | ||
| .did_open("workspace_diagnostic_mode/opened_file.py"); | ||
|
|
||
| // Request diagnostics - workspace mode should only show errors from files within the workspace | ||
| // Files outside the workspace (like dependencies) should not be shown | ||
| interaction | ||
| .server | ||
| .diagnostic("workspace_diagnostic_mode/opened_file.py"); | ||
|
|
||
| // Expect NO errors because the file itself has no errors | ||
| // More importantly, we should NOT see errors from dependencies or files outside workspace | ||
| interaction.client.expect_response(Response { | ||
| id: RequestId::from(2), | ||
| result: Some(serde_json::json!({ | ||
| "items": [], | ||
| "kind": "full" | ||
| })), | ||
| error: None, | ||
| }); | ||
|
|
||
| interaction.shutdown(); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.