Skip to main content

perl_lsp_diagnostics/
dead_code.rs

1//! Dead code detection using workspace-wide symbol analysis
2
3use perl_lsp_diagnostic_types::{Diagnostic, DiagnosticSeverity, DiagnosticTag};
4
5/// Detect dead code using workspace-wide symbol analysis
6///
7/// Identifies unused symbols (subroutines, variables, constants, packages)
8/// that have no references in the workspace. Returns diagnostics for symbols
9/// in the specified document.
10///
11/// # Arguments
12///
13/// * `workspace_index` - Workspace-wide symbol index
14/// * `document_uri` - URI of the document to generate diagnostics for
15/// * `source_text` - The source text of the document (for position conversion)
16/// * `line_index` - Line index helper for position conversion
17///
18/// # Returns
19///
20/// Dead code diagnostics for symbols in the specified document
21#[cfg(not(target_arch = "wasm32"))]
22pub fn detect_dead_code(
23    workspace_index: &perl_workspace_index::workspace_index::WorkspaceIndex,
24    document_uri: &str,
25    source_text: &str,
26    line_index: &perl_parser_core::position::LineStartsCache,
27) -> Vec<Diagnostic> {
28    use perl_workspace_index::workspace_index::SymbolKind;
29
30    let unused_symbols = workspace_index.find_unused_symbols();
31    let mut diagnostics = Vec::new();
32
33    for symbol in unused_symbols {
34        // Only report diagnostics for symbols in the current document
35        if symbol.uri != document_uri {
36            continue;
37        }
38
39        // Determine diagnostic code and message based on symbol kind
40        let (code, message_prefix) = match symbol.kind {
41            SymbolKind::Subroutine => ("dead-code-subroutine", "Unused subroutine"),
42            SymbolKind::Variable(_) => ("dead-code-variable", "Unused variable"),
43            SymbolKind::Constant => ("dead-code-constant", "Unused constant"),
44            SymbolKind::Package => ("dead-code-package", "Unused package"),
45            _ => continue, // Skip other symbol kinds
46        };
47
48        let message = format!("{}: '{}'", message_prefix, symbol.name);
49
50        // Convert line/column to byte offsets using the line index
51        let start_byte = line_index.position_to_offset(
52            source_text,
53            symbol.range.start.line,
54            symbol.range.start.column,
55        );
56        let end_byte = line_index.position_to_offset(
57            source_text,
58            symbol.range.end.line,
59            symbol.range.end.column,
60        );
61
62        diagnostics.push(Diagnostic {
63            range: (start_byte, end_byte),
64            severity: DiagnosticSeverity::Hint,
65            code: Some(code.to_string()),
66            message,
67            related_information: Vec::new(),
68            tags: vec![DiagnosticTag::Unnecessary],
69            suggestion: Some(format!(
70                "Remove unused {} '{}'",
71                message_prefix.to_lowercase().trim_start_matches("unused "),
72                symbol.name
73            )),
74        });
75    }
76
77    diagnostics
78}