Skip to main content

pytest_language_server/providers/
diagnostics.rs

1//! Diagnostics provider for pytest fixtures.
2
3use super::Backend;
4use tower_lsp_server::ls_types::*;
5use tracing::info;
6
7impl Backend {
8    /// Publish diagnostics for undeclared fixtures and circular dependencies in a file
9    pub async fn publish_diagnostics_for_file(&self, uri: &Uri, file_path: &std::path::Path) {
10        let mut diagnostics: Vec<Diagnostic> = Vec::new();
11
12        // Get config to check for disabled diagnostics
13        let config = self.config.read().await;
14        let config = &*config; // Dereference the RwLockReadGuard
15
16        // Collect undeclared fixture diagnostics (if not disabled)
17        if !config.is_diagnostic_disabled("undeclared-fixture") {
18            let undeclared = self.fixture_db.get_undeclared_fixtures(file_path);
19            for fixture in undeclared {
20                let line = Self::internal_line_to_lsp(fixture.line);
21                diagnostics.push(Diagnostic {
22                    range: Self::create_range(
23                        line,
24                        fixture.start_char as u32,
25                        line,
26                        fixture.end_char as u32,
27                    ),
28                    severity: Some(DiagnosticSeverity::WARNING),
29                    code: Some(NumberOrString::String("undeclared-fixture".to_string())),
30                    code_description: None,
31                    source: Some("pytest-lsp".to_string()),
32                    message: format!(
33                        "Fixture '{}' is used but not declared as a parameter",
34                        fixture.name
35                    ),
36                    related_information: None,
37                    tags: None,
38                    data: None,
39                });
40            }
41        }
42
43        // Collect circular dependency diagnostics (if not disabled)
44        if !config.is_diagnostic_disabled("circular-dependency") {
45            let cycles = self.fixture_db.detect_fixture_cycles_in_file(file_path);
46            for cycle in cycles {
47                let line = Self::internal_line_to_lsp(cycle.fixture.line);
48                let cycle_str = cycle.cycle_path.join(" → ");
49                diagnostics.push(Diagnostic {
50                    range: Self::create_range(
51                        line,
52                        cycle.fixture.start_char as u32,
53                        line,
54                        cycle.fixture.end_char as u32,
55                    ),
56                    severity: Some(DiagnosticSeverity::ERROR),
57                    code: Some(NumberOrString::String("circular-dependency".to_string())),
58                    code_description: None,
59                    source: Some("pytest-lsp".to_string()),
60                    message: format!("Circular fixture dependency detected: {}", cycle_str),
61                    related_information: None,
62                    tags: None,
63                    data: None,
64                });
65            }
66        }
67
68        // Collect scope mismatch diagnostics (if not disabled)
69        if !config.is_diagnostic_disabled("scope-mismatch") {
70            let mismatches = self.fixture_db.detect_scope_mismatches_in_file(file_path);
71            for mismatch in mismatches {
72                let line = Self::internal_line_to_lsp(mismatch.fixture.line);
73                diagnostics.push(Diagnostic {
74                    range: Self::create_range(
75                        line,
76                        mismatch.fixture.start_char as u32,
77                        line,
78                        mismatch.fixture.end_char as u32,
79                    ),
80                    severity: Some(DiagnosticSeverity::WARNING),
81                    code: Some(NumberOrString::String("scope-mismatch".to_string())),
82                    code_description: None,
83                    source: Some("pytest-lsp".to_string()),
84                    message: format!(
85                        "{}-scoped fixture '{}' depends on {}-scoped fixture '{}'",
86                        mismatch.fixture.scope.as_str(),
87                        mismatch.fixture.name,
88                        mismatch.dependency.scope.as_str(),
89                        mismatch.dependency.name
90                    ),
91                    related_information: None,
92                    tags: None,
93                    data: None,
94                });
95            }
96        }
97
98        info!("Publishing {} diagnostics for {:?}", diagnostics.len(), uri);
99        self.client
100            .publish_diagnostics(uri.clone(), diagnostics, None)
101            .await;
102    }
103}